├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── index.js ├── lib └── config.js ├── package-lock.json ├── package.json └── service-worker └── index.js /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | ## Version 23 | 24 | 25 | ## Test Case 26 | 27 | 28 | ## Steps to reproduce 29 | 30 | 31 | ## Expected Behavior 32 | 33 | 34 | ## Actual Behavior 35 | 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | Closes # . 12 | 13 | ## Changes proposed in this pull request 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .gitignore 11 | .jshintrc 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at brian@dockyard.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Improve documentation 4 | 5 | We are always looking to improve our documentation. If at some moment you are 6 | reading the documentation and something is not clear, or you can't find what you 7 | are looking for, then please open an issue with the repository. This gives us a 8 | chance to answer your question and to improve the documentation if needed. 9 | 10 | Pull requests correcting spelling or grammar mistakes are always welcome. 11 | 12 | ## Found a bug? 13 | 14 | Please try to answer at least the following questions when reporting a bug: 15 | 16 | - Which version of the project did you use when you noticed the bug? 17 | - How do you reproduce the error condition? 18 | - What happened that you think is a bug? 19 | - What should it do instead? 20 | 21 | It would really help the maintainers if you could provide a reduced test case 22 | that reproduces the error condition. 23 | 24 | ## Have a feature request? 25 | 26 | Please provide some thoughful commentary and code samples on what this feature 27 | should do and why it should be added (your use case). The minimal questions you 28 | should answer when submitting a feature request should be: 29 | 30 | - What will it allow you to do that you can't do today? 31 | - Why do you need this feature and how will it benefit other users? 32 | - Are there any drawbacks to this feature? 33 | 34 | ## Submitting a pull-request? 35 | 36 | Here are some things that will increase the chance that your pull-request will 37 | get accepted: 38 | - Did you confirm this fix/feature is something that is needed? 39 | - Did you write tests, preferably in a test driven style? 40 | - Did you add documentation for the changes you made? 41 | - Did you follow our [styleguide](https://github.com/dockyard/styleguides)? 42 | 43 | If your pull-request addresses an issue then please add the corresponding 44 | issue's number to the description of your pull-request. 45 | 46 | # How to work with this project locally 47 | 48 | ## Installation 49 | 50 | First clone this repository: 51 | 52 | ```sh 53 | git clone https://github.com/DockYard/ember-service-worker-index.git 54 | ``` 55 | 56 | 57 | 58 | ## Running tests 59 | 60 | 61 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Service Worker Index 2 | 3 | **[ember-service-worker-index is built and maintained by DockYard, contact us for expert Ember.js consulting](https://dockyard.com/ember-consulting)**. 4 | 5 | _An Ember Service Worker plugin that caches an Ember app's index file_ 6 | 7 | ## F#$& my assets aren't updating in development mode 8 | 9 | Turn on the "Update on reload" setting in the `Application > Service Workers` 10 | menu in the Chrome devtools. 11 | 12 | ## Installation 13 | 14 | ``` 15 | ember install ember-service-worker-index 16 | ``` 17 | 18 | ## Configuration 19 | 20 | The configuration is done in the `ember-cli-build.js` file: 21 | 22 | ```js 23 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 24 | 25 | module.exports = function(defaults) { 26 | var app = new EmberApp(defaults, { 27 | 'esw-index': { 28 | // Where the location of your index file is at, defaults to `index.html` 29 | location: 'app-shell.html', 30 | 31 | // Bypass esw-index and don't serve cached index file for matching URLs 32 | excludeScope: [/\/non-ember-app(\/.*)?$/, /\/another-app(\/.*)?$/], 33 | 34 | // Leave blank serve index file for all URLs, otherwise ONLY URLs which match 35 | // this pattern will be served the cached index file so you will need to list 36 | // every route in your app. 37 | includeScope: [/\/dashboard(\/.*)?$/, /\/admin(\/.*)?$/], 38 | 39 | // Indicate the caching strategy to use for the index.html file. 40 | // cache-first: read from the cache first 41 | // fallback: attempt to load but fallback to the cache after the timeout specified in "timeout" option 42 | // defaults to "cache-first" 43 | strategy: 'fallback', 44 | 45 | // Used along with strategy of "fallback". 46 | // The number of milliseconds to wait for newly loaded index file before falling back to the cache 47 | // defaults to 500 milliseconds 48 | timeout: 500, 49 | 50 | // Changing this version number will bust the cache, but you probably do not 51 | // want to be doing this manually, but rather using `versionStrategy` as 52 | // explained here http://ember-service-worker.com/documentation/configuration/#versioning 53 | version: '1' 54 | } 55 | }); 56 | 57 | return app.toTree(); 58 | }; 59 | ``` 60 | 61 | ## Authors 62 | 63 | * [Marten Schilstra](http://twitter.com/martndemus) 64 | 65 | ## Versioning 66 | 67 | This library follows [Semantic Versioning](http://semver.org) 68 | 69 | ## Want to help? 70 | 71 | Please do! We are always looking to improve this library. Please see our 72 | [Contribution Guidelines](https://github.com/dockyard/ember-service-worker-index/blob/master/CONTRIBUTING.md) 73 | on how to properly submit issues and pull requests. 74 | 75 | ## Legal 76 | 77 | [DockYard](http://dockyard.com/), Inc. © 2016 78 | 79 | [@dockyard](http://twitter.com/dockyard) 80 | 81 | [Licensed under the MIT license](http://www.opensource.org/licenses/mit-license.php) 82 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var Config = require('./lib/config'); 5 | var mergeTrees = require('broccoli-merge-trees'); 6 | 7 | module.exports = { 8 | name: 'ember-service-worker-index', 9 | 10 | included: function(app) { 11 | this._super.included && this._super.included.apply(this, arguments); 12 | this.app = app; 13 | this.app.options = this.app.options || {}; 14 | this.app.options['esw-index'] = this.app.options['esw-index'] || {}; 15 | }, 16 | 17 | treeForServiceWorker(swTree, appTree) { 18 | var options = this.app.options['esw-index']; 19 | options.env = this.app.env; 20 | var configFile = new Config([appTree], options); 21 | 22 | return mergeTrees([swTree, configFile]); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Plugin = require("broccoli-plugin"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const crypto = require("crypto"); 7 | 8 | function md5Hash(buf) { 9 | let md5 = crypto.createHash("md5"); 10 | md5.update(buf); 11 | return md5.digest("hex"); 12 | } 13 | 14 | module.exports = class Config extends Plugin { 15 | constructor(inputNodes, options) { 16 | super(inputNodes, { 17 | name: options && options.name, 18 | annotation: options && options.annotation 19 | }); 20 | 21 | this.options = options; 22 | } 23 | 24 | build() { 25 | let options = this.options; 26 | let version = options.version || "1"; 27 | let location = options.location || "index.html"; 28 | let excludeScope = options.excludeScope || []; 29 | let includeScope = options.includeScope || []; 30 | let strategy = options.strategy || 'cache-first'; 31 | let configuredTimeout = parseInt(`${options.timeout}`, 10); 32 | let timeout = isNaN(configuredTimeout) || configuredTimeout <= 0 ? 500 : configuredTimeout; // 0 would be the same as cache-first strategy 33 | 34 | let fileLocation = location; 35 | if (fileLocation[fileLocation.length - 1] === "/") { 36 | fileLocation = fileLocation + "index.html"; 37 | } 38 | 39 | let indexFilePath = path.join(this.inputPaths[0], fileLocation); 40 | let hash = md5Hash(fs.readFileSync(indexFilePath).toString()); 41 | 42 | let module = ""; 43 | module += `export const ENVIRONMENT = '${options.env}';\n`; 44 | module += `export const VERSION = '${version}';\n`; 45 | module += `export const INDEX_HTML_PATH = '${location}';\n`; 46 | module += `export const INDEX_EXCLUDE_SCOPE = [${excludeScope}];\n`; 47 | module += `export const INDEX_INCLUDE_SCOPE = [${includeScope}];\n`; 48 | module += `self.INDEX_FILE_HASH = '${hash}';\n`; 49 | module += `export const STRATEGY = '${strategy}';\n`; 50 | module += `export const TIMEOUT = '${timeout}';\n`; 51 | 52 | fs.writeFileSync(path.join(this.outputPath, "config.js"), module); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-service-worker-index", 3 | "version": "0.7.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 11 | }, 12 | "brace-expansion": { 13 | "version": "1.1.11", 14 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 15 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 16 | "requires": { 17 | "balanced-match": "^1.0.0", 18 | "concat-map": "0.0.1" 19 | } 20 | }, 21 | "broccoli-merge-trees": { 22 | "version": "3.0.1", 23 | "resolved": "https://registry.npmjs.org/broccoli-merge-trees/-/broccoli-merge-trees-3.0.1.tgz", 24 | "integrity": "sha512-EFPBLbCoyCLdjJx0lxn+acWXK/GAZesXokS4OsF7HuB+WdnV76HVJPdfwp9TaXaUkrtb7eU+ymh9tY9wOGQjMQ==", 25 | "requires": { 26 | "broccoli-plugin": "^1.3.0", 27 | "merge-trees": "^2.0.0" 28 | } 29 | }, 30 | "broccoli-plugin": { 31 | "version": "1.3.1", 32 | "resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-1.3.1.tgz", 33 | "integrity": "sha512-DW8XASZkmorp+q7J4EeDEZz+LoyKLAd2XZULXyD9l4m9/hAKV3vjHmB1kiUshcWAYMgTP1m2i4NnqCE/23h6AQ==", 34 | "requires": { 35 | "promise-map-series": "^0.2.1", 36 | "quick-temp": "^0.1.3", 37 | "rimraf": "^2.3.4", 38 | "symlink-or-copy": "^1.1.8" 39 | } 40 | }, 41 | "can-symlink": { 42 | "version": "1.0.0", 43 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/can-symlink/-/can-symlink-1.0.0.tgz", 44 | "integrity": "sha1-l7YH2KhLtsbiKLkC2GTstZS50hk=", 45 | "requires": { 46 | "tmp": "0.0.28" 47 | } 48 | }, 49 | "clean-up-path": { 50 | "version": "1.0.0", 51 | "resolved": "https://registry.npmjs.org/clean-up-path/-/clean-up-path-1.0.0.tgz", 52 | "integrity": "sha512-PHGlEF0Z6976qQyN6gM7kKH6EH0RdfZcc8V+QhFe36eRxV0SMH5OUBZG7Bxa9YcreNzyNbK63cGiZxdSZgosRw==" 53 | }, 54 | "concat-map": { 55 | "version": "0.0.1", 56 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/concat-map/-/concat-map-0.0.1.tgz", 57 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 58 | }, 59 | "debug": { 60 | "version": "2.6.9", 61 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 62 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 63 | "requires": { 64 | "ms": "2.0.0" 65 | } 66 | }, 67 | "fs-updater": { 68 | "version": "1.0.4", 69 | "resolved": "https://registry.npmjs.org/fs-updater/-/fs-updater-1.0.4.tgz", 70 | "integrity": "sha512-0pJX4mJF/qLsNEwTct8CdnnRdagfb+LmjRPJ8sO+nCnAZLW0cTmz4rTgU25n+RvTuWSITiLKrGVJceJPBIPlKg==", 71 | "requires": { 72 | "can-symlink": "^1.0.0", 73 | "clean-up-path": "^1.0.0", 74 | "heimdalljs": "^0.2.5", 75 | "heimdalljs-logger": "^0.1.9", 76 | "rimraf": "^2.6.2" 77 | } 78 | }, 79 | "fs.realpath": { 80 | "version": "1.0.0", 81 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/fs.realpath/-/fs.realpath-1.0.0.tgz", 82 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 83 | }, 84 | "glob": { 85 | "version": "7.1.3", 86 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 87 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 88 | "requires": { 89 | "fs.realpath": "^1.0.0", 90 | "inflight": "^1.0.4", 91 | "inherits": "2", 92 | "minimatch": "^3.0.4", 93 | "once": "^1.3.0", 94 | "path-is-absolute": "^1.0.0" 95 | } 96 | }, 97 | "heimdalljs": { 98 | "version": "0.2.6", 99 | "resolved": "https://registry.npmjs.org/heimdalljs/-/heimdalljs-0.2.6.tgz", 100 | "integrity": "sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==", 101 | "requires": { 102 | "rsvp": "~3.2.1" 103 | }, 104 | "dependencies": { 105 | "rsvp": { 106 | "version": "3.2.1", 107 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/rsvp/-/rsvp-3.2.1.tgz", 108 | "integrity": "sha1-B8tKXfJa3Z6Cbrxn3Mn9idsn2Eo=" 109 | } 110 | } 111 | }, 112 | "heimdalljs-logger": { 113 | "version": "0.1.10", 114 | "resolved": "https://registry.npmjs.org/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz", 115 | "integrity": "sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g==", 116 | "requires": { 117 | "debug": "^2.2.0", 118 | "heimdalljs": "^0.2.6" 119 | } 120 | }, 121 | "inflight": { 122 | "version": "1.0.6", 123 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/inflight/-/inflight-1.0.6.tgz", 124 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 125 | "requires": { 126 | "once": "^1.3.0", 127 | "wrappy": "1" 128 | } 129 | }, 130 | "inherits": { 131 | "version": "2.0.3", 132 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/inherits/-/inherits-2.0.3.tgz", 133 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 134 | }, 135 | "merge-trees": { 136 | "version": "2.0.0", 137 | "resolved": "https://registry.npmjs.org/merge-trees/-/merge-trees-2.0.0.tgz", 138 | "integrity": "sha512-5xBbmqYBalWqmhYm51XlohhkmVOua3VAUrrWh8t9iOkaLpS6ifqm/UVuUjQCeDVJ9Vx3g2l6ihfkbLSTeKsHbw==", 139 | "requires": { 140 | "fs-updater": "^1.0.4", 141 | "heimdalljs": "^0.2.5" 142 | } 143 | }, 144 | "minimatch": { 145 | "version": "3.0.4", 146 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 147 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 148 | "requires": { 149 | "brace-expansion": "^1.1.7" 150 | } 151 | }, 152 | "mktemp": { 153 | "version": "0.4.0", 154 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/mktemp/-/mktemp-0.4.0.tgz", 155 | "integrity": "sha1-bQUVYRyKjITkhKogABKbmOmB/ws=" 156 | }, 157 | "ms": { 158 | "version": "2.0.0", 159 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/ms/-/ms-2.0.0.tgz", 160 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 161 | }, 162 | "once": { 163 | "version": "1.4.0", 164 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/once/-/once-1.4.0.tgz", 165 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 166 | "requires": { 167 | "wrappy": "1" 168 | } 169 | }, 170 | "os-tmpdir": { 171 | "version": "1.0.2", 172 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 173 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 174 | }, 175 | "path-is-absolute": { 176 | "version": "1.0.1", 177 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 178 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 179 | }, 180 | "promise-map-series": { 181 | "version": "0.2.3", 182 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/promise-map-series/-/promise-map-series-0.2.3.tgz", 183 | "integrity": "sha1-wtN3r8kyU/a9A9u3d1XriKsgqEc=", 184 | "requires": { 185 | "rsvp": "^3.0.14" 186 | } 187 | }, 188 | "quick-temp": { 189 | "version": "0.1.8", 190 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/quick-temp/-/quick-temp-0.1.8.tgz", 191 | "integrity": "sha1-urAqJCq4+w3XWKPJd2sy+aXZRAg=", 192 | "requires": { 193 | "mktemp": "~0.4.0", 194 | "rimraf": "^2.5.4", 195 | "underscore.string": "~3.3.4" 196 | } 197 | }, 198 | "rimraf": { 199 | "version": "2.6.2", 200 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 201 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 202 | "requires": { 203 | "glob": "^7.0.5" 204 | } 205 | }, 206 | "rsvp": { 207 | "version": "3.6.2", 208 | "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", 209 | "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==" 210 | }, 211 | "sprintf-js": { 212 | "version": "1.1.1", 213 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/sprintf-js/-/sprintf-js-1.1.1.tgz", 214 | "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" 215 | }, 216 | "symlink-or-copy": { 217 | "version": "1.2.0", 218 | "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz", 219 | "integrity": "sha512-W31+GLiBmU/ZR02Ii0mVZICuNEN9daZ63xZMPDsYgPgNjMtg+atqLEGI7PPI936jYSQZxoLb/63xos8Adrx4Eg==" 220 | }, 221 | "tmp": { 222 | "version": "0.0.28", 223 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/tmp/-/tmp-0.0.28.tgz", 224 | "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", 225 | "requires": { 226 | "os-tmpdir": "~1.0.1" 227 | } 228 | }, 229 | "underscore.string": { 230 | "version": "3.3.5", 231 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", 232 | "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", 233 | "requires": { 234 | "sprintf-js": "^1.0.3", 235 | "util-deprecate": "^1.0.2" 236 | } 237 | }, 238 | "util-deprecate": { 239 | "version": "1.0.2", 240 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/util-deprecate/-/util-deprecate-1.0.2.tgz", 241 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 242 | }, 243 | "wrappy": { 244 | "version": "1.0.2", 245 | "resolved": "https://artifacts.netflix.com/api/npm/npm-netflix/wrappy/-/wrappy-1.0.2.tgz", 246 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-service-worker-index", 3 | "version": "0.7.2", 4 | "description": "An Ember Service Worker plugin that caches the index.html file", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "repository": "https://github.com/DockYard/ember-service-worker-index", 10 | "engines": { 11 | "node": ">= 6.0.0" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "keywords": [ 16 | "ember-addon", 17 | "ember-service-worker-plugin" 18 | ], 19 | "dependencies": { 20 | "broccoli-merge-trees": "^3.0.1", 21 | "broccoli-plugin": "^1.3.1" 22 | }, 23 | "ember-addon": { 24 | "configPath": "tests/dummy/config" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service-worker/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | INDEX_HTML_PATH, 3 | ENVIRONMENT, 4 | VERSION, 5 | INDEX_EXCLUDE_SCOPE, 6 | INDEX_INCLUDE_SCOPE, 7 | STRATEGY, 8 | TIMEOUT 9 | } from 'ember-service-worker-index/service-worker/config'; 10 | 11 | import { urlMatchesAnyPattern } from 'ember-service-worker/service-worker/url-utils'; 12 | import cleanupCaches from 'ember-service-worker/service-worker/cleanup-caches'; 13 | 14 | const CACHE_KEY_PREFIX = 'esw-index'; 15 | const CACHE_NAME = `${CACHE_KEY_PREFIX}-${VERSION}`; 16 | 17 | const INDEX_HTML_URL = new URL(INDEX_HTML_PATH, self.location).toString(); 18 | 19 | self.addEventListener('install', (event) => { 20 | event.waitUntil( 21 | fetch(INDEX_HTML_URL, { credentials: 'include' }).then((response) => { 22 | return caches 23 | .open(CACHE_NAME) 24 | .then((cache) => cache.put(INDEX_HTML_URL, response)); 25 | }) 26 | ); 27 | }); 28 | 29 | self.addEventListener('activate', (event) => { 30 | event.waitUntil(cleanupCaches(CACHE_KEY_PREFIX, CACHE_NAME)); 31 | }); 32 | 33 | self.addEventListener('fetch', (event) => { 34 | let request = event.request; 35 | let url = new URL(request.url); 36 | let isGETRequest = request.method === 'GET'; 37 | let acceptHeader = request.headers !== null ? request.headers.get('accept') : null; 38 | let isHTMLRequest = acceptHeader !== null ? acceptHeader.indexOf('text/html') !== -1 : true; 39 | let isLocal = url.origin === location.origin; 40 | let scopeExcluded = urlMatchesAnyPattern(request.url, INDEX_EXCLUDE_SCOPE); 41 | let scopeIncluded = !INDEX_INCLUDE_SCOPE.length || urlMatchesAnyPattern(request.url, INDEX_INCLUDE_SCOPE); 42 | let isTests = url.pathname === '/tests' && ENVIRONMENT === 'development'; 43 | if (!isTests && isGETRequest && isHTMLRequest && isLocal && scopeIncluded && !scopeExcluded) { 44 | if (STRATEGY === 'fallback') { 45 | cacheFallbackFetch(event, TIMEOUT); 46 | } 47 | else { 48 | return cacheFirstFetch(event); 49 | } 50 | } 51 | }); 52 | 53 | function cacheFirstFetch(event) { 54 | return event.respondWith( 55 | caches.match(INDEX_HTML_URL, { cacheName: CACHE_NAME }) 56 | .then((response) => { 57 | if (response) { 58 | return response; 59 | } 60 | 61 | /** 62 | Re-fetch the resource in the event that the cache had been cleared 63 | (mostly an issue with Safari 11.1 where clearing the cache causes 64 | the browser to throw a non-descriptive blank error page). 65 | */ 66 | return fetch(INDEX_HTML_URL, { credentials: 'include' }) 67 | .then((fetchedResponse) => { 68 | caches.open(CACHE_NAME).then((cache) => cache.put(INDEX_HTML_URL, fetchedResponse)); 69 | return fetchedResponse.clone(); 70 | }); 71 | }) 72 | ); 73 | } 74 | 75 | function cacheFallbackFetch(event, fetchTimeout) { 76 | const FETCH_TIMEOUT = fetchTimeout; 77 | let didTimeOut = false; 78 | new Promise(function(_resolve, reject) { 79 | const timeout = setTimeout(function() { 80 | didTimeOut = true; 81 | reject(new Error('Request timed out')); 82 | }, FETCH_TIMEOUT); 83 | 84 | return fetch(INDEX_HTML_URL, { credentials: 'include' }) 85 | .then(function(response) { 86 | /** 87 | Clear the timeout as cleanup 88 | */ 89 | clearTimeout(timeout); 90 | if(!didTimeOut) { 91 | caches.open(CACHE_NAME).then((cache) => cache.put(INDEX_HTML_URL, response)); 92 | return response.clone(); 93 | } 94 | }) 95 | .catch(function(err) { 96 | reject(err); 97 | }); 98 | }) 99 | .catch(function(err) { 100 | /** 101 | Rejection already happened with setTimeout 102 | */ 103 | if(didTimeOut) { 104 | return cacheFirstFetch(); 105 | } 106 | }); 107 | } 108 | --------------------------------------------------------------------------------