├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── examples ├── assets │ └── justAScript.js └── crossDomain.html ├── lib ├── logger.js ├── page.js ├── worker.js └── workerClientsList.js ├── package.json ├── swgetheaders-page.js └── swgetheaders-worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Gaël Métais 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 all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Worker Get Headers 2 | 3 | Being able to see headers information on every request can be very useful in your browser's network console. But it could sometimes be interesting to access these headers informations from the page, in JavaScript. XMLHttpRequests allow that, but most requests on a page are not XHR. 4 | 5 | Examples of things that it could help you with: 6 | - read file size on images, stylesheets, scripts, fonts, ... 7 | - detect misconfigured server (caching, compression, keep-alive, ...) 8 | - read caching and expiring informations 9 | 10 | *This project is currenlty more a "proof of concept" than a library you can rely on. Please use with extra caution and PLEASE report any bug you see.* 11 | 12 | 13 | ## How it works 14 | 15 | It uses [Service Workers](https://developer.mozilla.org/fr/docs/Web/API/Service_Worker_API) to spy on every request made, read the headers, than send the information back to the page's JavaScript. 16 | 17 | **Your website needs to be on HTTPS.** It works on localhost regardless of protocol. 18 | 19 | For security reasons, many limitations have been built in browsers regarding reading headers on **cross-domain** requests. Have a look at the [Cross domain problems](#cross-domain-problems) chapter. 20 | 21 | 22 | ## Compatible browsers 23 | 24 | This library will only work on browsers that support Service Workers: Chrome, FireFox and Opera. Edge should arrive soon. Have a look on [CanIUse](http://caniuse.com/#feat=serviceworkers). 25 | 26 | 27 | ## Install 28 | 29 | First you need to download the two scripts `swgetheaders-page.js` and `swgetheaders-worker.js` and put them in your workspace. 30 | 31 | #### The page-side script 32 | 33 | The `swgetheaders-page.js` is the library that your code will control. Just call it as a normal script on your page like this: 34 | ```html 35 | 36 | ``` 37 | You can (and probably should) concat it with your other scripts. 38 | 39 | #### The worker-side script 40 | The `swgetheaders-worker.js` is the service worker. It needs to be hosted on the same origin as the page. 41 | 42 | Add this line of code on your page to load the worker: 43 | ```js 44 | swgetheaders.registerServiceWorker('/some-path/swgetheaders-worker.js'); 45 | ``` 46 | 47 | Please note that the worker will not be available on the first page load, but only after a page change or a reload. This is due to some limitations in Service Workers. 48 | 49 | 50 | ## Usage 51 | 52 | You can subscribe to 2 kind of events: 53 | 54 | #### The `plugged` event 55 | ```js 56 | swgetheaders.on('plugged', function() { 57 | console.log('The service worker is now activated and ready to listen to requests'); 58 | }); 59 | ``` 60 | The worker is installed + the communication channel between the page and the worker is open. In most case, this appends lately on the page load and the worker has already spied some network requests. It automatically puts their headers in a waiting list and sends them to the page once the communication channel is open, so you will probably be flooded by previous requests just after this `plugged` event is sent. 61 | 62 | The status of the worker can also be asked at any time like this: `swgetheaders.isPlugged()` 63 | 64 | 65 | #### The `response` event 66 | ```js 67 | swgetheaders.on('response', function(request, response) { 68 | console.log('A response just arrived: ', response); 69 | }); 70 | ``` 71 | 72 | The worker catched a network response. The callback method takes two arguments: 73 | 74 | - First argument: the `request` object that looks like below: 75 | ```json 76 | { 77 | "method": "GET", 78 | "url": "http://localhost:8080/examples/assets/justAScript.js?q=1465589915112", 79 | "referrer": "http://localhost:8080/examples/checkGzip.html", 80 | "headers": [ 81 | { 82 | "name": "accept", 83 | "value": "*/*" 84 | }, 85 | { 86 | "name": "user-agent", 87 | "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2762.0 Safari/537.36" 88 | } 89 | ] 90 | } 91 | ``` 92 | 93 | - Second argument: the `response` object that looks like this: 94 | ```json 95 | { 96 | "status": 200, 97 | "statusText": "OK", 98 | "url": "http://localhost:8080/examples/assets/justAScript.js?q=1465589915112", 99 | "headers": [ 100 | { 101 | "name": "date", 102 | "value": "Fri, 10 Jun 2016 20:18:35 GMT" 103 | }, 104 | { 105 | "name": "last-modified", 106 | "value": "Thu, 09 Jun 2016 16:36:16 GMT" 107 | }, 108 | { 109 | "name": "server", 110 | "value": "ecstatic-0.7.6" 111 | }, 112 | { 113 | "name": "content-type", 114 | "value": "application/javascript; charset=utf-8" 115 | }, 116 | { 117 | "name": "cache-control", 118 | "value": "max-age=3600" 119 | }, 120 | { 121 | "name": "connection", 122 | "value": "keep-alive" 123 | }, 124 | { 125 | "name": "content-length", 126 | "value": "133" 127 | } 128 | ] 129 | } 130 | ``` 131 | 132 | 133 | ## Options 134 | 135 | When calling the `registerServiceWorker` method, you can add some options: 136 | 137 | ```js 138 | var options = { 139 | sameDomainOnly: false, 140 | corsExceptions: [ 141 | 'code.jquery.com', 142 | 'platform.twitter.com' 143 | ], 144 | debug: true 145 | }; 146 | 147 | swgetheaders.registerServiceWorker('/some-path/swgetheaders-worker.js', options); 148 | ``` 149 | 150 | #### `sameOriginOnly` 151 | 152 | When set to `true`, restricts spying to the same-origin requests. All third-party domains will be ignored. Default is `false`. 153 | 154 | #### `corsExceptions` 155 | 156 | By default, the library fetches every cross-domain in `no-cors` mode, to avoid the request to be blocked by the browser's cross-origin limitations. The problem is that the `no-cors` responses are "opaque", which means that headers are not accessible. 157 | 158 | If some of the cross-domain requests on your page respond with the `Access-Control-Allow-Origin` header, use this option to tell the library that it can call them with the `cors` mode. This means that the response headers will be readable (not all of the headers). Have a look at the [Cross domain problems](#cross-domain-problems) chapter for more information. 159 | 160 | This option needs to be an array of strings. When the browser makes a request to an URL, the URL will be checked with a simple `indexOf` on each of the specified exceptions, like this: `if (url.indexOf(exception) >= 0) {cors}`. So you can enter here domain names, file names, full paths, ... 161 | 162 | #### `debug` 163 | 164 | When set to `true`, the library will be much more verbose. Default is `false`. 165 | 166 | ## Full example 167 | 168 | ```js 169 | // Needed because the library uses browserify 170 | var swgetheaders = require('swgetheaders'); 171 | 172 | // Register the service worker (with some options) 173 | swgetheaders.registerServiceWorker('/swgetheaders-worker.js', { 174 | debug: true, 175 | corsExceptions: [ 176 | 'code.jquery.com', 177 | 'platform.twitter.com' 178 | ] 179 | }); 180 | 181 | swgetheaders.on('plugged', function() { 182 | console.log('The service worker is now activated and ready to listen to requests'); 183 | }); 184 | 185 | swgetheaders.on('response', function(request, response) { 186 | console.log('A response just arrived. We have both the request and the response:', request, response); 187 | }); 188 | ``` 189 | 190 | 191 | ## Cross domain problems 192 | 193 | CORS restrictions are a bit annoying. 194 | 195 | #### Opaque response 196 | When making a cross-domain request, the service-worker can't access the response headers (neither the response code). The library will fire a `response` event, but the response object will only be: `{"opaque": true}`. 197 | 198 | #### Access-Control-Allow-Origin 199 | If the cross-origin server responds with the `Access-Control-Allow-Origin: *` header (or your domain instead if `*`), then we can have more information than the opaque response. Sadly, it seems that not all headers are available. Just a few of them, such as Expire, Cache-Control, Content-Type, Last-Modified. If you have more information about which headers are concerned, please open an issue or email me. 200 | 201 | To gain access to cross-origin headers, the library needs you to specify an exception with the `corsExceptions` option. Double check the result, because if a request is sent with the exception and the server doesn't respond with the `Access-Control-Allow-Origin` header, then your request fails with an error: `Fetch API cannot load https://platform.twitter.com/widgets.js. No 'Access-Control-Allow-Origin' header is present on the requested resource.` 202 | 203 | If you don't need to spy on cross-origin requests, it's probably safer to use the `sameOriginOnly` option. 204 | 205 | #### Cookies 206 | Service workers disallow sending cookies along with cross-origin requests. This is the same security rules that applies on XMLHttpRequest. It might work if the server responds with a `Access-Control-Allow-Credentials` header, but this library is not compatible with that for the moment. 207 | 208 | Only same-origin cookies are sent. 209 | 210 | 211 | ## What's next? 212 | 213 | This library is still in very early stage. It could be made available as an npm module. It could also be enhanced to allow to modify the headers before sending the request. 214 | 215 | If you want me to go on enhancing the library, please add a star to the project on GitHub. The more stars, the more I'll do! 216 | 217 | 218 | # Author 219 | Gaël Métais. I'm a webperf freelance. Follow me on Twitter [@gaelmetais](https://twitter.com/gaelmetais), I tweet about Web Performances and Front-end! 220 | 221 | I can also help your company implement Service Workers, visit [my website](https://www.gaelmetais.com). 222 | -------------------------------------------------------------------------------- /examples/assets/justAScript.js: -------------------------------------------------------------------------------- 1 | function thatDoesNothing() { 2 | if (1 === 2) { 3 | window.alert('Your browser has a major problem'); 4 | } 5 | } 6 | 7 | thatDoesNothing(); -------------------------------------------------------------------------------- /examples/crossDomain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 |

Cross domain examples

22 |
The service worker is not fully installed, you need to refresh the page.
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | Request: 34 |
35 | Response: 36 |
37 | 38 | 39 | 113 | 114 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | var Logger = function() { 2 | 3 | var debug = false; 4 | 5 | function log(message) { 6 | if (debug) { 7 | console.info(message); 8 | } 9 | } 10 | 11 | function error(message) { 12 | console.error(message); 13 | } 14 | 15 | function getDebug() { 16 | return debug; 17 | } 18 | 19 | function setDebug(value) { 20 | debug = value; 21 | } 22 | 23 | return { 24 | log: log, 25 | error: error, 26 | getDebug: getDebug, 27 | setDebug: setDebug 28 | }; 29 | }; 30 | 31 | module.exports = new Logger(); -------------------------------------------------------------------------------- /lib/page.js: -------------------------------------------------------------------------------- 1 | var logger = require('./logger'); 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | var onPluggedCallbacks = []; 7 | var onRequestCallbacks = []; 8 | var onResponseCallbacks = []; 9 | 10 | var plugged = false; 11 | var corsExceptions = []; 12 | var sameOriginOnly = false; 13 | 14 | function registerServiceWorker(swPath, options) { 15 | if (options && options.debug !== undefined) { 16 | logger.setDebug(options.debug); 17 | } 18 | 19 | if (options && options.corsExceptions !== undefined) { 20 | corsExceptions = options.corsExceptions; 21 | } 22 | 23 | if (options && options.sameOriginOnly !== undefined) { 24 | sameOriginOnly = options.sameOriginOnly; 25 | } 26 | 27 | if (!('serviceWorker' in navigator)) { 28 | logger.log('[Page] This browser doesn\'t support service workers'); 29 | return; 30 | } 31 | 32 | if (navigator.serviceWorker.controller) { 33 | if (navigator.serviceWorker.controller.scriptURL.indexOf(swPath) >= 0) { 34 | logger.log('[Page] The service worker is already active'); 35 | openCommunicationWithWorker(); 36 | } else { 37 | logger.error('[Page] The page already has another service worker: ' + navigator.serviceWorker.controller.scriptURL); 38 | } 39 | return; 40 | } 41 | 42 | logger.log('[Page] The service worker needs to be installed'); 43 | navigator.serviceWorker.register(swPath) 44 | .then(navigator.serviceWorker.ready) 45 | .then(function (serviceWorkerRegistration) { 46 | logger.log('[Page] The service worker is registered. It will work after the page changes or is refreshed.'); 47 | }); 48 | } 49 | 50 | function openCommunicationWithWorker() { 51 | var messageChannel = new MessageChannel(); 52 | messageChannel.port1.onmessage = function(event) { 53 | if (event.data.error) { 54 | logger.error('[Page] Receveived an error message from SW:'); 55 | logger.error(event.data.error); 56 | } else { 57 | logger.log('[Page] Received a message from SW:'); 58 | logger.log(event.data); 59 | 60 | onDataReceivedFromSW(event.data); 61 | } 62 | }; 63 | navigator.serviceWorker.controller.postMessage({ 64 | type: 'hello', 65 | debug: logger.getDebug(), 66 | corsExceptions: corsExceptions, 67 | sameOriginOnly: sameOriginOnly 68 | }, [messageChannel.port2]); 69 | } 70 | 71 | function isPlugged() { 72 | return plugged; 73 | } 74 | 75 | function onDataReceivedFromSW(data) { 76 | if (data.type === 'plugged') { 77 | plugged = true; 78 | broadcast(onPluggedCallbacks); 79 | } else if (data.type === 'response') { 80 | broadcast(onResponseCallbacks, data.request, data.response); 81 | } 82 | } 83 | 84 | function broadcast(cbList, ...data) { 85 | cbList.forEach(function(cb) { 86 | cb(...data); 87 | }); 88 | } 89 | 90 | function on(event, callback) { 91 | switch(event) { 92 | case 'plugged': 93 | onPluggedCallbacks.push(callback); 94 | break; 95 | case 'response': 96 | onResponseCallbacks.push(callback); 97 | break; 98 | default: 99 | logger.error('[Page] Unknown event:'); 100 | logger.error(event); 101 | break; 102 | } 103 | } 104 | 105 | module.exports = { 106 | registerServiceWorker: registerServiceWorker, 107 | on: on, 108 | isPlugged: isPlugged 109 | }; 110 | })(); -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | var clientsList = require('./workerClientsList'); 2 | var logger = require('./logger'); 3 | 4 | 5 | var corsExceptions = []; 6 | var sameOriginOnly = false; 7 | 8 | self.addEventListener('activate', function (event) { 9 | logger.log('[Service worker] Activated'); 10 | }); 11 | 12 | self.addEventListener('message', function handler (event) { 13 | 14 | if (event.data && event.data.type === 'hello') { 15 | 16 | if (event.data.debug) { 17 | // TODO : find a solution to set debug mode before SW install and activate... 18 | logger.setDebug(true); 19 | } 20 | 21 | corsExceptions = event.data.corsExceptions; 22 | sameOriginOnly = event.data.sameOriginOnly; 23 | 24 | logger.log('[Service worker] Hello message received'); 25 | 26 | var clientId = event.source.id; 27 | clientsList.setClientPort(clientId, event.ports[0]); 28 | logger.log('[Service worker] Client\'s messaging port registered'); 29 | 30 | // Tell the client that the worker is ready 31 | sendToClient(clientId, {type: 'plugged'}); 32 | 33 | logger.log('[Service worker] Flushing the message waiting list'); 34 | var awaitingMessages = clientsList.flushWaitingList(clientId); 35 | awaitingMessages.forEach(function(data) { 36 | sendToClient(clientId, data); 37 | }); 38 | 39 | } else { 40 | logger.log('[Service worker] Un-catched message:'); 41 | logger.log(event.data); 42 | } 43 | }); 44 | 45 | self.addEventListener('fetch', function(event) { 46 | logger.log('[Service worker] Fetch event'); 47 | 48 | var request = createRequestObject(event.request); 49 | 50 | logger.log('[Service worker] Request sent'); 51 | 52 | var mode = getCorsForUrl(event.request); 53 | // Credentials would probably need some more options (which domains to send credentials to) 54 | var credentials = (mode === 'same-origin') ? 'include' : 'omit'; 55 | 56 | if (sameOriginOnly && mode !== 'same-origin') { 57 | logger.log('[Service worker] Request blocked by sameOriginOnly option'); 58 | return; 59 | } 60 | 61 | event.respondWith( 62 | 63 | fetch(new Request(event.request, {mode: mode, credentials: credentials})) 64 | 65 | .then(function(response) { 66 | logger.log('[Service worker] Response received'); 67 | 68 | sendToClient(event.clientId, { 69 | type: 'response', 70 | request: request, 71 | response: createResponseObject(response) 72 | }); 73 | 74 | return response; 75 | }) 76 | 77 | .catch(function(error) { 78 | logger.error('[Service worker] Fetch failed:'); 79 | logger.error(error); 80 | 81 | var response = new Response(new Blob(), { 82 | status: 409, 83 | statusText: '[Service worker problem] Remove the CORS exception for this request' 84 | }); 85 | 86 | sendToClient(event.clientId, { 87 | type: 'response', 88 | request: request, 89 | response: createResponseObject(response) 90 | }); 91 | }) 92 | ); 93 | 94 | }); 95 | 96 | function sendToClient(clientId, data) { 97 | self.clients.get(clientId).then(function(client) { 98 | var clientPort = clientsList.getClientPort(clientId); 99 | if (clientPort) { 100 | logger.log('[Service worker] Sending message to client'); 101 | clientPort.postMessage(data); 102 | } else { 103 | logger.log('[Service worker] Client messaging port not defined yet... Pushing message in waiting list'); 104 | clientsList.addToWaitingList(clientId, data); 105 | } 106 | }); 107 | } 108 | 109 | // Checks if the request should be fetched with the mode 'cors', 'np-cors' or 'same-origin' 110 | function getCorsForUrl(request) { 111 | var urlHost = hostParser(request.url); 112 | var referrerHost = hostParser(request.referrer); 113 | 114 | if (request.mode === 'navigate') { 115 | return 'navigate'; 116 | } 117 | 118 | if (urlHost === referrerHost || request.referrer === '') { 119 | return 'same-origin'; 120 | } 121 | 122 | var isAnException = corsExceptions.some(function(exception) { 123 | return (request.url.indexOf(exception) !== -1); 124 | }); 125 | 126 | return isAnException ? 'cors' : 'no-cors'; 127 | } 128 | 129 | function hostParser(url) { 130 | var match = url.match(/^(https?\:\/\/[^\/]*)(\/|$)/); 131 | return match && match[1]; 132 | } 133 | 134 | function formatHeaders(headersObject) { 135 | var headers = []; 136 | for (var pair of headersObject.entries()) { 137 | headers.push({ 138 | name: pair[0], 139 | value: pair[1] 140 | }); 141 | } 142 | 143 | return headers; 144 | } 145 | 146 | function createRequestObject(request) { 147 | return { 148 | method: request.method, 149 | url: request.url, 150 | referrer: request.referrer, 151 | headers: formatHeaders(request.headers) 152 | }; 153 | } 154 | 155 | function createResponseObject(response) { 156 | 157 | if (response.type === 'opaque') { 158 | // Happens on cross-origin requests. If the server returns an "Access-Control-Allow-Origin" header, you can 159 | // add the domain in the "corsExceptions" option. 160 | return { 161 | opaque: true 162 | }; 163 | } 164 | 165 | var result = { 166 | status: response.status, 167 | statusText: response.statusText 168 | }; 169 | 170 | if (response.url) { 171 | result.url = response.url; 172 | } 173 | 174 | var headers = formatHeaders(response.headers); 175 | if (headers.length > 0) { 176 | result.headers = headers; 177 | } 178 | 179 | return result; 180 | } 181 | 182 | -------------------------------------------------------------------------------- /lib/workerClientsList.js: -------------------------------------------------------------------------------- 1 | var WorkerClientsList = function() { 2 | 3 | var clientsList = {}; 4 | 5 | function setClientPort(clientId, port) { 6 | if (!clientsList[clientId]) { 7 | clientsList[clientId] = {}; 8 | } 9 | clientsList[clientId].port = port; 10 | } 11 | 12 | function getClientPort(clientId) { 13 | if (clientsList[clientId]) { 14 | return clientsList[clientId].port; 15 | } 16 | return null; 17 | } 18 | 19 | function addToWaitingList(clientId, data) { 20 | if (!clientsList[clientId]) { 21 | clientsList[clientId] = { 22 | waitingList: [] 23 | }; 24 | } 25 | clientsList[clientId].waitingList.push(data); 26 | } 27 | 28 | function flushWaitingList(clientId) { 29 | var result = []; 30 | if (clientsList[clientId] && clientsList[clientId].waitingList) { 31 | result = clientsList[clientId].waitingList; 32 | clientsList[clientId].waitingList =[]; 33 | } 34 | return result; 35 | } 36 | 37 | return { 38 | setClientPort: setClientPort, 39 | getClientPort: getClientPort, 40 | addToWaitingList: addToWaitingList, 41 | flushWaitingList: flushWaitingList 42 | }; 43 | }; 44 | 45 | module.exports = new WorkerClientsList(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sw-get-headers", 3 | "version": "0.0.1", 4 | "description": "Service Worker that reads network headers and send them back to the page", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "lint": "jshint ./lib/*.js", 8 | "compile": "browserify -r ./lib/page.js:swgetheaders -o swgetheaders-page.js && browserify lib/worker.js --outfile swgetheaders-worker.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/gmetais/sw-get-headers.git" 13 | }, 14 | "keywords": [ 15 | "JavaScript", 16 | "requests", 17 | "headers", 18 | "ServiceWorker" 19 | ], 20 | "author": "Gaël Métais", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/gmetais/sw-get-headers/issues" 24 | }, 25 | "homepage": "https://github.com/gmetais/sw-get-headers#readme", 26 | "dependencies": { 27 | }, 28 | "devDependencies": { 29 | "browserify": "13.0.0", 30 | "jshint": "2.9.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swgetheaders-page.js: -------------------------------------------------------------------------------- 1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { 67 | logger.log('[Page] The service worker is already active'); 68 | openCommunicationWithWorker(); 69 | } else { 70 | logger.error('[Page] The page already has another service worker: ' + navigator.serviceWorker.controller.scriptURL); 71 | } 72 | return; 73 | } 74 | 75 | logger.log('[Page] The service worker needs to be installed'); 76 | navigator.serviceWorker.register(swPath) 77 | .then(navigator.serviceWorker.ready) 78 | .then(function (serviceWorkerRegistration) { 79 | logger.log('[Page] The service worker is registered. It will work after the page changes or is refreshed.'); 80 | }); 81 | } 82 | 83 | function openCommunicationWithWorker() { 84 | var messageChannel = new MessageChannel(); 85 | messageChannel.port1.onmessage = function(event) { 86 | if (event.data.error) { 87 | logger.error('[Page] Receveived an error message from SW:'); 88 | logger.error(event.data.error); 89 | } else { 90 | logger.log('[Page] Received a message from SW:'); 91 | logger.log(event.data); 92 | 93 | onDataReceivedFromSW(event.data); 94 | } 95 | }; 96 | navigator.serviceWorker.controller.postMessage({ 97 | type: 'hello', 98 | debug: logger.getDebug(), 99 | corsExceptions: corsExceptions, 100 | sameOriginOnly: sameOriginOnly 101 | }, [messageChannel.port2]); 102 | } 103 | 104 | function isPlugged() { 105 | return plugged; 106 | } 107 | 108 | function onDataReceivedFromSW(data) { 109 | if (data.type === 'plugged') { 110 | plugged = true; 111 | broadcast(onPluggedCallbacks); 112 | } else if (data.type === 'response') { 113 | broadcast(onResponseCallbacks, data.request, data.response); 114 | } 115 | } 116 | 117 | function broadcast(cbList, ...data) { 118 | cbList.forEach(function(cb) { 119 | cb(...data); 120 | }); 121 | } 122 | 123 | function on(event, callback) { 124 | switch(event) { 125 | case 'plugged': 126 | onPluggedCallbacks.push(callback); 127 | break; 128 | case 'response': 129 | onResponseCallbacks.push(callback); 130 | break; 131 | default: 132 | logger.error('[Page] Unknown event:'); 133 | logger.error(event); 134 | break; 135 | } 136 | } 137 | 138 | module.exports = { 139 | registerServiceWorker: registerServiceWorker, 140 | on: on, 141 | isPlugged: isPlugged 142 | }; 143 | })(); 144 | },{"./logger":1}]},{},[]); 145 | -------------------------------------------------------------------------------- /swgetheaders-worker.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { 209 | result.headers = headers; 210 | } 211 | 212 | return result; 213 | } 214 | 215 | 216 | },{"./logger":1,"./workerClientsList":3}],3:[function(require,module,exports){ 217 | var WorkerClientsList = function() { 218 | 219 | var clientsList = {}; 220 | 221 | function setClientPort(clientId, port) { 222 | if (!clientsList[clientId]) { 223 | clientsList[clientId] = {}; 224 | } 225 | clientsList[clientId].port = port; 226 | } 227 | 228 | function getClientPort(clientId) { 229 | if (clientsList[clientId]) { 230 | return clientsList[clientId].port; 231 | } 232 | return null; 233 | } 234 | 235 | function addToWaitingList(clientId, data) { 236 | if (!clientsList[clientId]) { 237 | clientsList[clientId] = { 238 | waitingList: [] 239 | }; 240 | } 241 | clientsList[clientId].waitingList.push(data); 242 | } 243 | 244 | function flushWaitingList(clientId) { 245 | var result = []; 246 | if (clientsList[clientId] && clientsList[clientId].waitingList) { 247 | result = clientsList[clientId].waitingList; 248 | clientsList[clientId].waitingList =[]; 249 | } 250 | return result; 251 | } 252 | 253 | return { 254 | setClientPort: setClientPort, 255 | getClientPort: getClientPort, 256 | addToWaitingList: addToWaitingList, 257 | flushWaitingList: flushWaitingList 258 | }; 259 | }; 260 | 261 | module.exports = new WorkerClientsList(); 262 | },{}]},{},[2]); 263 | --------------------------------------------------------------------------------