├── .gitignore ├── lib ├── fetch_util.js ├── url_util.js ├── delta_cache_sw.js ├── fetch_proxy.js └── delta_fetch.js ├── test ├── public │ ├── test.html │ ├── mocha_driver.js │ ├── test.js │ ├── delta_cache_sw.min.js │ └── delta_cache_sw.js └── test.js ├── demo ├── public │ ├── style.css │ ├── demo.html │ ├── demo.js │ └── delta_cache_sw.js └── server.js ├── gulpfile.js ├── package.json ├── README.md └── dist ├── delta_cache_sw.min.js └── delta_cache_sw.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | npm-debug.log* 4 | -------------------------------------------------------------------------------- /lib/fetch_util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // copies all headers into a new Headers 4 | function cloneHeaders(headers) { 5 | let headersClone = new Headers(); 6 | for (let [name, value] of headers.entries()) { 7 | headersClone.append(name, value); 8 | } 9 | return headersClone; 10 | } 11 | 12 | function printHeaders(headers) { 13 | for (let [name, value] of headers.entries()) { 14 | console.log(name + ': ' + value); 15 | } 16 | } 17 | 18 | 19 | module.exports = { 20 | cloneHeaders, 21 | printHeaders 22 | }; 23 | -------------------------------------------------------------------------------- /test/public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/public/style.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | } 6 | #page { 7 | margin: 0px auto 0 auto; 8 | padding: 10px; 9 | width: 800px; 10 | text-align: left; 11 | } 12 | #columns { 13 | width: 100%; 14 | margin: 20px 0; 15 | } 16 | .column { 17 | width: 50%; 18 | float: left; 19 | vertical-align: top; 20 | } 21 | #log-box { 22 | border: 1px solid #d4dde9; 23 | border-radius: 3px; 24 | height: 250px; 25 | padding: 10px; 26 | text-align: left; 27 | overflow: scroll; 28 | } 29 | #content { 30 | border: 1px solid #d4dde9; 31 | border-radius: 3px; 32 | height: 250px; 33 | padding: 10px; 34 | overflow-y: scroll; 35 | text-align: left; 36 | margin: 0 10px 0 0; 37 | } 38 | a { 39 | color: #00B7FF; 40 | } 41 | -------------------------------------------------------------------------------- /lib/url_util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // returns whether the origins or the two urls are the same 5 | function isSameOrigin(url1, url2) { 6 | let parsedRequestUrl = getLocation(url1); 7 | let parsedCurrentUrl = getLocation(url2); 8 | 9 | return parsedRequestUrl.host === parsedCurrentUrl.host 10 | && parsedRequestUrl.protocol === parsedCurrentUrl.protocol; 11 | } 12 | 13 | function getLocation(href) { 14 | var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/); 15 | return match && { 16 | protocol: match[1], 17 | host: match[2], 18 | hostname: match[3], 19 | port: match[4], 20 | pathname: match[5], 21 | search: match[6], 22 | hash: match[7] 23 | } 24 | } 25 | 26 | module.exports = { 27 | isSameOrigin, 28 | getLocation 29 | } 30 | -------------------------------------------------------------------------------- /lib/delta_cache_sw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fetchProxy = require('./fetch_proxy'); 4 | 5 | const CACHE_NAME = 'delta-cache-v1'; 6 | 7 | self.addEventListener('install', function(event) { 8 | // forces the waiting service worker to become the active service worker 9 | self.skipWaiting(); 10 | }); 11 | 12 | self.addEventListener('activate', function(event) { 13 | // extend lifetime of event until cache is deleted 14 | event.waitUntil( 15 | // deletes cache to prevent incompatibility between requests 16 | caches.delete(CACHE_NAME) 17 | ); 18 | }); 19 | 20 | self.addEventListener('fetch', function(event) { 21 | event.respondWith( 22 | caches.open(CACHE_NAME).then( 23 | // a proxy between the client and the server 24 | // returns a promise that contains a response 25 | fetchProxy.bind(null, event.request, self.registration.scope) 26 | ) 27 | ); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var watch = require('gulp-watch'); 3 | var uglify = require('uglify-js-harmony'); 4 | var minifier = require('gulp-uglify/minifier'); 5 | var rename = require('gulp-rename'); 6 | var pump = require('pump'); 7 | var webpack = require('gulp-webpack'); 8 | 9 | gulp.task('client-compile-watcher', function(cb) { 10 | compileClientJavascripts(cb); 11 | watch('./src/delta_cache_sw.js', compileClientJavascripts) 12 | }); 13 | 14 | gulp.task('client-compile', compileClientJavascripts); 15 | 16 | function compileClientJavascripts(cb) { 17 | pump([ 18 | gulp.src('./lib/delta_cache_sw.js'), 19 | webpack({ 20 | output: { 21 | filename: 'delta_cache_sw.js' 22 | } 23 | }), 24 | gulp.dest('dist/'), 25 | gulp.dest('test/public/'), 26 | gulp.dest('demo/public/'), 27 | minifier({}, uglify), 28 | rename('delta_cache_sw.min.js'), 29 | gulp.dest('dist/') 30 | ], cb); 31 | } 32 | -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const fs = require('fs'); 5 | 6 | const express = require('express'); 7 | const createDeltaCache = require('delta-cache'); 8 | 9 | const deltaCache = createDeltaCache(); 10 | 11 | const app = express(); 12 | app.use(express.static(__dirname + '/public')); 13 | 14 | const text = fs.readFileSync(__dirname + '/sample_text/loremipsum.html') 15 | 16 | // use delta encoding (when possible) 17 | app.get('/deltaEncodedContent', (req, res) => { 18 | const time = new Buffer(new Date().toTimeString()) 19 | const dynamicText = Buffer.concat([time, text]) 20 | deltaCache.respondWithDeltaEncoding(req, res, dynamicText); 21 | }); 22 | 23 | // no delta encoding 24 | app.get('/normalContent', (req, res) => { 25 | const time = new Buffer(new Date().toTimeString()) 26 | const dynamicText = Buffer.concat([time, text]) 27 | res.end(dynamicText) 28 | }); 29 | 30 | const server = app.listen(4555, (err) => { 31 | console.log('Demo running on port 4555') 32 | }); 33 | -------------------------------------------------------------------------------- /test/public/mocha_driver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | mocha.globals(['jQuery']); 4 | 5 | const CACHE_NAME = 'delta-cache-v1'; 6 | 7 | if ('serviceWorker' in navigator) { 8 | navigator.serviceWorker.register('delta_cache_sw.js').then(function(registration) { 9 | 10 | // document must reload for requests to go through service worker 11 | if (registration.active === null) { 12 | document.write('reload to run tests (document must be controlled by the service worker)'); 13 | } 14 | else { 15 | mocha.run(() => { 16 | // uninstall service worker so it can be installed clean next time 17 | registration.unregister().then(success => { 18 | if (!success) { 19 | document.write('could not unregister service worker'); 20 | } 21 | return caches.delete(CACHE_NAME); 22 | }).then(success => { 23 | if (!success) { 24 | document.write('could not delete delta cache'); 25 | } 26 | }); 27 | }); 28 | } 29 | 30 | }).catch(function(err) { 31 | console.log(err); 32 | document.write('service worker failed to register') 33 | }); 34 | } 35 | else { 36 | document.write('service worker not supported'); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delta-cache-browser", 3 | "version": "0.1.2", 4 | "description": "Service worker that provides worry-free support for HTTP delta caching", 5 | "main": "lib/sw_delta_cache.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "node test/test.js", 11 | "run-demo": "node demo/server.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/wmsmacdonald/delta-cache-browser.git" 16 | }, 17 | "keywords": [ 18 | "encoding", 19 | "diff", 20 | "googlediffjson", 21 | "change", 22 | "caching" 23 | ], 24 | "author": "William MacDonald", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/wmsmacdonald/delta-cache-browser/issues" 28 | }, 29 | "homepage": "https://github.com/wmsmacdonald/delta-cache-browser#readme", 30 | "devDependencies": { 31 | "delta-cache": "^0.2.0", 32 | "express": "^4.14.0", 33 | "gulp": "^3.9.1", 34 | "gulp-rename": "^1.2.2", 35 | "gulp-uglify": "^1.5.4", 36 | "gulp-watch": "^4.3.9", 37 | "gulp-webpack": "^1.5.0", 38 | "open": "0.0.5", 39 | "pump": "^1.0.1", 40 | "uglify-js-harmony": "^2.6.2", 41 | "vcdiff-decoder": "^0.2.0" 42 | }, 43 | "dependencies": { 44 | "lorem-ipsum": "latest", 45 | "vcdiff-decoder": "^0.2.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/public/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Delta Cache Demo 6 | 9 | 10 | 14 | 15 | 16 |
17 |

Delta Cache Demo

18 |
19 |

Code

20 |
21 |
22 |
23 | 24 |
25 |
26 |

Content

27 |
28 |
29 |
30 |

Log

31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const fs = require('fs'); 5 | 6 | const express = require('express'); 7 | const createDeltaCache = require('delta-cache'); 8 | const open = require('open'); 9 | 10 | const deltaCache = createDeltaCache(); 11 | 12 | const app = express(); 13 | app.use(express.static('test/public')); 14 | 15 | let first = true; 16 | app.get('/dynamicContent', (req, res) => { 17 | console.log('GET', req.url); 18 | if (first) { 19 | res.locals.responseBody = 'version 1'; 20 | first = false; 21 | console.log(res.locals.responseBody); 22 | deltaCache.respondWithDeltaEncoding(req, res, res.locals.responseBody); 23 | } 24 | else { 25 | res.locals.responseBody = 'version 2'; 26 | console.log(res.locals.responseBody); 27 | deltaCache.respondWithDeltaEncoding(req, res, res.locals.responseBody, () => { 28 | process.exit() 29 | }); 30 | } 31 | }); 32 | 33 | app.get('/staticContent', (req, res) => { 34 | res.locals.responseBody = 'single response'; 35 | console.log('GET', req.url); 36 | console.log(res.locals.responseBody); 37 | deltaCache.respondWithDeltaEncoding(req, res, res.locals.responseBody); 38 | }); 39 | 40 | app.get('/noDelta', (req, res) => { 41 | console.log('GET', req.url); 42 | console.log('single response'); 43 | res.send('single response'); 44 | }); 45 | 46 | app.listen(8080, (err) => { 47 | open('http://localhost:8080/test.html'); 48 | }); 49 | -------------------------------------------------------------------------------- /demo/public/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | $(function() { 4 | const log = str => $('#log-box').append(str).append('
').scrollTop(Number.MAX_SAFE_INTEGER) 5 | 6 | function countUtf8Bytes(s) { 7 | var b = 0, i = 0, c 8 | for(;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1); 9 | return b 10 | } 11 | 12 | function showResponse(response) { 13 | return response.text().then(text => { 14 | $('#content').html(text) 15 | 16 | const actualSize = countUtf8Bytes(text) 17 | 18 | // was delta encoded in service worker 19 | if (response.headers.has('X-Delta-Length')) { 20 | const deltaSize = parseInt(response.headers.get('X-Delta-Length')) 21 | log(`Fetched file using delta encoding - ${deltaSize} bytes received instead of ${actualSize} bytes`) 22 | } 23 | else { 24 | log(`Fetched file without delta encoding - ${actualSize} bytes received`) 25 | } 26 | }) 27 | } 28 | 29 | $('#fetch-normal-button').click(function() { 30 | fetch('/normalContent').then(showResponse); 31 | }); 32 | 33 | $('#fetch-delta-button').click(function() { 34 | fetch('/deltaEncodedContent').then(showResponse); 35 | }); 36 | 37 | 38 | if ('serviceWorker' in navigator) { 39 | navigator.serviceWorker.register('delta_cache_sw.js').then(function(registration) { 40 | 41 | // document must reload for requests to go through service worker 42 | if (registration.active === null) { 43 | window.location.reload() 44 | } 45 | 46 | }).catch(err => alert('Service worker failed to register: ' + err)) 47 | } 48 | else { 49 | alert('Service worker not supported, please use Chrome') 50 | } 51 | }) 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/fetch_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const urlUtil = require('./url_util'); 4 | const deltaFetch = require('./delta_fetch'); 5 | const fetchUtil = require('./fetch_util'); 6 | 7 | function fetchProxy(originalRequest, scope, cache) { 8 | // get cached response which matches the request 9 | const newResponse = cache.match(originalRequest).then(cachedResponse => 10 | fetch( 11 | // create delta request with cached version of file 12 | deltaFetch.createDeltaRequest(originalRequest, cachedResponse.headers.get('ETag')) 13 | ) 14 | // handle delta response to create normal response 15 | .then(processServerResponse.bind(null, cachedResponse)) 16 | ) 17 | // not in cache, no delta encoding available, do normal request 18 | .catch(() => fetch(originalRequest)); 19 | 20 | newResponse.then(response => { 21 | deltaFetch.cacheIfHasEtag(cache, originalRequest, response.clone()); 22 | }) 23 | 24 | return newResponse; 25 | } 26 | 27 | // handle delta encoded and normal responses, return promise containing Response object 28 | function processServerResponse(cachedResponse, serverResponse) { 29 | 30 | // server sent a patch (rather than the full file) 31 | if (serverResponse.status === 226 && serverResponse.headers.get('Delta-Base') === cachedResponse.headers.get('ETag')) { 32 | // use the patch on the cached file to create an updated response 33 | const newResponse = deltaFetch.patchResponse(serverResponse, cachedResponse); 34 | return Promise.resolve(newResponse); 35 | } 36 | // no change from cached version 37 | else if (serverResponse.status === 304) { 38 | const newResponse = deltaFetch.convert304to200(cachedResponse, serverResonse.headers) 39 | return Promise.resolve(newResponse); 40 | } 41 | // no delta encoding 42 | else { 43 | return Promise.resolve(serverResponse); 44 | } 45 | } 46 | 47 | module.exports = fetchProxy; 48 | 49 | -------------------------------------------------------------------------------- /test/public/test.js: -------------------------------------------------------------------------------- 1 | describe('delta_cache_sw.js', function() { 2 | 3 | describe('static sample_text', function() { 4 | it('should return correct sample_text, without X-Delta-Length header', function(done) { 5 | fetch('/staticContent').then(response => { 6 | expect(response.headers.has('X-Delta-Length')).to.be.false 7 | return response.text().then(responseBody => { 8 | expect(responseBody).to.be('single response'); 9 | return fetch('/staticContent'); 10 | }); 11 | }).then(response => { 12 | expect(response.headers.has('X-Delta-Length')).to.be.false 13 | response.text().then(responseBody => { 14 | expect(responseBody).to.be('single response'); 15 | done(); 16 | }); 17 | }).catch(err => { 18 | done(err); 19 | }); 20 | }); 21 | }); 22 | 23 | describe('no delta sample_text', function() { 24 | it('should return correct sample_text', function(done) { 25 | $.get('/staticContent').then((responseBody, _, xhrRequest) => { 26 | expect(responseBody).to.be('single response'); 27 | return $.get('/staticContent'); 28 | }).then((responseBody, _, xhrRequest) => { 29 | expect(responseBody).to.be('single response'); 30 | done(); 31 | }).catch(err => { 32 | done(err); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('dynamic sample_text', function() { 38 | it('should return correct sample_text, with X-Delta-Length header for second request', function(done) { 39 | return fetch('/dynamicContent').then(response => { 40 | expect(response.headers.has('X-Delta-Length')).to.be.false 41 | return response.text().then(responseBody => { 42 | expect(responseBody).to.be('version 1'); 43 | return fetch('/dynamicContent') 44 | }); 45 | }).then(response => { 46 | expect(response.headers.get('X-Delta-Length')).to.equal('24') 47 | return response.text().then(responseBody => { 48 | expect(responseBody).to.be('version 2'); 49 | done(); 50 | }); 51 | }).catch(err => { 52 | done(err); 53 | }); 54 | }); 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /lib/delta_fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fetchUtil = require('./fetch_util'); 4 | const vcdiff = require('vcdiff-decoder'); 5 | 6 | // cache the request/response if response contains the Delta-Version header 7 | function cacheIfHasEtag(cache, request, response) { 8 | if (response.headers.has('ETag')) { 9 | return cache.delete(request).then(() => { 10 | return cache.put(request, response.clone()); 11 | }); 12 | } 13 | else { 14 | return Promise.resolve(); 15 | } 16 | } 17 | 18 | // creates copy of request that contains the Etag of the cached response 19 | // as well as other headers 20 | function createDeltaRequest(originalRequest, cachedEtag) { 21 | const headers = fetchUtil.cloneHeaders(originalRequest.headers); 22 | 23 | // set VCDIFF encoding headers 24 | headers.set('A-IM', 'vcdiff'); 25 | headers.set('If-None-Match', cachedEtag); 26 | 27 | // return new request with delta headers 28 | return new Request(originalRequest.url, { 29 | method: originalRequest.method, 30 | headers, 31 | // can't create request with mode 'navigate', so we put 'same-origin' 32 | // since we know it's the same origin 33 | mode: originalRequest.mode === 'navigate' ? 34 | 'same-origin' : originalRequest.mode, 35 | credentials: originalRequest.credentials, 36 | redirect: 'manual' 37 | }); 38 | } 39 | 40 | // create 200 response from 304 response 41 | function convert304To200(response, newHeaders) { 42 | response.blob().then(blob => { 43 | const headers = fetchUtil.cloneHeaders(newHeaders); 44 | headers.set('Content-Type', cachedResponse.headers.get('Content-Type')); 45 | headers.delete('Content-Length'); 46 | 47 | header.set('X-Delta-Length', '0'); 48 | 49 | return new Response(blob, { 50 | status: 200, 51 | statusText: 'OK', 52 | headers, 53 | url: serverResponse.url 54 | }); 55 | }) 56 | } 57 | 58 | // takes a delta response and applies its patch to the other response 59 | // returns a promise resolving to the new response 60 | function patchResponse(patchResponse, responseToChange) { 61 | return Promise.all([patchResponse.arrayBuffer(), responseToChange.arrayBuffer()]).then(([deltaArrayBuffer, sourceArrayBuffer]) => { 62 | const delta = new Uint8Array(deltaArrayBuffer); 63 | const source = new Uint8Array(sourceArrayBuffer); 64 | 65 | const updated = vcdiff.decodeSync(delta, source); 66 | const headers = fetchUtil.cloneHeaders(patchResponse.headers); 67 | 68 | if (responseToChange.headers.has('Content-Type')) { 69 | headers.set('Content-Type', responseToChange.headers.get('Content-Type')); 70 | } 71 | 72 | // discard delta headers 73 | headers.delete('Content-Length'); 74 | headers.delete('Delta-Base'); 75 | headers.delete('im'); 76 | 77 | headers.set('X-Delta-Length', deltaArrayBuffer.byteLength.toString()); 78 | 79 | return new Response(updated, { 80 | status: 200, 81 | statusText: 'OK', 82 | headers: headers, 83 | url: patchResponse.url 84 | }); 85 | }); 86 | } 87 | 88 | module.exports = { 89 | cacheIfHasEtag, 90 | createDeltaRequest, 91 | convert304To200, 92 | patchResponse 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delta-cache-browser 2 | 3 | Partially cache dynamic content and send only the changes over the wire. 4 | 5 | When used with dynamic content, [delta encoding can provide 62%-65% savings for HTML files](http://www.webreference.com/internet/software/servers/http/deltaencoding/intro/printversion.html). 6 | 7 | There's no need to change any of your requests - simply include the following code and the service worker will automatically intercept all GET requests on your domain and use delta encoding if available. 8 | 9 | ### Usage 10 | Copy dist/delta_cache_sw.js into your project. 11 | ```javascript 12 | // register the service worker if service workers are supported 13 | if ('serviceWorker' in navigator) { 14 | // register the service worker (will activate with document reload) 15 | navigator.serviceWorker.register('delta_cache_sw.js').then(function() { 16 | console.log('delta cache service worker registered'); 17 | }); 18 | } 19 | ``` 20 | 21 | ### Browser Support 22 | 23 | ![Chrome](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/37.1.9/archive/chrome_12-48/chrome_12-48_48x48.png) 24 | ![Firefox](https://cdnjs.cloudflare.com/ajax/libs/browser-logos/37.1.9/archive/firefox_1.5-3/firefox_1.5-3_48x48.png) 25 | 26 | Delta-Cache relies on services workers, which are currently only supported in Firefox and Chrome. 27 | 28 | If service workers are not supported, it will fallback to normal browser control. 29 | 30 | ### Server Support 31 | 32 | Works with any [RFC 3229](https://tools.ietf.org/html/rfc3229) compliant server. The encoding used for the deltas is `vcdiff`, an [efficient and flexible binary delta encoding format](https://tools.ietf.org/html/rfc3229). 33 | 34 | Server Implementations: 35 | * [delta-cache-node](https://github.com/wmsmacdonald/delta-cache-node) 36 | * ~~[delta-cache-express](https://github.com/wmsmacdonald/delta-cache-express)~~ (DEPRECATED) 37 | 38 | ### How it Works 39 | All GET requests go through the service worker. 40 | 41 | ![delta-cache](https://cloud.githubusercontent.com/assets/9937668/19878794/f39831b4-9fba-11e6-8e2c-033c1bb46d01.png) 42 | 43 | The first time the URL is requested, it will get the content from the server as usual. The service worker will then cache the response, so the next time the same URL is requested, it'll ask the server to use delta encoding. The server then sends the difference between the old and the new file. The service worker will use this delta to compute the new file using the cached version. 44 | 45 | Because only the changes are sent from the server, the file sizes are much smaller. 46 | 47 | ### When to Use 48 | 49 | Delta encoding works well with content that barely changes, such as server generated templates and some web API endpoints. 50 | 51 | ### Identical Responses 52 | The service worker always returns a response that is identical (including headers) to one without using the service worker. However, the service worker will add a `X-Delta-Length` header if it uses delta encoding. The value of this header is the integer size of the delta request body (without headers) in bytes. 53 | 54 | ### Demo 55 | ```bash 56 | npm run-script run-demo 57 | ``` 58 | 59 | ### Testing 60 | ```bash 61 | npm test 62 | ``` 63 | This command will open a browser page. Then reload the page. Then, the service worker will install and the Mocha test suite will run. The service worker is automatically removed when the mocha test finishes. 64 | 65 | Open chrome://serviceworker-internals/ in Chrome to debug or remove the service worker. 66 | -------------------------------------------------------------------------------- /dist/delta_cache_sw.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(s){if(n[s])return n[s].exports;var i=n[s]={exports:{},id:s,loaded:!1};return e[s].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";const s=n(1),i="delta-cache-v1";self.addEventListener("install",function(e){self.skipWaiting()}),self.addEventListener("activate",function(e){e.waitUntil(caches.delete(i))}),self.addEventListener("fetch",function(e){e.respondWith(caches.open(i).then(s.bind(null,e.request,self.registration.scope)))})},function(e,t,n){"use strict";function s(e,t,n){const s=n.match(e).then(t=>fetch(o.createDeltaRequest(e,t.headers.get("ETag"))).then(i.bind(null,t))).catch(()=>fetch(e));return s.then(t=>{o.cacheIfHasEtag(n,e,t.clone())}),s}function i(e,t){if(226===t.status&&t.headers.get("Delta-Base")===e.headers.get("ETag")){const n=o.patchResponse(t,e);return Promise.resolve(n)}if(304===t.status){const t=o.convert304to200(e,serverResonse.headers);return Promise.resolve(t)}return Promise.resolve(t)}const o=(n(2),n(3));n(4);e.exports=s},function(e,t){"use strict";function n(e,t){let n=s(e),i=s(t);return n.host===i.host&&n.protocol===i.protocol}function s(e){var t=e.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/);return t&&{protocol:t[1],host:t[2],hostname:t[3],port:t[4],pathname:t[5],search:t[6],hash:t[7]}}e.exports={isSameOrigin:n,getLocation:s}},function(e,t,n){"use strict";function s(e,t,n){return n.headers.has("ETag")?e.delete(t).then(()=>{return e.put(t,n.clone())}):Promise.resolve()}function i(e,t){const n=a.cloneHeaders(e.headers);return n.set("A-IM","vcdiff"),n.set("If-None-Match",t),new Request(e.url,{method:e.method,headers:n,mode:"navigate"===e.mode?"same-origin":e.mode,credentials:e.credentials,redirect:"manual"})}function o(e,t){e.blob().then(e=>{const n=a.cloneHeaders(t);return n.set("Content-Type",cachedResponse.headers.get("Content-Type")),n.delete("Content-Length"),header.set("X-Delta-Length","0"),new Response(e,{status:200,statusText:"OK",headers:n,url:serverResponse.url})})}function r(e,t){return Promise.all([e.arrayBuffer(),t.arrayBuffer()]).then(([n,s])=>{const i=new Uint8Array(n),o=new Uint8Array(s),r=h.decodeSync(i,o),u=a.cloneHeaders(e.headers);return t.headers.has("Content-Type")&&u.set("Content-Type",t.headers.get("Content-Type")),u.delete("Content-Length"),u.delete("Delta-Base"),u.delete("im"),u.set("X-Delta-Length",n.byteLength.toString()),new Response(r,{status:200,statusText:"OK",headers:u,url:e.url})})}const a=n(4),h=n(5);e.exports={cacheIfHasEtag:s,createDeltaRequest:i,convert304To200:o,patchResponse:r}},function(e,t){"use strict";function n(e){let t=new Headers;for(let[n,s]of e.entries())t.append(n,s);return t}function s(e){for(let[t,n]of e.entries())console.log(t+": "+n)}e.exports={cloneHeaders:n,printHeaders:s}},function(e,t,n){"use strict";function s(e,t){let n=new o(e,t);return n.decode()}function i(e,t){}const o=(n(6),n(7));e.exports={decodeSync:s,decode:i}},function(e,t){"use strict";function n(e){let t={};return e.forEach(e=>{let n=function(t){var n=Error.apply(this,arguments);n.name=this.name=e,this.stack=n.stack,this.message=n.message,this.name=e,this.message=t};n.prototype=Object.create(Error.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),t[e]=n}),t}e.exports=n(["NotImplemented","InvalidDelta"])},function(e,t,n){"use strict";function s(e,t){this.delta=e,this.position=0,this.source=t,this.targetWindows=new r.TypedArrayList}function i(e,t,n,s){this.U=e,this.UTargetPosition=t,this.data=n,this.dataPosition=0,this.addresses=s,this.addressesPosition=0,this.nearCache=new u(4),this.sameCache=new c(3)}const o=n(6),r=n(8),a=n(9),h=n(10),u=n(13),c=n(14);s.prototype.decode=function(){for(this._consumeHeader();this._consumeWindow(););let e=this.targetWindows.typedArrays.reduce((e,t)=>t.length+e,0),t=new Uint8Array(e),n=0;for(let s=0;s>1;if(n||s)throw new o.NotImplemented("non-zero Hdr_Indicator (VCD_DECOMPRESS or VCD_CODETABLE bit is set)");this.position+=5},s.prototype._consumeWindow=function(){let e=this.delta[this.position++],t=1&e,n=1&e>>1;if(t&&n)throw new o.InvalidDelta("VCD_SOURCE and VCD_TARGET cannot both be set in Win_Indicator");if(t){let e,t,n;({value:e,position:this.position})=a(this.delta,this.position),{value:t,position:this.position}=a(this.delta,this.position),{value:n,position:this.position}=a(this.delta,this.position);let s=this.source.slice(t,t+e);this._buildTargetWindow(this.position,s),this.position+=n}else if(n)throw new o.NotImplemented("non-zero VCD_TARGET in Win_Indicator");return this.position{e.execute(a)});let u=o.typedArrays[1];this.targetWindows.add(u)},i.prototype.getNextAddressInteger=function(){let e;return{value:e,position:this.addressesPosition}=a(this.addresses,this.addressesPosition),e},i.prototype.getNextAddressByte=function(){let e=this.addresses[this.addressesPosition++];return e},e.exports=s},function(e,t){"use strict";function n(e){let t=String.fromCharCode.apply(null,e),n=decodeURIComponent(escape(t));return n}function s(e){for(var t=new Uint8Array(e.length),n=0,s=e.length;nt?s-1:s}o.prototype.add=function(e){let t=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],n=t.filter(t=>e instanceof t);if(n.length<1)throw Error("Given "+typeof e+" when expected a TypedArray");let s;if(0===this.typedArrays.length)s=0;else{let e=this.startIndexes.length-1,t=this.startIndexes[e],n=this.typedArrays[e].length;s=t+n}this.startIndexes.push(s),this.typedArrays.push(e),this.length+=s+e.length},o.prototype.get=function(e){let t=r(this.startIndexes,e),n=e-this.startIndexes[t];return this.typedArrays[t][n]},o.prototype.set=function(e,t){if("number"!=typeof e||isNaN(e))throw new Error("Given non-number index: "+e);let n=r(this.startIndexes,e),s=e-this.startIndexes[n];this.typedArrays[n][s]=t},e.exports={uint8ArrayToString:n,stringToUint8Array:s,equal:i,TypedArrayList:o}},function(e,t){"use strict";function n(e,t){let n=e[t++],s=[],i=0;for(;n>=128;){if(s.unshift(n-128),n=e[t++],i>1e3)throw new Error("Integer is probably too long");i++}return s.unshift(n),{position:t,value:s.reduce((e,t,n)=>e+t*Math.pow(128,n),0)}}e.exports=n},function(e,t,n){"use strict";function s(e,t){let n,s,a,h;if({value:n,position:t}=o(e,t),0!==e[t])throw new i.NotImplemented("VCD_DECOMPRESS is not supported, Delta_Indicator must be zero at byte "+t+" and not "+e[t]);t++,{value:s,position:t}=o(e,t),{value:a,position:t}=o(e,t),{value:h,position:t}=o(e,t);let u=t+s,c=e.slice(t,u),p=u+a,d=e.slice(u,p),l=r(d),f=p+h,y=e.slice(p,f);t=f;let g={targetWindowLength:n,position:t,data:c,instructions:l,addresses:y};return g}const i=n(6),o=n(9),r=n(11);e.exports=s},function(e,t,n){"use strict";function s(e){let t=[],n=0;for(;n=0&&n0&&(this.near[this.nextSlot]=e,this.nextSlot=(this.nextSlot+1)%this.near.length)},n.prototype.get=function(e,t){let n=this.near[e]+t;return n},e.exports=n},function(e,t){"use strict";function n(e){this.size=e,this.same=new Array(256*this.size).fill(0)}n.prototype.update=function(e){this.same.length>0&&(this.same[e%(256*this.size)]=e)},n.prototype.get=function(e,t){let n=this.same[256*e+t];return n},e.exports=n}]); -------------------------------------------------------------------------------- /test/public/delta_cache_sw.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e,n){return n.headers.has("ETag")?t["delete"](e).then((()=>{return t.put(e,n.clone())})):void 0}function i(t,e){let n=s(t),r=s(e);return n.host===r.host&&n.protocol===r.protocol}function s(t){var e=t.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/);return e&&{protocol:e[1],host:e[2],hostname:e[3],port:e[4],pathname:e[5],search:e[6],hash:e[7]}}const h=n(1);let a;self.addEventListener("install",function(t){self.skipWaiting()}),self.addEventListener("activate",function(t){t.waitUntil(caches["delete"]("delta").then((()=>caches.open("delta"))).then((t=>{a=t})))}),self.onfetch=function(t){let e,n,s,f=t.request,l=a.match(t.request);l["catch"]((t=>console.log(t)));let o=l.then((t=>{console.log(t),n=t;let r=i(f.url,self.registration.scope);if(r&&void 0!==n){let s=h.cloneHeaders(f.headers);e=n.headers.get("ETag"),s.set("A-IM","googlediffjson"),s.set("If-None-Match",e);let a={method:f.method,headers:s,mode:f.mode,credentials:f.credentials,redirect:"manual"};return"navigate"===f.mode&&(a.mode="same-origin"),fetch(new Request(f.url,a))}return fetch(f)})).then((t=>{return 226===t.status&&t.headers.get("Delta-Base")===e?h.patchResponse(t,n):304===t.status?n.text().then((e=>{let r=h.cloneHeaders(t.headers);return r.set("Content-Type",n.headers.get("Content-Type")),r["delete"]("Content-Length"),new Response(e,{status:200,statusText:"OK",headers:r,url:t.url})})):Promise.resolve(t)})).then((e=>{return s=e,r(a,t.request,s.clone())})).then((()=>{return s}))["catch"]((t=>{console.log(t)}));t.respondWith(o)}},function(t,e,n){"use strict";function r(t){let e=new Headers;for(let[n,r]of t.entries())e.append(n,r);return e}function i(t){for(let[e,n]of t.entries())console.log(e+": "+n)}function s(t,e){return Promise.all([t.json(),e.text()]).then((([n,i])=>{let s=a.patch_apply(n,i)[0],h=r(t.headers);return h.set("Content-Type",e.headers.get("Content-Type")),h["delete"]("Content-Length"),new Response(s,{status:200,statusText:"OK",headers:h,url:t.url})}))}const h=n(2);let a=new h;t.exports={cloneHeaders:r,printHeaders:i,patchResponse:s}},function(t,e){"use strict";function n(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32}var r=-1,i=1,s=0;n.Diff,n.prototype.diff_main=function(t,e,n,r){"undefined"==typeof r&&(r=this.Diff_Timeout<=0?Number.MAX_VALUE:(new Date).getTime()+1e3*this.Diff_Timeout);var i=r;if(null==t||null==e)throw new Error("Null input. (diff_main)");if(t==e)return t?[[s,t]]:[];"undefined"==typeof n&&(n=!0);var h=n,a=this.diff_commonPrefix(t,e),f=t.substring(0,a);t=t.substring(a),e=e.substring(a),a=this.diff_commonSuffix(t,e);var l=t.substring(t.length-a);t=t.substring(0,t.length-a),e=e.substring(0,e.length-a);var o=this.diff_compute_(t,e,h,i);return f&&o.unshift([s,f]),l&&o.push([s,l]),this.diff_cleanupMerge(o),o},n.prototype.diff_compute_=function(t,e,n,h){var a;if(!t)return[[i,e]];if(!e)return[[r,t]];var f=t.length>e.length?t:e,l=t.length>e.length?e:t,o=f.indexOf(l);if(-1!=o)return a=[[i,f.substring(0,o)],[s,l],[i,f.substring(o+l.length)]],t.length>e.length&&(a[0][0]=a[2][0]=r),a;if(1==l.length)return[[r,t],[i,e]];var g=this.diff_halfMatch_(t,e);if(g){var c=g[0],u=g[1],p=g[2],d=g[3],_=g[4],b=this.diff_main(c,p,n,h),v=this.diff_main(u,d,n,h);return b.concat([[s,_]],v)}return n&&t.length>100&&e.length>100?this.diff_lineMode_(t,e,h):this.diff_bisect_(t,e,h)},n.prototype.diff_lineMode_=function(t,e,n){var h=this.diff_linesToChars_(t,e);t=h.chars1,e=h.chars2;var a=h.lineArray,f=this.diff_main(t,e,!1,n);this.diff_charsToLines_(f,a),this.diff_cleanupSemantic(f),f.push([s,""]);for(var l=0,o=0,g=0,c="",u="";l=1&&g>=1){f.splice(l-o-g,o+g),l=l-o-g;for(var h=this.diff_main(c,u,!1,n),p=h.length-1;p>=0;p--)f.splice(l,0,h[p]);l+=h.length}g=0,o=0,c="",u=""}l++}return f.pop(),f},n.prototype.diff_bisect_=function(t,e,n){for(var s=t.length,h=e.length,a=Math.ceil((s+h)/2),f=a,l=2*a,o=new Array(l),g=new Array(l),c=0;l>c;c++)o[c]=-1,g[c]=-1;o[f+1]=0,g[f+1]=0;for(var u=s-h,p=u%2!=0,d=0,_=0,b=0,v=0,m=0;a>m&&!((new Date).getTime()>n);m++){for(var x=-m+d;m-_>=x;x+=2){var M,y=f+x;M=x==-m||x!=m&&o[y-1]M&&h>w&&t.charAt(M)==e.charAt(w);)M++,w++;if(o[y]=M,M>s)_+=2;else if(w>h)d+=2;else if(p){var A=f+u-x;if(A>=0&&l>A&&-1!=g[A]){var k=s-g[A];if(M>=k)return this.diff_bisectSplit_(t,e,M,w,n)}}}for(var E=-m+b;m-v>=E;E+=2){var k,A=f+E;k=E==-m||E!=m&&g[A-1]k&&h>T&&t.charAt(s-k-1)==e.charAt(h-T-1);)k++,T++;if(g[A]=k,k>s)v+=2;else if(T>h)b+=2;else if(!p){var y=f+u-E;if(y>=0&&l>y&&-1!=o[y]){var M=o[y],w=f+M-y;if(k=s-k,M>=k)return this.diff_bisectSplit_(t,e,M,w,n)}}}}return[[r,t],[i,e]]},n.prototype.diff_bisectSplit_=function(t,e,n,r,i){var s=t.substring(0,n),h=e.substring(0,r),a=t.substring(n),f=e.substring(r),l=this.diff_main(s,h,!1,i),o=this.diff_main(a,f,!1,i);return l.concat(o)},n.prototype.diff_linesToChars_=function(t,e){function n(t){for(var e="",n=0,s=-1,h=r.length;sn;)t.substring(s,i)==e.substring(s,i)?(n=i,s=n):r=i,i=Math.floor((r-n)/2+n);return i},n.prototype.diff_commonSuffix=function(t,e){if(!t||!e||t.charAt(t.length-1)!=e.charAt(e.length-1))return 0;for(var n=0,r=Math.min(t.length,e.length),i=r,s=0;i>n;)t.substring(t.length-i,t.length-s)==e.substring(e.length-i,e.length-s)?(n=i,s=n):r=i,i=Math.floor((r-n)/2+n);return i},n.prototype.diff_commonOverlap_=function(t,e){var n=t.length,r=e.length;if(0==n||0==r)return 0;n>r?t=t.substring(n-r):r>n&&(e=e.substring(0,n));var i=Math.min(n,r);if(t==e)return i;for(var s=0,h=1;;){var a=t.substring(i-h),f=e.indexOf(a);if(-1==f)return s;h+=f,(0==f||t.substring(i-h)==e.substring(0,h))&&(s=h,h++)}},n.prototype.diff_halfMatch_=function(t,e){function n(t,e,n){for(var r,i,s,a,f=t.substring(n,n+Math.floor(t.length/4)),l=-1,o="";-1!=(l=e.indexOf(f,l+1));){var g=h.diff_commonPrefix(t.substring(n),e.substring(l)),c=h.diff_commonSuffix(t.substring(0,n),e.substring(0,l));o.length=t.length?[r,i,s,a,o]:null}if(this.Diff_Timeout<=0)return null;var r=t.length>e.length?t:e,i=t.length>e.length?e:t;if(r.length<4||2*i.lengthf[4].length?a:f:a;var l,o,g,c;t.length>e.length?(l=s[0],o=s[1],g=s[2],c=s[3]):(g=s[0],c=s[1],l=s[2],o=s[3]);var u=s[4];return[l,o,g,c,u]},n.prototype.diff_cleanupSemantic=function(t){for(var e=!1,n=[],h=0,a=null,f=0,l=0,o=0,g=0,c=0;f0?n[h-1]:-1,l=0,o=0,g=0,c=0,a=null,e=!0)),f++;for(e&&this.diff_cleanupMerge(t),this.diff_cleanupSemanticLossless(t),f=1;f=_?(d>=u.length/2||d>=p.length/2)&&(t.splice(f,0,[s,p.substring(0,d)]),t[f-1][1]=u.substring(0,u.length-d),t[f+1][1]=p.substring(d),f++):(_>=u.length/2||_>=p.length/2)&&(t.splice(f,0,[s,u.substring(0,_)]),t[f-1][0]=i,t[f-1][1]=p.substring(0,p.length-_),t[f+1][0]=r,t[f+1][1]=u.substring(_),f++),f++}f++}},n.prototype.diff_cleanupSemanticLossless=function(t){function e(t,e){if(!t||!e)return 6;var r=t.charAt(t.length-1),i=e.charAt(0),s=r.match(n.nonAlphaNumericRegex_),h=i.match(n.nonAlphaNumericRegex_),a=s&&r.match(n.whitespaceRegex_),f=h&&i.match(n.whitespaceRegex_),l=a&&r.match(n.linebreakRegex_),o=f&&i.match(n.linebreakRegex_),g=l&&t.match(n.blanklineEndRegex_),c=o&&e.match(n.blanklineStartRegex_);return g||c?5:l||o?4:s&&!a&&f?3:a||f?2:s||h?1:0}for(var r=1;r=u&&(u=p,o=i,g=h,c=a)}t[r-1][1]!=o&&(o?t[r-1][1]=o:(t.splice(r-1,1),r--),t[r][1]=g,c?t[r+1][1]=c:(t.splice(r+1,1),r--))}r++}},n.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,n.whitespaceRegex_=/\s/,n.linebreakRegex_=/[\r\n]/,n.blanklineEndRegex_=/\n\r?\n$/,n.blanklineStartRegex_=/^\r?\n\r?\n/,n.prototype.diff_cleanupEfficiency=function(t){for(var e=!1,n=[],h=0,a=null,f=0,l=!1,o=!1,g=!1,c=!1;f0?n[h-1]:-1,g=c=!1),e=!0)),f++;e&&this.diff_cleanupMerge(t)},n.prototype.diff_cleanupMerge=function(t){t.push([s,""]);for(var e,n=0,h=0,a=0,f="",l="";n1?(0!==h&&0!==a&&(e=this.diff_commonPrefix(l,f),0!==e&&(n-h-a>0&&t[n-h-a-1][0]==s?t[n-h-a-1][1]+=l.substring(0,e):(t.splice(0,0,[s,l.substring(0,e)]),n++),l=l.substring(e),f=f.substring(e)),e=this.diff_commonSuffix(l,f),0!==e&&(t[n][1]=l.substring(l.length-e)+t[n][1],l=l.substring(0,l.length-e),f=f.substring(0,f.length-e))),0===h?t.splice(n-a,h+a,[i,l]):0===a?t.splice(n-h,h+a,[r,f]):t.splice(n-h-a,h+a,[r,f],[i,l]),n=n-h-a+(h?1:0)+(a?1:0)+1):0!==n&&t[n-1][0]==s?(t[n-1][1]+=t[n][1],t.splice(n,1)):n++,a=0,h=0,f="",l=""}""===t[t.length-1][1]&&t.pop();var o=!1;for(n=1;ne));n++)a=s,f=h;return t.length!=n&&t[n][0]===r?f:f+(e-a)},n.prototype.diff_prettyHtml=function(t){for(var e=[],n=/&/g,h=//g,f=/\n/g,l=0;l");switch(o){case i:e[l]=''+c+"";break;case r:e[l]=''+c+"";break;case s:e[l]=""+c+""}}return e.join("")},n.prototype.diff_text1=function(t){for(var e=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var i=this.match_alphabet_(e),s=this,h=this.Match_Threshold,a=t.indexOf(e,n);-1!=a&&(h=Math.min(r(0,a),h),a=t.lastIndexOf(e,n+e.length),-1!=a&&(h=Math.min(r(0,a),h)));var f=1<l;)r(u,n+o)<=h?l=o:c=o,o=Math.floor((c-l)/2+l);c=o;var p=Math.max(1,n-o+1),d=Math.min(n+o,t.length)+e.length,_=Array(d+2);_[d+1]=(1<=p;b--){var v=i[t.charAt(b-1)];if(0===u?_[b]=(_[b+1]<<1|1)&v:_[b]=(_[b+1]<<1|1)&v|((g[b+1]|g[b])<<1|1)|g[b+1],_[b]&f){var m=r(u,b-1);if(h>=m){if(h=m,a=b-1,!(a>n))break;p=Math.max(1,2*n-a)}}}if(r(u+1,n)>h)break;g=_}return a},n.prototype.match_alphabet_=function(t){for(var e={},n=0;n2&&(this.diff_cleanupSemantic(f),this.diff_cleanupEfficiency(f));else if(t&&"object"==typeof t&&"undefined"==typeof e&&"undefined"==typeof h)f=t,a=this.diff_text1(f);else if("string"==typeof t&&e&&"object"==typeof e&&"undefined"==typeof h)a=t,f=e;else{if("string"!=typeof t||"string"!=typeof e||!h||"object"!=typeof h)throw new Error("Unknown call format to patch_make.");a=t,f=h}if(0===f.length)return[];for(var l=[],o=new n.patch_obj,g=0,c=0,u=0,p=a,d=a,_=0;_=2*this.Patch_Margin&&g&&(this.patch_addContext_(o,p),l.push(o),o=new n.patch_obj,g=0,p=d,c=u)}b!==i&&(c+=v.length),b!==r&&(u+=v.length)}return g&&(this.patch_addContext_(o,p),l.push(o)),l},n.prototype.patch_deepCopy=function(t){for(var e=[],r=0;rthis.Match_MaxBits?(l=this.match_main(e,g.substring(0,this.Match_MaxBits),o),-1!=l&&(c=this.match_main(e,g.substring(g.length-this.Match_MaxBits),o+g.length-this.Match_MaxBits),(-1==c||l>=c)&&(l=-1))):l=this.match_main(e,g,o),-1==l)a[f]=!1,h-=t[f].length2-t[f].length1;else{a[f]=!0,h=l-o;var u;if(u=-1==c?e.substring(l,l+g.length):e.substring(l,c+this.Match_MaxBits),g==u)e=e.substring(0,l)+this.diff_text2(t[f].diffs)+e.substring(l+g.length);else{var p=this.diff_main(g,u,!1);if(g.length>this.Match_MaxBits&&this.diff_levenshtein(p)/g.length>this.Patch_DeleteThreshold)a[f]=!1;else{this.diff_cleanupSemanticLossless(p);for(var d,_=0,b=0;b=r;r++)n+=String.fromCharCode(r);for(var r=0;rh[0][1].length){var a=e-h[0][1].length;h[0][1]=n.substring(h[0][1].length)+h[0][1],i.start1-=a,i.start2-=a,i.length1+=a,i.length2+=a}if(i=t[t.length-1],h=i.diffs,0==h.length||h[h.length-1][0]!=s)h.push([s,n]),i.length1+=e,i.length2+=e;else if(e>h[h.length-1][1].length){var a=e-h[h.length-1][1].length;h[h.length-1][1]+=n.substring(0,a),i.length1+=a,i.length2+=a}return n},n.prototype.patch_splitMax=function(t){for(var e=this.Match_MaxBits,h=0;h2*e?(g.length1+=p.length,f+=p.length,c=!1,g.diffs.push([u,p]),a.diffs.shift()):(p=p.substring(0,e-g.length1-this.Patch_Margin),g.length1+=p.length,f+=p.length,u===s?(g.length2+=p.length,l+=p.length):c=!1,g.diffs.push([u,p]),p==a.diffs[0][1]?a.diffs.shift():a.diffs[0][1]=a.diffs[0][1].substring(p.length))}o=this.diff_text2(g.diffs),o=o.substring(o.length-this.Patch_Margin);var d=this.diff_text1(a.diffs).substring(0,this.Patch_Margin);""!==d&&(g.length1+=d.length,g.length2+=d.length,0!==g.diffs.length&&g.diffs[g.diffs.length-1][0]===s?g.diffs[g.diffs.length-1][1]+=d:g.diffs.push([s,d])),c||t.splice(++h,0,g)}}},n.prototype.patch_toText=function(t){for(var e=[],n=0;n 91 | fetch( 92 | // create delta request with cached version of file 93 | deltaFetch.createDeltaRequest(originalRequest, cachedResponse.headers.get('ETag')) 94 | ) 95 | // handle delta response to create normal response 96 | .then(processServerResponse.bind(null, cachedResponse)) 97 | ) 98 | // not in cache, no delta encoding available, do normal request 99 | .catch(() => fetch(originalRequest)); 100 | 101 | newResponse.then(response => { 102 | deltaFetch.cacheIfHasEtag(cache, originalRequest, response.clone()); 103 | }) 104 | 105 | return newResponse; 106 | } 107 | 108 | // handle delta encoded and normal responses, return promise containing Response object 109 | function processServerResponse(cachedResponse, serverResponse) { 110 | 111 | // server sent a patch (rather than the full file) 112 | if (serverResponse.status === 226 && serverResponse.headers.get('Delta-Base') === cachedResponse.headers.get('ETag')) { 113 | // use the patch on the cached file to create an updated response 114 | const newResponse = deltaFetch.patchResponse(serverResponse, cachedResponse); 115 | return Promise.resolve(newResponse); 116 | } 117 | // no change from cached version 118 | else if (serverResponse.status === 304) { 119 | const newResponse = deltaFetch.convert304to200(cachedResponse, serverResonse.headers) 120 | return Promise.resolve(newResponse); 121 | } 122 | // no delta encoding 123 | else { 124 | return Promise.resolve(serverResponse); 125 | } 126 | } 127 | 128 | module.exports = fetchProxy; 129 | 130 | 131 | 132 | /***/ }), 133 | /* 2 */ 134 | /***/ (function(module, exports) { 135 | 136 | 'use strict'; 137 | 138 | 139 | // returns whether the origins or the two urls are the same 140 | function isSameOrigin(url1, url2) { 141 | let parsedRequestUrl = getLocation(url1); 142 | let parsedCurrentUrl = getLocation(url2); 143 | 144 | return parsedRequestUrl.host === parsedCurrentUrl.host 145 | && parsedRequestUrl.protocol === parsedCurrentUrl.protocol; 146 | } 147 | 148 | function getLocation(href) { 149 | var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/); 150 | return match && { 151 | protocol: match[1], 152 | host: match[2], 153 | hostname: match[3], 154 | port: match[4], 155 | pathname: match[5], 156 | search: match[6], 157 | hash: match[7] 158 | } 159 | } 160 | 161 | module.exports = { 162 | isSameOrigin, 163 | getLocation 164 | } 165 | 166 | 167 | /***/ }), 168 | /* 3 */ 169 | /***/ (function(module, exports, __webpack_require__) { 170 | 171 | 'use strict'; 172 | 173 | const fetchUtil = __webpack_require__(4); 174 | const vcdiff = __webpack_require__(5); 175 | 176 | // cache the request/response if response contains the Delta-Version header 177 | function cacheIfHasEtag(cache, request, response) { 178 | if (response.headers.has('ETag')) { 179 | return cache.delete(request).then(() => { 180 | return cache.put(request, response.clone()); 181 | }); 182 | } 183 | else { 184 | return Promise.resolve(); 185 | } 186 | } 187 | 188 | // creates copy of request that contains the Etag of the cached response 189 | // as well as other headers 190 | function createDeltaRequest(originalRequest, cachedEtag) { 191 | const headers = fetchUtil.cloneHeaders(originalRequest.headers); 192 | 193 | // set VCDIFF encoding headers 194 | headers.set('A-IM', 'vcdiff'); 195 | headers.set('If-None-Match', cachedEtag); 196 | 197 | // return new request with delta headers 198 | return new Request(originalRequest.url, { 199 | method: originalRequest.method, 200 | headers, 201 | // can't create request with mode 'navigate', so we put 'same-origin' 202 | // since we know it's the same origin 203 | mode: originalRequest.mode === 'navigate' ? 204 | 'same-origin' : originalRequest.mode, 205 | credentials: originalRequest.credentials, 206 | redirect: 'manual' 207 | }); 208 | } 209 | 210 | // create 200 response from 304 response 211 | function convert304To200(response, newHeaders) { 212 | response.blob().then(blob => { 213 | const headers = fetchUtil.cloneHeaders(newHeaders); 214 | headers.set('Content-Type', cachedResponse.headers.get('Content-Type')); 215 | headers.delete('Content-Length'); 216 | 217 | header.set('X-Delta-Length', '0'); 218 | 219 | return new Response(blob, { 220 | status: 200, 221 | statusText: 'OK', 222 | headers, 223 | url: serverResponse.url 224 | }); 225 | }) 226 | } 227 | 228 | // takes a delta response and applies its patch to the other response 229 | // returns a promise resolving to the new response 230 | function patchResponse(patchResponse, responseToChange) { 231 | return Promise.all([patchResponse.arrayBuffer(), responseToChange.arrayBuffer()]).then(([deltaArrayBuffer, sourceArrayBuffer]) => { 232 | const delta = new Uint8Array(deltaArrayBuffer); 233 | const source = new Uint8Array(sourceArrayBuffer); 234 | 235 | const updated = vcdiff.decodeSync(delta, source); 236 | const headers = fetchUtil.cloneHeaders(patchResponse.headers); 237 | 238 | if (responseToChange.headers.has('Content-Type')) { 239 | headers.set('Content-Type', responseToChange.headers.get('Content-Type')); 240 | } 241 | 242 | // discard delta headers 243 | headers.delete('Content-Length'); 244 | headers.delete('Delta-Base'); 245 | headers.delete('im'); 246 | 247 | headers.set('X-Delta-Length', deltaArrayBuffer.byteLength.toString()); 248 | 249 | return new Response(updated, { 250 | status: 200, 251 | statusText: 'OK', 252 | headers: headers, 253 | url: patchResponse.url 254 | }); 255 | }); 256 | } 257 | 258 | module.exports = { 259 | cacheIfHasEtag, 260 | createDeltaRequest, 261 | convert304To200, 262 | patchResponse 263 | } 264 | 265 | 266 | /***/ }), 267 | /* 4 */ 268 | /***/ (function(module, exports) { 269 | 270 | 'use strict'; 271 | 272 | // copies all headers into a new Headers 273 | function cloneHeaders(headers) { 274 | let headersClone = new Headers(); 275 | for (let [name, value] of headers.entries()) { 276 | headersClone.append(name, value); 277 | } 278 | return headersClone; 279 | } 280 | 281 | function printHeaders(headers) { 282 | for (let [name, value] of headers.entries()) { 283 | console.log(name + ': ' + value); 284 | } 285 | } 286 | 287 | 288 | module.exports = { 289 | cloneHeaders, 290 | printHeaders 291 | }; 292 | 293 | 294 | /***/ }), 295 | /* 5 */ 296 | /***/ (function(module, exports, __webpack_require__) { 297 | 298 | 'use strict'; 299 | const errors = __webpack_require__(6); 300 | const VCDiff = __webpack_require__(7); 301 | 302 | /** 303 | * 304 | * @param delta {Uint8Array} 305 | * @param source {Uint8Array} 306 | */ 307 | function decodeSync(delta, source) { 308 | let vcdiff = new VCDiff(delta, source); 309 | return vcdiff.decode(); 310 | } 311 | 312 | function decode(delta, buffer) { 313 | 314 | } 315 | 316 | module.exports = { 317 | decodeSync, 318 | decode 319 | }; 320 | 321 | 322 | 323 | 324 | /***/ }), 325 | /* 6 */ 326 | /***/ (function(module, exports) { 327 | 328 | 'use strict'; 329 | /** 330 | * Takes in array of names of errors and returns an object mapping those names to error functions that take in one parameter that is used as the message for the error 331 | * @param names {[]} 332 | * @returns {{name1: function(message),...}} 333 | * @constructor 334 | */ 335 | function CustomErrors(names) { 336 | let errors = {}; 337 | names.forEach(name => { 338 | let CustomError = function CustomError(message) { 339 | var temp = Error.apply(this, arguments); 340 | temp.name = this.name = name; 341 | this.stack = temp.stack; 342 | this.message = temp.message; 343 | this.name = name; 344 | this.message = message; 345 | }; 346 | CustomError.prototype = Object.create(Error.prototype, { 347 | constructor: { 348 | value: CustomError, 349 | writable: true, 350 | configurable: true 351 | } 352 | }); 353 | errors[name] = CustomError; 354 | }); 355 | return errors; 356 | } 357 | 358 | module.exports = CustomErrors(['NotImplemented', 'InvalidDelta']); 359 | 360 | /***/ }), 361 | /* 7 */ 362 | /***/ (function(module, exports, __webpack_require__) { 363 | 364 | 'use strict'; 365 | 366 | const errors = __webpack_require__(6); 367 | const TypedArray = __webpack_require__(8); 368 | const deserializeInteger = __webpack_require__(9); 369 | const deserializeDelta = __webpack_require__(10); 370 | const NearCache = __webpack_require__(13); 371 | const SameCache = __webpack_require__(14); 372 | 373 | /** 374 | * 375 | * @param delta {Uint8Array} 376 | * @param source {Uint8Array} 377 | * @constructor 378 | */ 379 | function VCDiff(delta, source) { 380 | this.delta = delta; 381 | this.position = 0; 382 | this.source = source; 383 | this.targetWindows = new TypedArray.TypedArrayList(); 384 | } 385 | 386 | VCDiff.prototype.decode = function() { 387 | this._consumeHeader(); 388 | while (this._consumeWindow()) {} 389 | 390 | let targetLength = this.targetWindows.typedArrays.reduce((sum, uint8Array) => uint8Array.length + sum, 0); 391 | let target = new Uint8Array(targetLength); 392 | let position = 0; 393 | 394 | // concat all uint8arrays 395 | for (let arrayNum = 0; arrayNum < this.targetWindows.typedArrays.length; arrayNum++) { 396 | let array = this.targetWindows.typedArrays[arrayNum]; 397 | let length = array.length; 398 | target.set(array, position); 399 | position += length; 400 | } 401 | 402 | return target; 403 | }; 404 | 405 | VCDiff.prototype._consumeHeader = function() { 406 | 407 | let hasVCDiffHeader = this.delta[0] === 214 && // V 408 | this.delta[1] === 195 && // C 409 | this.delta[2] === 196 && // D 410 | this.delta[3] === 0; // \0 411 | 412 | if (!hasVCDiffHeader) { 413 | throw new errors.InvalidDelta('first 3 bytes not VCD'); 414 | } 415 | 416 | let hdrIndicator = this.delta[4]; 417 | // extract least significant bit 418 | let vcdDecompress = 1 & hdrIndicator; 419 | // extract second least significant bit 420 | let vcdCodetable = 1 & (hdrIndicator >> 1); 421 | 422 | // verify not using Hdr_Indicator 423 | if (vcdDecompress || vcdCodetable) { 424 | throw new errors.NotImplemented( 425 | 'non-zero Hdr_Indicator (VCD_DECOMPRESS or VCD_CODETABLE bit is set)' 426 | ); 427 | } 428 | 429 | this.position += 5; 430 | }; 431 | 432 | VCDiff.prototype._consumeWindow = function() { 433 | let winIndicator = this.delta[this.position++]; 434 | 435 | // extract least significant bit 436 | let vcdSource = 1 & winIndicator; 437 | // extract second least significant bit 438 | let vcdTarget = 1 & (winIndicator >> 1); 439 | 440 | if (vcdSource && vcdTarget) { 441 | throw new errors.InvalidDelta( 442 | 'VCD_SOURCE and VCD_TARGET cannot both be set in Win_Indicator' 443 | ) 444 | } 445 | else if (vcdSource) { 446 | let sourceSegmentLength, sourceSegmentPosition, deltaLength; 447 | ({ value: sourceSegmentLength, position: this.position } = deserializeInteger(this.delta, this.position)); 448 | ({ value: sourceSegmentPosition, position: this.position } = deserializeInteger(this.delta, this.position)); 449 | ({ value: deltaLength, position: this.position } = deserializeInteger(this.delta, this.position)); 450 | 451 | let sourceSegment = this.source.slice(sourceSegmentPosition, sourceSegmentPosition + sourceSegmentLength); 452 | this._buildTargetWindow(this.position, sourceSegment); 453 | this.position += deltaLength; 454 | } 455 | else if (vcdTarget) { 456 | throw new errors.NotImplemented( 457 | 'non-zero VCD_TARGET in Win_Indicator' 458 | ) 459 | } 460 | return this.position < this.delta.length; 461 | }; 462 | 463 | // first integer is target window length 464 | VCDiff.prototype._buildTargetWindow = function(position, sourceSegment) { 465 | let window = deserializeDelta(this.delta, position); 466 | 467 | let T = new Uint8Array(window.targetWindowLength); 468 | 469 | let U = new TypedArray.TypedArrayList(); 470 | U.add(sourceSegment); 471 | U.add(T); 472 | 473 | let targetPosition = this.source.length; 474 | let dataPosition = 0; 475 | 476 | let delta = new Delta(U, this.source.length, window.data, window.addresses); 477 | window.instructions.forEach(instruction => { 478 | instruction.execute(delta); 479 | }); 480 | 481 | let target = U.typedArrays[1]; 482 | this.targetWindows.add(target); 483 | }; 484 | 485 | function Delta(U, UTargetPosition, data, addresses) { 486 | this.U = U; 487 | this.UTargetPosition = UTargetPosition; 488 | this.data = data; 489 | this.dataPosition = 0; 490 | this.addresses = addresses; 491 | this.addressesPosition = 0; 492 | this.nearCache = new NearCache(4); 493 | this.sameCache = new SameCache(3); 494 | } 495 | 496 | Delta.prototype.getNextAddressInteger = function() { 497 | let value; 498 | // get next address and increase the address position for the next address 499 | ({value, position: this.addressesPosition } = deserializeInteger(this.addresses, this.addressesPosition)); 500 | return value; 501 | }; 502 | 503 | Delta.prototype.getNextAddressByte = function() { 504 | // get next address and increase the address position for the next address 505 | let value = this.addresses[this.addressesPosition++]; 506 | return value; 507 | }; 508 | 509 | module.exports = VCDiff; 510 | 511 | /***/ }), 512 | /* 8 */ 513 | /***/ (function(module, exports) { 514 | 515 | 'use strict'; 516 | 517 | function uint8ArrayToString(uintArray) { 518 | let encodedString = String.fromCharCode.apply(null, uintArray); 519 | let decodedString = decodeURIComponent(escape(encodedString)); 520 | return decodedString; 521 | } 522 | 523 | function stringToUint8Array(str) { 524 | var buf = new Uint8Array(str.length); 525 | for (var i=0, strLen=str.length; i < strLen; i++) { 526 | buf[i] = str.charCodeAt(i); 527 | } 528 | return buf; 529 | } 530 | 531 | function equal(typedArray1, typedArray2) { 532 | if (typedArray1.length !== typedArray2.length) { 533 | return false; 534 | } 535 | for (let i = 0; i < typedArray1.length; i++) { 536 | if (typedArray1[i] !== typedArray2[i]) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | 543 | function TypedArrayList() { 544 | this.typedArrays = []; 545 | this.startIndexes = []; 546 | this.length = 0; 547 | } 548 | 549 | TypedArrayList.prototype.add = function(typedArray) { 550 | let typedArrayTypes = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, 551 | Int32Array, Uint32Array, Float32Array, Float64Array]; 552 | 553 | let matchingTypedArrayTypes = typedArrayTypes.filter(typedArrayType => typedArray instanceof typedArrayType); 554 | if (matchingTypedArrayTypes.length < 1) { 555 | throw Error('Given ' + typeof typedArray + ' when expected a TypedArray'); 556 | } 557 | 558 | let startIndex; 559 | if (this.typedArrays.length === 0) { 560 | startIndex = 0; 561 | } 562 | else { 563 | let lastIndex = this.startIndexes.length - 1; 564 | let lastStartIndex = this.startIndexes[lastIndex]; 565 | let lastLength = this.typedArrays[lastIndex].length; 566 | startIndex = lastStartIndex + lastLength; 567 | } 568 | 569 | this.startIndexes.push(startIndex); 570 | this.typedArrays.push(typedArray); 571 | this.length += startIndex + typedArray.length; 572 | }; 573 | 574 | TypedArrayList.prototype.get = function(index) { 575 | let listIndex = getIndex(this.startIndexes, index); 576 | let typedArray = index - this.startIndexes[listIndex]; 577 | return this.typedArrays[listIndex][typedArray]; 578 | }; 579 | 580 | TypedArrayList.prototype.set = function(index, value) { 581 | if (typeof index !== 'number' || isNaN(index)) { 582 | throw new Error('Given non-number index: ' + index); 583 | } 584 | //console.log(index); 585 | 586 | let listIndex = getIndex(this.startIndexes, index); 587 | let typedArrayIndex = index - this.startIndexes[listIndex]; 588 | this.typedArrays[listIndex][typedArrayIndex] = value; 589 | }; 590 | 591 | function getIndex(arr, element) { 592 | let low = 0; 593 | let high = arr.length - 1; 594 | 595 | while (low < high) { 596 | let mid = Math.floor((low + high) / 2); 597 | 598 | if (arr[mid] === element) { 599 | return mid; 600 | } 601 | else if (arr[mid] < element) { 602 | low = mid + 1; 603 | } 604 | else { 605 | high = mid - 1; 606 | } 607 | } 608 | if (arr[high] > element) { 609 | return high - 1; 610 | } 611 | else { 612 | return high; 613 | } 614 | } 615 | 616 | module.exports = { 617 | uint8ArrayToString, 618 | stringToUint8Array, 619 | equal, 620 | TypedArrayList 621 | }; 622 | 623 | 624 | 625 | /***/ }), 626 | /* 9 */ 627 | /***/ (function(module, exports) { 628 | 629 | 'use strict'; 630 | 631 | /** 632 | * Converts RFC 3284 definition of integer in buffer to decimal 633 | * Also returns the index of the byte after the integer 634 | * @param buffer {Uint8Array} 635 | * @param position {Number} 636 | * @returns {{position: {Number}, value: {Number}}} 637 | */ 638 | function integer(buffer, position) { 639 | let integer = buffer[position++]; 640 | let digitArray = []; 641 | 642 | let numBytes = 0; 643 | 644 | // if the most significant bit is set, the the integer continues 645 | while (integer >= 128) { 646 | digitArray.unshift(integer - 128); 647 | integer = buffer[position++]; 648 | if (numBytes > 1000) { 649 | throw new Error('Integer is probably too long') 650 | } 651 | numBytes++; 652 | } 653 | 654 | digitArray.unshift(integer); 655 | 656 | // convert from base 128 to decimal 657 | return { 658 | position: position, 659 | value: digitArray.reduce((sum, digit, index) => sum + digit * Math.pow(128, index), 0) 660 | }; 661 | } 662 | 663 | module.exports = integer; 664 | 665 | /***/ }), 666 | /* 10 */ 667 | /***/ (function(module, exports, __webpack_require__) { 668 | 669 | 'use strict'; 670 | 671 | const errors = __webpack_require__(6); 672 | const deserializeInteger = __webpack_require__(9); 673 | const tokenizeInstructions = __webpack_require__(11); 674 | 675 | function delta(delta, position) { 676 | 677 | let targetWindowLength, dataLength, instructionsLength, addressesLength; 678 | 679 | // parentheses are needed for assignment destructuring 680 | ({ value: targetWindowLength, position } = deserializeInteger(delta, position)); 681 | 682 | // Delta_Indicator byte 683 | if (delta[position] !== 0) { 684 | throw new errors.NotImplemented( 685 | 'VCD_DECOMPRESS is not supported, Delta_Indicator must be zero at byte ' + position + ' and not ' + delta[position] 686 | ); 687 | } 688 | position++; 689 | 690 | ({ value: dataLength, position } = deserializeInteger(delta, position)); 691 | ({ value: instructionsLength, position } = deserializeInteger(delta, position)); 692 | ({ value: addressesLength, position } = deserializeInteger(delta, position)); 693 | 694 | let dataNextPosition = position + dataLength; 695 | let data = delta.slice(position, dataNextPosition); 696 | 697 | let instructionsNextPosition = dataNextPosition + instructionsLength; 698 | let instructions = delta.slice(dataNextPosition, instructionsNextPosition); 699 | let deserializedInstructions = tokenizeInstructions(instructions); 700 | 701 | let addressesNextPosition = instructionsNextPosition + addressesLength; 702 | let addresses = delta.slice(instructionsNextPosition, addressesNextPosition); 703 | 704 | position = addressesNextPosition; 705 | 706 | let window = { 707 | targetWindowLength, 708 | position, 709 | data, 710 | instructions: deserializedInstructions, 711 | addresses 712 | }; 713 | 714 | return window; 715 | } 716 | 717 | module.exports = delta; 718 | 719 | 720 | 721 | /***/ }), 722 | /* 11 */ 723 | /***/ (function(module, exports, __webpack_require__) { 724 | 725 | 'use strict'; 726 | 727 | const instructions = __webpack_require__(12); 728 | const deserializeInteger = __webpack_require__(9); 729 | 730 | function tokenizeInstructions(instructionsBuffer) { 731 | let deserializedInstructions = []; 732 | 733 | let instructionsPosition = 0; 734 | 735 | while (instructionsPosition < instructionsBuffer.length) { 736 | let index = instructionsBuffer[instructionsPosition++]; 737 | 738 | let addSize, copySize, size; 739 | 740 | if (index === 0) { 741 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 742 | deserializedInstructions.push(new instructions.RUN(size)); 743 | } 744 | else if (index === 1) { 745 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 746 | deserializedInstructions.push(new instructions.ADD(size)); 747 | } 748 | else if (index < 19) { 749 | deserializedInstructions.push(new instructions.ADD(index - 1)); 750 | } 751 | else if (index === 19) { 752 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 753 | deserializedInstructions.push(new instructions.COPY(size, 0)); 754 | } 755 | else if (index < 35) { 756 | deserializedInstructions.push(new instructions.COPY(index - 16, 0)); 757 | } 758 | else if (index === 35) { 759 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 760 | deserializedInstructions.push(new instructions.COPY(size, 1)); 761 | } 762 | else if (index < 51) { 763 | deserializedInstructions.push(new instructions.COPY(index - 32, 1)); 764 | } 765 | else if (index === 51) { 766 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 767 | deserializedInstructions.push(new instructions.COPY(size, 2)); 768 | } 769 | else if (index < 67) { 770 | deserializedInstructions.push(new instructions.COPY(index - 48, 2)); 771 | } 772 | else if (index === 67) { 773 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 774 | deserializedInstructions.push(new instructions.COPY(size, 3)); 775 | } 776 | else if (index < 83) { 777 | deserializedInstructions.push(new instructions.COPY(index - 64, 3)); 778 | } 779 | else if (index === 83) { 780 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 781 | deserializedInstructions.push(new instructions.COPY(size, 4)); 782 | } 783 | else if (index < 99) { 784 | deserializedInstructions.push(new instructions.COPY(index - 80, 4)); 785 | } 786 | else if (index === 99) { 787 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 788 | deserializedInstructions.push(new instructions.COPY(size, 5)); 789 | } 790 | else if (index < 115) { 791 | deserializedInstructions.push(new instructions.COPY(index - 96, 5)); 792 | } 793 | else if (index === 115) { 794 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 795 | deserializedInstructions.push(new instructions.COPY(size, 6)); 796 | } 797 | else if (index < 131) { 798 | deserializedInstructions.push(new instructions.COPY(index - 112, 6)); 799 | } 800 | else if (index === 131) { 801 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 802 | deserializedInstructions.push(new instructions.COPY(size, 7)); 803 | } 804 | else if (index < 147) { 805 | deserializedInstructions.push(new instructions.COPY(index - 127, 7)); 806 | } 807 | else if (index === 147) { 808 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 809 | deserializedInstructions.push(new instructions.COPY(size, 8)); 810 | } 811 | else if (index < 163) { 812 | deserializedInstructions.push(new instructions.COPY(index - 144, 8)); 813 | } 814 | else if (index < 175) { 815 | ({addSize, copySize} = ADD_COPY(index, 163)); 816 | 817 | deserializedInstructions.push(new instructions.ADD(addSize)); 818 | deserializedInstructions.push(new instructions.COPY(copySize, 0)); 819 | } 820 | else if (index < 187) { 821 | ({addSize, copySize} = ADD_COPY(index, 175)); 822 | 823 | deserializedInstructions.push(new instructions.ADD(addSize)); 824 | deserializedInstructions.push(new instructions.COPY(copySize, 1)); 825 | } 826 | else if (index < 199) { 827 | ({addSize, copySize} = ADD_COPY(index, 187)); 828 | 829 | deserializedInstructions.push(new instructions.ADD(addSize)); 830 | deserializedInstructions.push(new instructions.COPY(copySize, 2)); 831 | } 832 | else if (index < 211) { 833 | ({addSize, copySize} = ADD_COPY(index, 199)); 834 | 835 | deserializedInstructions.push(new instructions.ADD(addSize)); 836 | deserializedInstructions.push(new instructions.COPY(copySize, 3)); 837 | } 838 | else if (index < 223) { 839 | ({addSize, copySize} = ADD_COPY(index, 211)); 840 | 841 | deserializedInstructions.push(new instructions.ADD(addSize)); 842 | deserializedInstructions.push(new instructions.COPY(copySize, 4)); 843 | } 844 | else if (index < 235) { 845 | ({addSize, copySize} = ADD_COPY(index, 223)); 846 | 847 | deserializedInstructions.push(new instructions.ADD(addSize)); 848 | deserializedInstructions.push(new instructions.COPY(copySize, 5)); 849 | } 850 | else if (index < 239) { 851 | deserializedInstructions.push(new instructions.ADD(index - 235 + 1)); 852 | deserializedInstructions.push(new instructions.COPY(4, 6)); 853 | } 854 | else if (index < 243) { 855 | deserializedInstructions.push(new instructions.ADD(index - 239 + 1)); 856 | deserializedInstructions.push(new instructions.COPY(4, 7)); 857 | } 858 | else if (index < 247) { 859 | deserializedInstructions.push(new instructions.ADD(index - 243 + 1)); 860 | deserializedInstructions.push(new instructions.COPY(4, 8)); 861 | } 862 | else if (index < 256) { 863 | deserializedInstructions.push(new instructions.COPY(4, index - 247)); 864 | deserializedInstructions.push(new instructions.ADD(1)); 865 | } 866 | else { 867 | throw new Error('Should not get here'); 868 | } 869 | } 870 | 871 | return deserializedInstructions; 872 | } 873 | 874 | function ADD_COPY(index, baseIndex) { 875 | let zeroBased = index - baseIndex; 876 | 877 | // 0,1,2 -> 0 3,4,5 -> 1 etc. 878 | let addSizeIndex = Math.floor(zeroBased / 3); 879 | // offset so size starts at 1 880 | let addSize = addSizeIndex + 1; 881 | 882 | // rotate through 0, 1, and 2 883 | let copySizeIndex = zeroBased % 3; 884 | // offset so size starts at 4 885 | let copySize = copySizeIndex + 4; 886 | 887 | return [addSize, copySize]; 888 | } 889 | 890 | module.exports = tokenizeInstructions; 891 | 892 | /***/ }), 893 | /* 12 */ 894 | /***/ (function(module, exports, __webpack_require__) { 895 | 896 | 'use strict'; 897 | 898 | const deserializeInteger = __webpack_require__(9); 899 | const TypedArray = __webpack_require__(8); 900 | 901 | function ADD(size) { 902 | this.size = size; 903 | } 904 | function COPY(size, mode) { 905 | this.size = size; 906 | this.mode = mode; 907 | } 908 | function RUN(size) { 909 | this.size = size; 910 | } 911 | 912 | ADD.prototype.name = 'ADD'; 913 | COPY.prototype.name = 'COPY'; 914 | RUN.prototype.name = 'RUN'; 915 | 916 | ADD.prototype.execute = function(delta) { 917 | for (let i = 0; i < this.size; i++) { 918 | delta.U.set(delta.UTargetPosition + i, delta.data[delta.dataPosition + i]); 919 | } 920 | delta.dataPosition += this.size; 921 | delta.UTargetPosition += this.size; 922 | }; 923 | 924 | COPY.prototype.execute = function(delta) { 925 | let address, m, next, method; 926 | 927 | if (this.mode === 0) { 928 | address = delta.getNextAddressInteger(); 929 | } 930 | else if (this.mode === 1) { 931 | next = delta.getNextAddressInteger(); 932 | address = delta.UTargetPosition - next; 933 | } 934 | else if ((m = this.mode - 2) >= 0 && (m < delta.nearCache.size)) { 935 | next = delta.getNextAddressInteger(); 936 | address = delta.nearCache.get(m, next); 937 | method = 'near'; 938 | } 939 | // same cache 940 | else { 941 | m = this.mode - (2 + delta.nearCache.size); 942 | next = delta.getNextAddressByte(); 943 | address = delta.sameCache.get(m, next); 944 | method = 'same'; 945 | } 946 | 947 | delta.nearCache.update(address); 948 | delta.sameCache.update(address); 949 | 950 | for (let i = 0; i < this.size; i++) { 951 | delta.U.set(delta.UTargetPosition + i, delta.U.get(address + i)); 952 | } 953 | 954 | delta.UTargetPosition += this.size; 955 | }; 956 | 957 | RUN.prototype.execute = function(delta) { 958 | for (let i = 0; i < this.size; i++) { 959 | // repeat single byte 960 | delta.U[delta.UTargetPosition + i] = delta.data[delta.dataPosition]; 961 | } 962 | // increment to next byte 963 | delta.dataPosition++; 964 | delta.UTargetPosition += this.size; 965 | }; 966 | 967 | let instructions = { 968 | ADD, 969 | COPY, 970 | RUN 971 | }; 972 | 973 | module.exports = instructions; 974 | 975 | /***/ }), 976 | /* 13 */ 977 | /***/ (function(module, exports) { 978 | 979 | 'use strict'; 980 | 981 | function NearCache(size) { 982 | this.size = size; 983 | this.near = new Array(this.size).fill(0); 984 | this.nextSlot = 0; 985 | } 986 | 987 | NearCache.prototype.update = function(address) { 988 | if (this.near.length > 0) { 989 | this.near[this.nextSlot] = address; 990 | this.nextSlot = (this.nextSlot + 1) % this.near.length; 991 | } 992 | }; 993 | 994 | NearCache.prototype.get = function(m, offset) { 995 | let address = this.near[m] + offset; 996 | return address; 997 | }; 998 | 999 | module.exports = NearCache; 1000 | 1001 | /***/ }), 1002 | /* 14 */ 1003 | /***/ (function(module, exports) { 1004 | 1005 | 'use strict'; 1006 | 1007 | function SameCache(size) { 1008 | this.size = size; 1009 | this.same = new Array(this.size * 256).fill(0); 1010 | } 1011 | 1012 | SameCache.prototype.update = function(address) { 1013 | if (this.same.length > 0) { 1014 | this.same[address % (this.size * 256)] = address; 1015 | } 1016 | }; 1017 | 1018 | SameCache.prototype.get = function(m, offset) { 1019 | let address = this.same[m * 256 + offset]; 1020 | return address; 1021 | }; 1022 | 1023 | module.exports = SameCache; 1024 | 1025 | /***/ }) 1026 | /******/ ]); -------------------------------------------------------------------------------- /demo/public/delta_cache_sw.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ (function(module, exports, __webpack_require__) { 46 | 47 | 'use strict'; 48 | 49 | const fetchProxy = __webpack_require__(1); 50 | 51 | const CACHE_NAME = 'delta-cache-v1'; 52 | 53 | self.addEventListener('install', function(event) { 54 | // forces the waiting service worker to become the active service worker 55 | self.skipWaiting(); 56 | }); 57 | 58 | self.addEventListener('activate', function(event) { 59 | // extend lifetime of event until cache is deleted 60 | event.waitUntil( 61 | // deletes cache to prevent incompatibility between requests 62 | caches.delete(CACHE_NAME) 63 | ); 64 | }); 65 | 66 | self.addEventListener('fetch', function(event) { 67 | event.respondWith( 68 | caches.open(CACHE_NAME).then( 69 | // a proxy between the client and the server 70 | // returns a promise that contains a response 71 | fetchProxy.bind(null, event.request, self.registration.scope) 72 | ) 73 | ); 74 | }); 75 | 76 | 77 | 78 | /***/ }), 79 | /* 1 */ 80 | /***/ (function(module, exports, __webpack_require__) { 81 | 82 | 'use strict'; 83 | 84 | const urlUtil = __webpack_require__(2); 85 | const deltaFetch = __webpack_require__(3); 86 | const fetchUtil = __webpack_require__(4); 87 | 88 | function fetchProxy(originalRequest, scope, cache) { 89 | // get cached response which matches the request 90 | const newResponse = cache.match(originalRequest).then(cachedResponse => 91 | fetch( 92 | // create delta request with cached version of file 93 | deltaFetch.createDeltaRequest(originalRequest, cachedResponse.headers.get('ETag')) 94 | ) 95 | // handle delta response to create normal response 96 | .then(processServerResponse.bind(null, cachedResponse)) 97 | ) 98 | // not in cache, no delta encoding available, do normal request 99 | .catch(() => fetch(originalRequest)); 100 | 101 | newResponse.then(response => { 102 | deltaFetch.cacheIfHasEtag(cache, originalRequest, response.clone()); 103 | }) 104 | 105 | return newResponse; 106 | } 107 | 108 | // handle delta encoded and normal responses, return promise containing Response object 109 | function processServerResponse(cachedResponse, serverResponse) { 110 | 111 | // server sent a patch (rather than the full file) 112 | if (serverResponse.status === 226 && serverResponse.headers.get('Delta-Base') === cachedResponse.headers.get('ETag')) { 113 | // use the patch on the cached file to create an updated response 114 | const newResponse = deltaFetch.patchResponse(serverResponse, cachedResponse); 115 | return Promise.resolve(newResponse); 116 | } 117 | // no change from cached version 118 | else if (serverResponse.status === 304) { 119 | const newResponse = deltaFetch.convert304to200(cachedResponse, serverResonse.headers) 120 | return Promise.resolve(newResponse); 121 | } 122 | // no delta encoding 123 | else { 124 | return Promise.resolve(serverResponse); 125 | } 126 | } 127 | 128 | module.exports = fetchProxy; 129 | 130 | 131 | 132 | /***/ }), 133 | /* 2 */ 134 | /***/ (function(module, exports) { 135 | 136 | 'use strict'; 137 | 138 | 139 | // returns whether the origins or the two urls are the same 140 | function isSameOrigin(url1, url2) { 141 | let parsedRequestUrl = getLocation(url1); 142 | let parsedCurrentUrl = getLocation(url2); 143 | 144 | return parsedRequestUrl.host === parsedCurrentUrl.host 145 | && parsedRequestUrl.protocol === parsedCurrentUrl.protocol; 146 | } 147 | 148 | function getLocation(href) { 149 | var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/); 150 | return match && { 151 | protocol: match[1], 152 | host: match[2], 153 | hostname: match[3], 154 | port: match[4], 155 | pathname: match[5], 156 | search: match[6], 157 | hash: match[7] 158 | } 159 | } 160 | 161 | module.exports = { 162 | isSameOrigin, 163 | getLocation 164 | } 165 | 166 | 167 | /***/ }), 168 | /* 3 */ 169 | /***/ (function(module, exports, __webpack_require__) { 170 | 171 | 'use strict'; 172 | 173 | const fetchUtil = __webpack_require__(4); 174 | const vcdiff = __webpack_require__(5); 175 | 176 | // cache the request/response if response contains the Delta-Version header 177 | function cacheIfHasEtag(cache, request, response) { 178 | if (response.headers.has('ETag')) { 179 | return cache.delete(request).then(() => { 180 | return cache.put(request, response.clone()); 181 | }); 182 | } 183 | else { 184 | return Promise.resolve(); 185 | } 186 | } 187 | 188 | // creates copy of request that contains the Etag of the cached response 189 | // as well as other headers 190 | function createDeltaRequest(originalRequest, cachedEtag) { 191 | const headers = fetchUtil.cloneHeaders(originalRequest.headers); 192 | 193 | // set VCDIFF encoding headers 194 | headers.set('A-IM', 'vcdiff'); 195 | headers.set('If-None-Match', cachedEtag); 196 | 197 | // return new request with delta headers 198 | return new Request(originalRequest.url, { 199 | method: originalRequest.method, 200 | headers, 201 | // can't create request with mode 'navigate', so we put 'same-origin' 202 | // since we know it's the same origin 203 | mode: originalRequest.mode === 'navigate' ? 204 | 'same-origin' : originalRequest.mode, 205 | credentials: originalRequest.credentials, 206 | redirect: 'manual' 207 | }); 208 | } 209 | 210 | // create 200 response from 304 response 211 | function convert304To200(response, newHeaders) { 212 | response.blob().then(blob => { 213 | const headers = fetchUtil.cloneHeaders(newHeaders); 214 | headers.set('Content-Type', cachedResponse.headers.get('Content-Type')); 215 | headers.delete('Content-Length'); 216 | 217 | header.set('X-Delta-Length', '0'); 218 | 219 | return new Response(blob, { 220 | status: 200, 221 | statusText: 'OK', 222 | headers, 223 | url: serverResponse.url 224 | }); 225 | }) 226 | } 227 | 228 | // takes a delta response and applies its patch to the other response 229 | // returns a promise resolving to the new response 230 | function patchResponse(patchResponse, responseToChange) { 231 | return Promise.all([patchResponse.arrayBuffer(), responseToChange.arrayBuffer()]).then(([deltaArrayBuffer, sourceArrayBuffer]) => { 232 | const delta = new Uint8Array(deltaArrayBuffer); 233 | const source = new Uint8Array(sourceArrayBuffer); 234 | 235 | const updated = vcdiff.decodeSync(delta, source); 236 | const headers = fetchUtil.cloneHeaders(patchResponse.headers); 237 | 238 | if (responseToChange.headers.has('Content-Type')) { 239 | headers.set('Content-Type', responseToChange.headers.get('Content-Type')); 240 | } 241 | 242 | // discard delta headers 243 | headers.delete('Content-Length'); 244 | headers.delete('Delta-Base'); 245 | headers.delete('im'); 246 | 247 | headers.set('X-Delta-Length', deltaArrayBuffer.byteLength.toString()); 248 | 249 | return new Response(updated, { 250 | status: 200, 251 | statusText: 'OK', 252 | headers: headers, 253 | url: patchResponse.url 254 | }); 255 | }); 256 | } 257 | 258 | module.exports = { 259 | cacheIfHasEtag, 260 | createDeltaRequest, 261 | convert304To200, 262 | patchResponse 263 | } 264 | 265 | 266 | /***/ }), 267 | /* 4 */ 268 | /***/ (function(module, exports) { 269 | 270 | 'use strict'; 271 | 272 | // copies all headers into a new Headers 273 | function cloneHeaders(headers) { 274 | let headersClone = new Headers(); 275 | for (let [name, value] of headers.entries()) { 276 | headersClone.append(name, value); 277 | } 278 | return headersClone; 279 | } 280 | 281 | function printHeaders(headers) { 282 | for (let [name, value] of headers.entries()) { 283 | console.log(name + ': ' + value); 284 | } 285 | } 286 | 287 | 288 | module.exports = { 289 | cloneHeaders, 290 | printHeaders 291 | }; 292 | 293 | 294 | /***/ }), 295 | /* 5 */ 296 | /***/ (function(module, exports, __webpack_require__) { 297 | 298 | 'use strict'; 299 | const errors = __webpack_require__(6); 300 | const VCDiff = __webpack_require__(7); 301 | 302 | /** 303 | * 304 | * @param delta {Uint8Array} 305 | * @param source {Uint8Array} 306 | */ 307 | function decodeSync(delta, source) { 308 | let vcdiff = new VCDiff(delta, source); 309 | return vcdiff.decode(); 310 | } 311 | 312 | function decode(delta, buffer) { 313 | 314 | } 315 | 316 | module.exports = { 317 | decodeSync, 318 | decode 319 | }; 320 | 321 | 322 | 323 | 324 | /***/ }), 325 | /* 6 */ 326 | /***/ (function(module, exports) { 327 | 328 | 'use strict'; 329 | /** 330 | * Takes in array of names of errors and returns an object mapping those names to error functions that take in one parameter that is used as the message for the error 331 | * @param names {[]} 332 | * @returns {{name1: function(message),...}} 333 | * @constructor 334 | */ 335 | function CustomErrors(names) { 336 | let errors = {}; 337 | names.forEach(name => { 338 | let CustomError = function CustomError(message) { 339 | var temp = Error.apply(this, arguments); 340 | temp.name = this.name = name; 341 | this.stack = temp.stack; 342 | this.message = temp.message; 343 | this.name = name; 344 | this.message = message; 345 | }; 346 | CustomError.prototype = Object.create(Error.prototype, { 347 | constructor: { 348 | value: CustomError, 349 | writable: true, 350 | configurable: true 351 | } 352 | }); 353 | errors[name] = CustomError; 354 | }); 355 | return errors; 356 | } 357 | 358 | module.exports = CustomErrors(['NotImplemented', 'InvalidDelta']); 359 | 360 | /***/ }), 361 | /* 7 */ 362 | /***/ (function(module, exports, __webpack_require__) { 363 | 364 | 'use strict'; 365 | 366 | const errors = __webpack_require__(6); 367 | const TypedArray = __webpack_require__(8); 368 | const deserializeInteger = __webpack_require__(9); 369 | const deserializeDelta = __webpack_require__(10); 370 | const NearCache = __webpack_require__(13); 371 | const SameCache = __webpack_require__(14); 372 | 373 | /** 374 | * 375 | * @param delta {Uint8Array} 376 | * @param source {Uint8Array} 377 | * @constructor 378 | */ 379 | function VCDiff(delta, source) { 380 | this.delta = delta; 381 | this.position = 0; 382 | this.source = source; 383 | this.targetWindows = new TypedArray.TypedArrayList(); 384 | } 385 | 386 | VCDiff.prototype.decode = function() { 387 | this._consumeHeader(); 388 | while (this._consumeWindow()) {} 389 | 390 | let targetLength = this.targetWindows.typedArrays.reduce((sum, uint8Array) => uint8Array.length + sum, 0); 391 | let target = new Uint8Array(targetLength); 392 | let position = 0; 393 | 394 | // concat all uint8arrays 395 | for (let arrayNum = 0; arrayNum < this.targetWindows.typedArrays.length; arrayNum++) { 396 | let array = this.targetWindows.typedArrays[arrayNum]; 397 | let length = array.length; 398 | target.set(array, position); 399 | position += length; 400 | } 401 | 402 | return target; 403 | }; 404 | 405 | VCDiff.prototype._consumeHeader = function() { 406 | 407 | let hasVCDiffHeader = this.delta[0] === 214 && // V 408 | this.delta[1] === 195 && // C 409 | this.delta[2] === 196 && // D 410 | this.delta[3] === 0; // \0 411 | 412 | if (!hasVCDiffHeader) { 413 | throw new errors.InvalidDelta('first 3 bytes not VCD'); 414 | } 415 | 416 | let hdrIndicator = this.delta[4]; 417 | // extract least significant bit 418 | let vcdDecompress = 1 & hdrIndicator; 419 | // extract second least significant bit 420 | let vcdCodetable = 1 & (hdrIndicator >> 1); 421 | 422 | // verify not using Hdr_Indicator 423 | if (vcdDecompress || vcdCodetable) { 424 | throw new errors.NotImplemented( 425 | 'non-zero Hdr_Indicator (VCD_DECOMPRESS or VCD_CODETABLE bit is set)' 426 | ); 427 | } 428 | 429 | this.position += 5; 430 | }; 431 | 432 | VCDiff.prototype._consumeWindow = function() { 433 | let winIndicator = this.delta[this.position++]; 434 | 435 | // extract least significant bit 436 | let vcdSource = 1 & winIndicator; 437 | // extract second least significant bit 438 | let vcdTarget = 1 & (winIndicator >> 1); 439 | 440 | if (vcdSource && vcdTarget) { 441 | throw new errors.InvalidDelta( 442 | 'VCD_SOURCE and VCD_TARGET cannot both be set in Win_Indicator' 443 | ) 444 | } 445 | else if (vcdSource) { 446 | let sourceSegmentLength, sourceSegmentPosition, deltaLength; 447 | ({ value: sourceSegmentLength, position: this.position } = deserializeInteger(this.delta, this.position)); 448 | ({ value: sourceSegmentPosition, position: this.position } = deserializeInteger(this.delta, this.position)); 449 | ({ value: deltaLength, position: this.position } = deserializeInteger(this.delta, this.position)); 450 | 451 | let sourceSegment = this.source.slice(sourceSegmentPosition, sourceSegmentPosition + sourceSegmentLength); 452 | this._buildTargetWindow(this.position, sourceSegment); 453 | this.position += deltaLength; 454 | } 455 | else if (vcdTarget) { 456 | throw new errors.NotImplemented( 457 | 'non-zero VCD_TARGET in Win_Indicator' 458 | ) 459 | } 460 | return this.position < this.delta.length; 461 | }; 462 | 463 | // first integer is target window length 464 | VCDiff.prototype._buildTargetWindow = function(position, sourceSegment) { 465 | let window = deserializeDelta(this.delta, position); 466 | 467 | let T = new Uint8Array(window.targetWindowLength); 468 | 469 | let U = new TypedArray.TypedArrayList(); 470 | U.add(sourceSegment); 471 | U.add(T); 472 | 473 | let targetPosition = this.source.length; 474 | let dataPosition = 0; 475 | 476 | let delta = new Delta(U, this.source.length, window.data, window.addresses); 477 | window.instructions.forEach(instruction => { 478 | instruction.execute(delta); 479 | }); 480 | 481 | let target = U.typedArrays[1]; 482 | this.targetWindows.add(target); 483 | }; 484 | 485 | function Delta(U, UTargetPosition, data, addresses) { 486 | this.U = U; 487 | this.UTargetPosition = UTargetPosition; 488 | this.data = data; 489 | this.dataPosition = 0; 490 | this.addresses = addresses; 491 | this.addressesPosition = 0; 492 | this.nearCache = new NearCache(4); 493 | this.sameCache = new SameCache(3); 494 | } 495 | 496 | Delta.prototype.getNextAddressInteger = function() { 497 | let value; 498 | // get next address and increase the address position for the next address 499 | ({value, position: this.addressesPosition } = deserializeInteger(this.addresses, this.addressesPosition)); 500 | return value; 501 | }; 502 | 503 | Delta.prototype.getNextAddressByte = function() { 504 | // get next address and increase the address position for the next address 505 | let value = this.addresses[this.addressesPosition++]; 506 | return value; 507 | }; 508 | 509 | module.exports = VCDiff; 510 | 511 | /***/ }), 512 | /* 8 */ 513 | /***/ (function(module, exports) { 514 | 515 | 'use strict'; 516 | 517 | function uint8ArrayToString(uintArray) { 518 | let encodedString = String.fromCharCode.apply(null, uintArray); 519 | let decodedString = decodeURIComponent(escape(encodedString)); 520 | return decodedString; 521 | } 522 | 523 | function stringToUint8Array(str) { 524 | var buf = new Uint8Array(str.length); 525 | for (var i=0, strLen=str.length; i < strLen; i++) { 526 | buf[i] = str.charCodeAt(i); 527 | } 528 | return buf; 529 | } 530 | 531 | function equal(typedArray1, typedArray2) { 532 | if (typedArray1.length !== typedArray2.length) { 533 | return false; 534 | } 535 | for (let i = 0; i < typedArray1.length; i++) { 536 | if (typedArray1[i] !== typedArray2[i]) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | 543 | function TypedArrayList() { 544 | this.typedArrays = []; 545 | this.startIndexes = []; 546 | this.length = 0; 547 | } 548 | 549 | TypedArrayList.prototype.add = function(typedArray) { 550 | let typedArrayTypes = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, 551 | Int32Array, Uint32Array, Float32Array, Float64Array]; 552 | 553 | let matchingTypedArrayTypes = typedArrayTypes.filter(typedArrayType => typedArray instanceof typedArrayType); 554 | if (matchingTypedArrayTypes.length < 1) { 555 | throw Error('Given ' + typeof typedArray + ' when expected a TypedArray'); 556 | } 557 | 558 | let startIndex; 559 | if (this.typedArrays.length === 0) { 560 | startIndex = 0; 561 | } 562 | else { 563 | let lastIndex = this.startIndexes.length - 1; 564 | let lastStartIndex = this.startIndexes[lastIndex]; 565 | let lastLength = this.typedArrays[lastIndex].length; 566 | startIndex = lastStartIndex + lastLength; 567 | } 568 | 569 | this.startIndexes.push(startIndex); 570 | this.typedArrays.push(typedArray); 571 | this.length += startIndex + typedArray.length; 572 | }; 573 | 574 | TypedArrayList.prototype.get = function(index) { 575 | let listIndex = getIndex(this.startIndexes, index); 576 | let typedArray = index - this.startIndexes[listIndex]; 577 | return this.typedArrays[listIndex][typedArray]; 578 | }; 579 | 580 | TypedArrayList.prototype.set = function(index, value) { 581 | if (typeof index !== 'number' || isNaN(index)) { 582 | throw new Error('Given non-number index: ' + index); 583 | } 584 | //console.log(index); 585 | 586 | let listIndex = getIndex(this.startIndexes, index); 587 | let typedArrayIndex = index - this.startIndexes[listIndex]; 588 | this.typedArrays[listIndex][typedArrayIndex] = value; 589 | }; 590 | 591 | function getIndex(arr, element) { 592 | let low = 0; 593 | let high = arr.length - 1; 594 | 595 | while (low < high) { 596 | let mid = Math.floor((low + high) / 2); 597 | 598 | if (arr[mid] === element) { 599 | return mid; 600 | } 601 | else if (arr[mid] < element) { 602 | low = mid + 1; 603 | } 604 | else { 605 | high = mid - 1; 606 | } 607 | } 608 | if (arr[high] > element) { 609 | return high - 1; 610 | } 611 | else { 612 | return high; 613 | } 614 | } 615 | 616 | module.exports = { 617 | uint8ArrayToString, 618 | stringToUint8Array, 619 | equal, 620 | TypedArrayList 621 | }; 622 | 623 | 624 | 625 | /***/ }), 626 | /* 9 */ 627 | /***/ (function(module, exports) { 628 | 629 | 'use strict'; 630 | 631 | /** 632 | * Converts RFC 3284 definition of integer in buffer to decimal 633 | * Also returns the index of the byte after the integer 634 | * @param buffer {Uint8Array} 635 | * @param position {Number} 636 | * @returns {{position: {Number}, value: {Number}}} 637 | */ 638 | function integer(buffer, position) { 639 | let integer = buffer[position++]; 640 | let digitArray = []; 641 | 642 | let numBytes = 0; 643 | 644 | // if the most significant bit is set, the the integer continues 645 | while (integer >= 128) { 646 | digitArray.unshift(integer - 128); 647 | integer = buffer[position++]; 648 | if (numBytes > 1000) { 649 | throw new Error('Integer is probably too long') 650 | } 651 | numBytes++; 652 | } 653 | 654 | digitArray.unshift(integer); 655 | 656 | // convert from base 128 to decimal 657 | return { 658 | position: position, 659 | value: digitArray.reduce((sum, digit, index) => sum + digit * Math.pow(128, index), 0) 660 | }; 661 | } 662 | 663 | module.exports = integer; 664 | 665 | /***/ }), 666 | /* 10 */ 667 | /***/ (function(module, exports, __webpack_require__) { 668 | 669 | 'use strict'; 670 | 671 | const errors = __webpack_require__(6); 672 | const deserializeInteger = __webpack_require__(9); 673 | const tokenizeInstructions = __webpack_require__(11); 674 | 675 | function delta(delta, position) { 676 | 677 | let targetWindowLength, dataLength, instructionsLength, addressesLength; 678 | 679 | // parentheses are needed for assignment destructuring 680 | ({ value: targetWindowLength, position } = deserializeInteger(delta, position)); 681 | 682 | // Delta_Indicator byte 683 | if (delta[position] !== 0) { 684 | throw new errors.NotImplemented( 685 | 'VCD_DECOMPRESS is not supported, Delta_Indicator must be zero at byte ' + position + ' and not ' + delta[position] 686 | ); 687 | } 688 | position++; 689 | 690 | ({ value: dataLength, position } = deserializeInteger(delta, position)); 691 | ({ value: instructionsLength, position } = deserializeInteger(delta, position)); 692 | ({ value: addressesLength, position } = deserializeInteger(delta, position)); 693 | 694 | let dataNextPosition = position + dataLength; 695 | let data = delta.slice(position, dataNextPosition); 696 | 697 | let instructionsNextPosition = dataNextPosition + instructionsLength; 698 | let instructions = delta.slice(dataNextPosition, instructionsNextPosition); 699 | let deserializedInstructions = tokenizeInstructions(instructions); 700 | 701 | let addressesNextPosition = instructionsNextPosition + addressesLength; 702 | let addresses = delta.slice(instructionsNextPosition, addressesNextPosition); 703 | 704 | position = addressesNextPosition; 705 | 706 | let window = { 707 | targetWindowLength, 708 | position, 709 | data, 710 | instructions: deserializedInstructions, 711 | addresses 712 | }; 713 | 714 | return window; 715 | } 716 | 717 | module.exports = delta; 718 | 719 | 720 | 721 | /***/ }), 722 | /* 11 */ 723 | /***/ (function(module, exports, __webpack_require__) { 724 | 725 | 'use strict'; 726 | 727 | const instructions = __webpack_require__(12); 728 | const deserializeInteger = __webpack_require__(9); 729 | 730 | function tokenizeInstructions(instructionsBuffer) { 731 | let deserializedInstructions = []; 732 | 733 | let instructionsPosition = 0; 734 | 735 | while (instructionsPosition < instructionsBuffer.length) { 736 | let index = instructionsBuffer[instructionsPosition++]; 737 | 738 | let addSize, copySize, size; 739 | 740 | if (index === 0) { 741 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 742 | deserializedInstructions.push(new instructions.RUN(size)); 743 | } 744 | else if (index === 1) { 745 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 746 | deserializedInstructions.push(new instructions.ADD(size)); 747 | } 748 | else if (index < 19) { 749 | deserializedInstructions.push(new instructions.ADD(index - 1)); 750 | } 751 | else if (index === 19) { 752 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 753 | deserializedInstructions.push(new instructions.COPY(size, 0)); 754 | } 755 | else if (index < 35) { 756 | deserializedInstructions.push(new instructions.COPY(index - 16, 0)); 757 | } 758 | else if (index === 35) { 759 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 760 | deserializedInstructions.push(new instructions.COPY(size, 1)); 761 | } 762 | else if (index < 51) { 763 | deserializedInstructions.push(new instructions.COPY(index - 32, 1)); 764 | } 765 | else if (index === 51) { 766 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 767 | deserializedInstructions.push(new instructions.COPY(size, 2)); 768 | } 769 | else if (index < 67) { 770 | deserializedInstructions.push(new instructions.COPY(index - 48, 2)); 771 | } 772 | else if (index === 67) { 773 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 774 | deserializedInstructions.push(new instructions.COPY(size, 3)); 775 | } 776 | else if (index < 83) { 777 | deserializedInstructions.push(new instructions.COPY(index - 64, 3)); 778 | } 779 | else if (index === 83) { 780 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 781 | deserializedInstructions.push(new instructions.COPY(size, 4)); 782 | } 783 | else if (index < 99) { 784 | deserializedInstructions.push(new instructions.COPY(index - 80, 4)); 785 | } 786 | else if (index === 99) { 787 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 788 | deserializedInstructions.push(new instructions.COPY(size, 5)); 789 | } 790 | else if (index < 115) { 791 | deserializedInstructions.push(new instructions.COPY(index - 96, 5)); 792 | } 793 | else if (index === 115) { 794 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 795 | deserializedInstructions.push(new instructions.COPY(size, 6)); 796 | } 797 | else if (index < 131) { 798 | deserializedInstructions.push(new instructions.COPY(index - 112, 6)); 799 | } 800 | else if (index === 131) { 801 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 802 | deserializedInstructions.push(new instructions.COPY(size, 7)); 803 | } 804 | else if (index < 147) { 805 | deserializedInstructions.push(new instructions.COPY(index - 127, 7)); 806 | } 807 | else if (index === 147) { 808 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 809 | deserializedInstructions.push(new instructions.COPY(size, 8)); 810 | } 811 | else if (index < 163) { 812 | deserializedInstructions.push(new instructions.COPY(index - 144, 8)); 813 | } 814 | else if (index < 175) { 815 | ({addSize, copySize} = ADD_COPY(index, 163)); 816 | 817 | deserializedInstructions.push(new instructions.ADD(addSize)); 818 | deserializedInstructions.push(new instructions.COPY(copySize, 0)); 819 | } 820 | else if (index < 187) { 821 | ({addSize, copySize} = ADD_COPY(index, 175)); 822 | 823 | deserializedInstructions.push(new instructions.ADD(addSize)); 824 | deserializedInstructions.push(new instructions.COPY(copySize, 1)); 825 | } 826 | else if (index < 199) { 827 | ({addSize, copySize} = ADD_COPY(index, 187)); 828 | 829 | deserializedInstructions.push(new instructions.ADD(addSize)); 830 | deserializedInstructions.push(new instructions.COPY(copySize, 2)); 831 | } 832 | else if (index < 211) { 833 | ({addSize, copySize} = ADD_COPY(index, 199)); 834 | 835 | deserializedInstructions.push(new instructions.ADD(addSize)); 836 | deserializedInstructions.push(new instructions.COPY(copySize, 3)); 837 | } 838 | else if (index < 223) { 839 | ({addSize, copySize} = ADD_COPY(index, 211)); 840 | 841 | deserializedInstructions.push(new instructions.ADD(addSize)); 842 | deserializedInstructions.push(new instructions.COPY(copySize, 4)); 843 | } 844 | else if (index < 235) { 845 | ({addSize, copySize} = ADD_COPY(index, 223)); 846 | 847 | deserializedInstructions.push(new instructions.ADD(addSize)); 848 | deserializedInstructions.push(new instructions.COPY(copySize, 5)); 849 | } 850 | else if (index < 239) { 851 | deserializedInstructions.push(new instructions.ADD(index - 235 + 1)); 852 | deserializedInstructions.push(new instructions.COPY(4, 6)); 853 | } 854 | else if (index < 243) { 855 | deserializedInstructions.push(new instructions.ADD(index - 239 + 1)); 856 | deserializedInstructions.push(new instructions.COPY(4, 7)); 857 | } 858 | else if (index < 247) { 859 | deserializedInstructions.push(new instructions.ADD(index - 243 + 1)); 860 | deserializedInstructions.push(new instructions.COPY(4, 8)); 861 | } 862 | else if (index < 256) { 863 | deserializedInstructions.push(new instructions.COPY(4, index - 247)); 864 | deserializedInstructions.push(new instructions.ADD(1)); 865 | } 866 | else { 867 | throw new Error('Should not get here'); 868 | } 869 | } 870 | 871 | return deserializedInstructions; 872 | } 873 | 874 | function ADD_COPY(index, baseIndex) { 875 | let zeroBased = index - baseIndex; 876 | 877 | // 0,1,2 -> 0 3,4,5 -> 1 etc. 878 | let addSizeIndex = Math.floor(zeroBased / 3); 879 | // offset so size starts at 1 880 | let addSize = addSizeIndex + 1; 881 | 882 | // rotate through 0, 1, and 2 883 | let copySizeIndex = zeroBased % 3; 884 | // offset so size starts at 4 885 | let copySize = copySizeIndex + 4; 886 | 887 | return [addSize, copySize]; 888 | } 889 | 890 | module.exports = tokenizeInstructions; 891 | 892 | /***/ }), 893 | /* 12 */ 894 | /***/ (function(module, exports, __webpack_require__) { 895 | 896 | 'use strict'; 897 | 898 | const deserializeInteger = __webpack_require__(9); 899 | const TypedArray = __webpack_require__(8); 900 | 901 | function ADD(size) { 902 | this.size = size; 903 | } 904 | function COPY(size, mode) { 905 | this.size = size; 906 | this.mode = mode; 907 | } 908 | function RUN(size) { 909 | this.size = size; 910 | } 911 | 912 | ADD.prototype.name = 'ADD'; 913 | COPY.prototype.name = 'COPY'; 914 | RUN.prototype.name = 'RUN'; 915 | 916 | ADD.prototype.execute = function(delta) { 917 | for (let i = 0; i < this.size; i++) { 918 | delta.U.set(delta.UTargetPosition + i, delta.data[delta.dataPosition + i]); 919 | } 920 | delta.dataPosition += this.size; 921 | delta.UTargetPosition += this.size; 922 | }; 923 | 924 | COPY.prototype.execute = function(delta) { 925 | let address, m, next, method; 926 | 927 | if (this.mode === 0) { 928 | address = delta.getNextAddressInteger(); 929 | } 930 | else if (this.mode === 1) { 931 | next = delta.getNextAddressInteger(); 932 | address = delta.UTargetPosition - next; 933 | } 934 | else if ((m = this.mode - 2) >= 0 && (m < delta.nearCache.size)) { 935 | next = delta.getNextAddressInteger(); 936 | address = delta.nearCache.get(m, next); 937 | method = 'near'; 938 | } 939 | // same cache 940 | else { 941 | m = this.mode - (2 + delta.nearCache.size); 942 | next = delta.getNextAddressByte(); 943 | address = delta.sameCache.get(m, next); 944 | method = 'same'; 945 | } 946 | 947 | delta.nearCache.update(address); 948 | delta.sameCache.update(address); 949 | 950 | for (let i = 0; i < this.size; i++) { 951 | delta.U.set(delta.UTargetPosition + i, delta.U.get(address + i)); 952 | } 953 | 954 | delta.UTargetPosition += this.size; 955 | }; 956 | 957 | RUN.prototype.execute = function(delta) { 958 | for (let i = 0; i < this.size; i++) { 959 | // repeat single byte 960 | delta.U[delta.UTargetPosition + i] = delta.data[delta.dataPosition]; 961 | } 962 | // increment to next byte 963 | delta.dataPosition++; 964 | delta.UTargetPosition += this.size; 965 | }; 966 | 967 | let instructions = { 968 | ADD, 969 | COPY, 970 | RUN 971 | }; 972 | 973 | module.exports = instructions; 974 | 975 | /***/ }), 976 | /* 13 */ 977 | /***/ (function(module, exports) { 978 | 979 | 'use strict'; 980 | 981 | function NearCache(size) { 982 | this.size = size; 983 | this.near = new Array(this.size).fill(0); 984 | this.nextSlot = 0; 985 | } 986 | 987 | NearCache.prototype.update = function(address) { 988 | if (this.near.length > 0) { 989 | this.near[this.nextSlot] = address; 990 | this.nextSlot = (this.nextSlot + 1) % this.near.length; 991 | } 992 | }; 993 | 994 | NearCache.prototype.get = function(m, offset) { 995 | let address = this.near[m] + offset; 996 | return address; 997 | }; 998 | 999 | module.exports = NearCache; 1000 | 1001 | /***/ }), 1002 | /* 14 */ 1003 | /***/ (function(module, exports) { 1004 | 1005 | 'use strict'; 1006 | 1007 | function SameCache(size) { 1008 | this.size = size; 1009 | this.same = new Array(this.size * 256).fill(0); 1010 | } 1011 | 1012 | SameCache.prototype.update = function(address) { 1013 | if (this.same.length > 0) { 1014 | this.same[address % (this.size * 256)] = address; 1015 | } 1016 | }; 1017 | 1018 | SameCache.prototype.get = function(m, offset) { 1019 | let address = this.same[m * 256 + offset]; 1020 | return address; 1021 | }; 1022 | 1023 | module.exports = SameCache; 1024 | 1025 | /***/ }) 1026 | /******/ ]); -------------------------------------------------------------------------------- /test/public/delta_cache_sw.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ (function(module, exports, __webpack_require__) { 46 | 47 | 'use strict'; 48 | 49 | const fetchProxy = __webpack_require__(1); 50 | 51 | const CACHE_NAME = 'delta-cache-v1'; 52 | 53 | self.addEventListener('install', function(event) { 54 | // forces the waiting service worker to become the active service worker 55 | self.skipWaiting(); 56 | }); 57 | 58 | self.addEventListener('activate', function(event) { 59 | // extend lifetime of event until cache is deleted 60 | event.waitUntil( 61 | // deletes cache to prevent incompatibility between requests 62 | caches.delete(CACHE_NAME) 63 | ); 64 | }); 65 | 66 | self.addEventListener('fetch', function(event) { 67 | event.respondWith( 68 | caches.open(CACHE_NAME).then( 69 | // a proxy between the client and the server 70 | // returns a promise that contains a response 71 | fetchProxy.bind(null, event.request, self.registration.scope) 72 | ) 73 | ); 74 | }); 75 | 76 | 77 | 78 | /***/ }), 79 | /* 1 */ 80 | /***/ (function(module, exports, __webpack_require__) { 81 | 82 | 'use strict'; 83 | 84 | const urlUtil = __webpack_require__(2); 85 | const deltaFetch = __webpack_require__(3); 86 | const fetchUtil = __webpack_require__(4); 87 | 88 | function fetchProxy(originalRequest, scope, cache) { 89 | // get cached response which matches the request 90 | const newResponse = cache.match(originalRequest).then(cachedResponse => 91 | fetch( 92 | // create delta request with cached version of file 93 | deltaFetch.createDeltaRequest(originalRequest, cachedResponse.headers.get('ETag')) 94 | ) 95 | // handle delta response to create normal response 96 | .then(processServerResponse.bind(null, cachedResponse)) 97 | ) 98 | // not in cache, no delta encoding available, do normal request 99 | .catch(() => fetch(originalRequest)); 100 | 101 | newResponse.then(response => { 102 | deltaFetch.cacheIfHasEtag(cache, originalRequest, response.clone()); 103 | }) 104 | 105 | return newResponse; 106 | } 107 | 108 | // handle delta encoded and normal responses, return promise containing Response object 109 | function processServerResponse(cachedResponse, serverResponse) { 110 | 111 | // server sent a patch (rather than the full file) 112 | if (serverResponse.status === 226 && serverResponse.headers.get('Delta-Base') === cachedResponse.headers.get('ETag')) { 113 | // use the patch on the cached file to create an updated response 114 | const newResponse = deltaFetch.patchResponse(serverResponse, cachedResponse); 115 | return Promise.resolve(newResponse); 116 | } 117 | // no change from cached version 118 | else if (serverResponse.status === 304) { 119 | const newResponse = deltaFetch.convert304to200(cachedResponse, serverResonse.headers) 120 | return Promise.resolve(newResponse); 121 | } 122 | // no delta encoding 123 | else { 124 | return Promise.resolve(serverResponse); 125 | } 126 | } 127 | 128 | module.exports = fetchProxy; 129 | 130 | 131 | 132 | /***/ }), 133 | /* 2 */ 134 | /***/ (function(module, exports) { 135 | 136 | 'use strict'; 137 | 138 | 139 | // returns whether the origins or the two urls are the same 140 | function isSameOrigin(url1, url2) { 141 | let parsedRequestUrl = getLocation(url1); 142 | let parsedCurrentUrl = getLocation(url2); 143 | 144 | return parsedRequestUrl.host === parsedCurrentUrl.host 145 | && parsedRequestUrl.protocol === parsedCurrentUrl.protocol; 146 | } 147 | 148 | function getLocation(href) { 149 | var match = href.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)(\/[^?#]*)(\?[^#]*|)(#.*|)$/); 150 | return match && { 151 | protocol: match[1], 152 | host: match[2], 153 | hostname: match[3], 154 | port: match[4], 155 | pathname: match[5], 156 | search: match[6], 157 | hash: match[7] 158 | } 159 | } 160 | 161 | module.exports = { 162 | isSameOrigin, 163 | getLocation 164 | } 165 | 166 | 167 | /***/ }), 168 | /* 3 */ 169 | /***/ (function(module, exports, __webpack_require__) { 170 | 171 | 'use strict'; 172 | 173 | const fetchUtil = __webpack_require__(4); 174 | const vcdiff = __webpack_require__(5); 175 | 176 | // cache the request/response if response contains the Delta-Version header 177 | function cacheIfHasEtag(cache, request, response) { 178 | if (response.headers.has('ETag')) { 179 | return cache.delete(request).then(() => { 180 | return cache.put(request, response.clone()); 181 | }); 182 | } 183 | else { 184 | return Promise.resolve(); 185 | } 186 | } 187 | 188 | // creates copy of request that contains the Etag of the cached response 189 | // as well as other headers 190 | function createDeltaRequest(originalRequest, cachedEtag) { 191 | const headers = fetchUtil.cloneHeaders(originalRequest.headers); 192 | 193 | // set VCDIFF encoding headers 194 | headers.set('A-IM', 'vcdiff'); 195 | headers.set('If-None-Match', cachedEtag); 196 | 197 | // return new request with delta headers 198 | return new Request(originalRequest.url, { 199 | method: originalRequest.method, 200 | headers, 201 | // can't create request with mode 'navigate', so we put 'same-origin' 202 | // since we know it's the same origin 203 | mode: originalRequest.mode === 'navigate' ? 204 | 'same-origin' : originalRequest.mode, 205 | credentials: originalRequest.credentials, 206 | redirect: 'manual' 207 | }); 208 | } 209 | 210 | // create 200 response from 304 response 211 | function convert304To200(response, newHeaders) { 212 | response.blob().then(blob => { 213 | const headers = fetchUtil.cloneHeaders(newHeaders); 214 | headers.set('Content-Type', cachedResponse.headers.get('Content-Type')); 215 | headers.delete('Content-Length'); 216 | 217 | header.set('X-Delta-Length', '0'); 218 | 219 | return new Response(blob, { 220 | status: 200, 221 | statusText: 'OK', 222 | headers, 223 | url: serverResponse.url 224 | }); 225 | }) 226 | } 227 | 228 | // takes a delta response and applies its patch to the other response 229 | // returns a promise resolving to the new response 230 | function patchResponse(patchResponse, responseToChange) { 231 | return Promise.all([patchResponse.arrayBuffer(), responseToChange.arrayBuffer()]).then(([deltaArrayBuffer, sourceArrayBuffer]) => { 232 | const delta = new Uint8Array(deltaArrayBuffer); 233 | const source = new Uint8Array(sourceArrayBuffer); 234 | 235 | const updated = vcdiff.decodeSync(delta, source); 236 | const headers = fetchUtil.cloneHeaders(patchResponse.headers); 237 | 238 | if (responseToChange.headers.has('Content-Type')) { 239 | headers.set('Content-Type', responseToChange.headers.get('Content-Type')); 240 | } 241 | 242 | // discard delta headers 243 | headers.delete('Content-Length'); 244 | headers.delete('Delta-Base'); 245 | headers.delete('im'); 246 | 247 | headers.set('X-Delta-Length', deltaArrayBuffer.byteLength.toString()); 248 | 249 | return new Response(updated, { 250 | status: 200, 251 | statusText: 'OK', 252 | headers: headers, 253 | url: patchResponse.url 254 | }); 255 | }); 256 | } 257 | 258 | module.exports = { 259 | cacheIfHasEtag, 260 | createDeltaRequest, 261 | convert304To200, 262 | patchResponse 263 | } 264 | 265 | 266 | /***/ }), 267 | /* 4 */ 268 | /***/ (function(module, exports) { 269 | 270 | 'use strict'; 271 | 272 | // copies all headers into a new Headers 273 | function cloneHeaders(headers) { 274 | let headersClone = new Headers(); 275 | for (let [name, value] of headers.entries()) { 276 | headersClone.append(name, value); 277 | } 278 | return headersClone; 279 | } 280 | 281 | function printHeaders(headers) { 282 | for (let [name, value] of headers.entries()) { 283 | console.log(name + ': ' + value); 284 | } 285 | } 286 | 287 | 288 | module.exports = { 289 | cloneHeaders, 290 | printHeaders 291 | }; 292 | 293 | 294 | /***/ }), 295 | /* 5 */ 296 | /***/ (function(module, exports, __webpack_require__) { 297 | 298 | 'use strict'; 299 | const errors = __webpack_require__(6); 300 | const VCDiff = __webpack_require__(7); 301 | 302 | /** 303 | * 304 | * @param delta {Uint8Array} 305 | * @param source {Uint8Array} 306 | */ 307 | function decodeSync(delta, source) { 308 | let vcdiff = new VCDiff(delta, source); 309 | return vcdiff.decode(); 310 | } 311 | 312 | function decode(delta, buffer) { 313 | 314 | } 315 | 316 | module.exports = { 317 | decodeSync, 318 | decode 319 | }; 320 | 321 | 322 | 323 | 324 | /***/ }), 325 | /* 6 */ 326 | /***/ (function(module, exports) { 327 | 328 | 'use strict'; 329 | /** 330 | * Takes in array of names of errors and returns an object mapping those names to error functions that take in one parameter that is used as the message for the error 331 | * @param names {[]} 332 | * @returns {{name1: function(message),...}} 333 | * @constructor 334 | */ 335 | function CustomErrors(names) { 336 | let errors = {}; 337 | names.forEach(name => { 338 | let CustomError = function CustomError(message) { 339 | var temp = Error.apply(this, arguments); 340 | temp.name = this.name = name; 341 | this.stack = temp.stack; 342 | this.message = temp.message; 343 | this.name = name; 344 | this.message = message; 345 | }; 346 | CustomError.prototype = Object.create(Error.prototype, { 347 | constructor: { 348 | value: CustomError, 349 | writable: true, 350 | configurable: true 351 | } 352 | }); 353 | errors[name] = CustomError; 354 | }); 355 | return errors; 356 | } 357 | 358 | module.exports = CustomErrors(['NotImplemented', 'InvalidDelta']); 359 | 360 | /***/ }), 361 | /* 7 */ 362 | /***/ (function(module, exports, __webpack_require__) { 363 | 364 | 'use strict'; 365 | 366 | const errors = __webpack_require__(6); 367 | const TypedArray = __webpack_require__(8); 368 | const deserializeInteger = __webpack_require__(9); 369 | const deserializeDelta = __webpack_require__(10); 370 | const NearCache = __webpack_require__(13); 371 | const SameCache = __webpack_require__(14); 372 | 373 | /** 374 | * 375 | * @param delta {Uint8Array} 376 | * @param source {Uint8Array} 377 | * @constructor 378 | */ 379 | function VCDiff(delta, source) { 380 | this.delta = delta; 381 | this.position = 0; 382 | this.source = source; 383 | this.targetWindows = new TypedArray.TypedArrayList(); 384 | } 385 | 386 | VCDiff.prototype.decode = function() { 387 | this._consumeHeader(); 388 | while (this._consumeWindow()) {} 389 | 390 | let targetLength = this.targetWindows.typedArrays.reduce((sum, uint8Array) => uint8Array.length + sum, 0); 391 | let target = new Uint8Array(targetLength); 392 | let position = 0; 393 | 394 | // concat all uint8arrays 395 | for (let arrayNum = 0; arrayNum < this.targetWindows.typedArrays.length; arrayNum++) { 396 | let array = this.targetWindows.typedArrays[arrayNum]; 397 | let length = array.length; 398 | target.set(array, position); 399 | position += length; 400 | } 401 | 402 | return target; 403 | }; 404 | 405 | VCDiff.prototype._consumeHeader = function() { 406 | 407 | let hasVCDiffHeader = this.delta[0] === 214 && // V 408 | this.delta[1] === 195 && // C 409 | this.delta[2] === 196 && // D 410 | this.delta[3] === 0; // \0 411 | 412 | if (!hasVCDiffHeader) { 413 | throw new errors.InvalidDelta('first 3 bytes not VCD'); 414 | } 415 | 416 | let hdrIndicator = this.delta[4]; 417 | // extract least significant bit 418 | let vcdDecompress = 1 & hdrIndicator; 419 | // extract second least significant bit 420 | let vcdCodetable = 1 & (hdrIndicator >> 1); 421 | 422 | // verify not using Hdr_Indicator 423 | if (vcdDecompress || vcdCodetable) { 424 | throw new errors.NotImplemented( 425 | 'non-zero Hdr_Indicator (VCD_DECOMPRESS or VCD_CODETABLE bit is set)' 426 | ); 427 | } 428 | 429 | this.position += 5; 430 | }; 431 | 432 | VCDiff.prototype._consumeWindow = function() { 433 | let winIndicator = this.delta[this.position++]; 434 | 435 | // extract least significant bit 436 | let vcdSource = 1 & winIndicator; 437 | // extract second least significant bit 438 | let vcdTarget = 1 & (winIndicator >> 1); 439 | 440 | if (vcdSource && vcdTarget) { 441 | throw new errors.InvalidDelta( 442 | 'VCD_SOURCE and VCD_TARGET cannot both be set in Win_Indicator' 443 | ) 444 | } 445 | else if (vcdSource) { 446 | let sourceSegmentLength, sourceSegmentPosition, deltaLength; 447 | ({ value: sourceSegmentLength, position: this.position } = deserializeInteger(this.delta, this.position)); 448 | ({ value: sourceSegmentPosition, position: this.position } = deserializeInteger(this.delta, this.position)); 449 | ({ value: deltaLength, position: this.position } = deserializeInteger(this.delta, this.position)); 450 | 451 | let sourceSegment = this.source.slice(sourceSegmentPosition, sourceSegmentPosition + sourceSegmentLength); 452 | this._buildTargetWindow(this.position, sourceSegment); 453 | this.position += deltaLength; 454 | } 455 | else if (vcdTarget) { 456 | throw new errors.NotImplemented( 457 | 'non-zero VCD_TARGET in Win_Indicator' 458 | ) 459 | } 460 | return this.position < this.delta.length; 461 | }; 462 | 463 | // first integer is target window length 464 | VCDiff.prototype._buildTargetWindow = function(position, sourceSegment) { 465 | let window = deserializeDelta(this.delta, position); 466 | 467 | let T = new Uint8Array(window.targetWindowLength); 468 | 469 | let U = new TypedArray.TypedArrayList(); 470 | U.add(sourceSegment); 471 | U.add(T); 472 | 473 | let targetPosition = this.source.length; 474 | let dataPosition = 0; 475 | 476 | let delta = new Delta(U, this.source.length, window.data, window.addresses); 477 | window.instructions.forEach(instruction => { 478 | instruction.execute(delta); 479 | }); 480 | 481 | let target = U.typedArrays[1]; 482 | this.targetWindows.add(target); 483 | }; 484 | 485 | function Delta(U, UTargetPosition, data, addresses) { 486 | this.U = U; 487 | this.UTargetPosition = UTargetPosition; 488 | this.data = data; 489 | this.dataPosition = 0; 490 | this.addresses = addresses; 491 | this.addressesPosition = 0; 492 | this.nearCache = new NearCache(4); 493 | this.sameCache = new SameCache(3); 494 | } 495 | 496 | Delta.prototype.getNextAddressInteger = function() { 497 | let value; 498 | // get next address and increase the address position for the next address 499 | ({value, position: this.addressesPosition } = deserializeInteger(this.addresses, this.addressesPosition)); 500 | return value; 501 | }; 502 | 503 | Delta.prototype.getNextAddressByte = function() { 504 | // get next address and increase the address position for the next address 505 | let value = this.addresses[this.addressesPosition++]; 506 | return value; 507 | }; 508 | 509 | module.exports = VCDiff; 510 | 511 | /***/ }), 512 | /* 8 */ 513 | /***/ (function(module, exports) { 514 | 515 | 'use strict'; 516 | 517 | function uint8ArrayToString(uintArray) { 518 | let encodedString = String.fromCharCode.apply(null, uintArray); 519 | let decodedString = decodeURIComponent(escape(encodedString)); 520 | return decodedString; 521 | } 522 | 523 | function stringToUint8Array(str) { 524 | var buf = new Uint8Array(str.length); 525 | for (var i=0, strLen=str.length; i < strLen; i++) { 526 | buf[i] = str.charCodeAt(i); 527 | } 528 | return buf; 529 | } 530 | 531 | function equal(typedArray1, typedArray2) { 532 | if (typedArray1.length !== typedArray2.length) { 533 | return false; 534 | } 535 | for (let i = 0; i < typedArray1.length; i++) { 536 | if (typedArray1[i] !== typedArray2[i]) { 537 | return false; 538 | } 539 | } 540 | return true; 541 | } 542 | 543 | function TypedArrayList() { 544 | this.typedArrays = []; 545 | this.startIndexes = []; 546 | this.length = 0; 547 | } 548 | 549 | TypedArrayList.prototype.add = function(typedArray) { 550 | let typedArrayTypes = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, 551 | Int32Array, Uint32Array, Float32Array, Float64Array]; 552 | 553 | let matchingTypedArrayTypes = typedArrayTypes.filter(typedArrayType => typedArray instanceof typedArrayType); 554 | if (matchingTypedArrayTypes.length < 1) { 555 | throw Error('Given ' + typeof typedArray + ' when expected a TypedArray'); 556 | } 557 | 558 | let startIndex; 559 | if (this.typedArrays.length === 0) { 560 | startIndex = 0; 561 | } 562 | else { 563 | let lastIndex = this.startIndexes.length - 1; 564 | let lastStartIndex = this.startIndexes[lastIndex]; 565 | let lastLength = this.typedArrays[lastIndex].length; 566 | startIndex = lastStartIndex + lastLength; 567 | } 568 | 569 | this.startIndexes.push(startIndex); 570 | this.typedArrays.push(typedArray); 571 | this.length += startIndex + typedArray.length; 572 | }; 573 | 574 | TypedArrayList.prototype.get = function(index) { 575 | let listIndex = getIndex(this.startIndexes, index); 576 | let typedArray = index - this.startIndexes[listIndex]; 577 | return this.typedArrays[listIndex][typedArray]; 578 | }; 579 | 580 | TypedArrayList.prototype.set = function(index, value) { 581 | if (typeof index !== 'number' || isNaN(index)) { 582 | throw new Error('Given non-number index: ' + index); 583 | } 584 | //console.log(index); 585 | 586 | let listIndex = getIndex(this.startIndexes, index); 587 | let typedArrayIndex = index - this.startIndexes[listIndex]; 588 | this.typedArrays[listIndex][typedArrayIndex] = value; 589 | }; 590 | 591 | function getIndex(arr, element) { 592 | let low = 0; 593 | let high = arr.length - 1; 594 | 595 | while (low < high) { 596 | let mid = Math.floor((low + high) / 2); 597 | 598 | if (arr[mid] === element) { 599 | return mid; 600 | } 601 | else if (arr[mid] < element) { 602 | low = mid + 1; 603 | } 604 | else { 605 | high = mid - 1; 606 | } 607 | } 608 | if (arr[high] > element) { 609 | return high - 1; 610 | } 611 | else { 612 | return high; 613 | } 614 | } 615 | 616 | module.exports = { 617 | uint8ArrayToString, 618 | stringToUint8Array, 619 | equal, 620 | TypedArrayList 621 | }; 622 | 623 | 624 | 625 | /***/ }), 626 | /* 9 */ 627 | /***/ (function(module, exports) { 628 | 629 | 'use strict'; 630 | 631 | /** 632 | * Converts RFC 3284 definition of integer in buffer to decimal 633 | * Also returns the index of the byte after the integer 634 | * @param buffer {Uint8Array} 635 | * @param position {Number} 636 | * @returns {{position: {Number}, value: {Number}}} 637 | */ 638 | function integer(buffer, position) { 639 | let integer = buffer[position++]; 640 | let digitArray = []; 641 | 642 | let numBytes = 0; 643 | 644 | // if the most significant bit is set, the the integer continues 645 | while (integer >= 128) { 646 | digitArray.unshift(integer - 128); 647 | integer = buffer[position++]; 648 | if (numBytes > 1000) { 649 | throw new Error('Integer is probably too long') 650 | } 651 | numBytes++; 652 | } 653 | 654 | digitArray.unshift(integer); 655 | 656 | // convert from base 128 to decimal 657 | return { 658 | position: position, 659 | value: digitArray.reduce((sum, digit, index) => sum + digit * Math.pow(128, index), 0) 660 | }; 661 | } 662 | 663 | module.exports = integer; 664 | 665 | /***/ }), 666 | /* 10 */ 667 | /***/ (function(module, exports, __webpack_require__) { 668 | 669 | 'use strict'; 670 | 671 | const errors = __webpack_require__(6); 672 | const deserializeInteger = __webpack_require__(9); 673 | const tokenizeInstructions = __webpack_require__(11); 674 | 675 | function delta(delta, position) { 676 | 677 | let targetWindowLength, dataLength, instructionsLength, addressesLength; 678 | 679 | // parentheses are needed for assignment destructuring 680 | ({ value: targetWindowLength, position } = deserializeInteger(delta, position)); 681 | 682 | // Delta_Indicator byte 683 | if (delta[position] !== 0) { 684 | throw new errors.NotImplemented( 685 | 'VCD_DECOMPRESS is not supported, Delta_Indicator must be zero at byte ' + position + ' and not ' + delta[position] 686 | ); 687 | } 688 | position++; 689 | 690 | ({ value: dataLength, position } = deserializeInteger(delta, position)); 691 | ({ value: instructionsLength, position } = deserializeInteger(delta, position)); 692 | ({ value: addressesLength, position } = deserializeInteger(delta, position)); 693 | 694 | let dataNextPosition = position + dataLength; 695 | let data = delta.slice(position, dataNextPosition); 696 | 697 | let instructionsNextPosition = dataNextPosition + instructionsLength; 698 | let instructions = delta.slice(dataNextPosition, instructionsNextPosition); 699 | let deserializedInstructions = tokenizeInstructions(instructions); 700 | 701 | let addressesNextPosition = instructionsNextPosition + addressesLength; 702 | let addresses = delta.slice(instructionsNextPosition, addressesNextPosition); 703 | 704 | position = addressesNextPosition; 705 | 706 | let window = { 707 | targetWindowLength, 708 | position, 709 | data, 710 | instructions: deserializedInstructions, 711 | addresses 712 | }; 713 | 714 | return window; 715 | } 716 | 717 | module.exports = delta; 718 | 719 | 720 | 721 | /***/ }), 722 | /* 11 */ 723 | /***/ (function(module, exports, __webpack_require__) { 724 | 725 | 'use strict'; 726 | 727 | const instructions = __webpack_require__(12); 728 | const deserializeInteger = __webpack_require__(9); 729 | 730 | function tokenizeInstructions(instructionsBuffer) { 731 | let deserializedInstructions = []; 732 | 733 | let instructionsPosition = 0; 734 | 735 | while (instructionsPosition < instructionsBuffer.length) { 736 | let index = instructionsBuffer[instructionsPosition++]; 737 | 738 | let addSize, copySize, size; 739 | 740 | if (index === 0) { 741 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 742 | deserializedInstructions.push(new instructions.RUN(size)); 743 | } 744 | else if (index === 1) { 745 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 746 | deserializedInstructions.push(new instructions.ADD(size)); 747 | } 748 | else if (index < 19) { 749 | deserializedInstructions.push(new instructions.ADD(index - 1)); 750 | } 751 | else if (index === 19) { 752 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 753 | deserializedInstructions.push(new instructions.COPY(size, 0)); 754 | } 755 | else if (index < 35) { 756 | deserializedInstructions.push(new instructions.COPY(index - 16, 0)); 757 | } 758 | else if (index === 35) { 759 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 760 | deserializedInstructions.push(new instructions.COPY(size, 1)); 761 | } 762 | else if (index < 51) { 763 | deserializedInstructions.push(new instructions.COPY(index - 32, 1)); 764 | } 765 | else if (index === 51) { 766 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 767 | deserializedInstructions.push(new instructions.COPY(size, 2)); 768 | } 769 | else if (index < 67) { 770 | deserializedInstructions.push(new instructions.COPY(index - 48, 2)); 771 | } 772 | else if (index === 67) { 773 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 774 | deserializedInstructions.push(new instructions.COPY(size, 3)); 775 | } 776 | else if (index < 83) { 777 | deserializedInstructions.push(new instructions.COPY(index - 64, 3)); 778 | } 779 | else if (index === 83) { 780 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 781 | deserializedInstructions.push(new instructions.COPY(size, 4)); 782 | } 783 | else if (index < 99) { 784 | deserializedInstructions.push(new instructions.COPY(index - 80, 4)); 785 | } 786 | else if (index === 99) { 787 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 788 | deserializedInstructions.push(new instructions.COPY(size, 5)); 789 | } 790 | else if (index < 115) { 791 | deserializedInstructions.push(new instructions.COPY(index - 96, 5)); 792 | } 793 | else if (index === 115) { 794 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 795 | deserializedInstructions.push(new instructions.COPY(size, 6)); 796 | } 797 | else if (index < 131) { 798 | deserializedInstructions.push(new instructions.COPY(index - 112, 6)); 799 | } 800 | else if (index === 131) { 801 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 802 | deserializedInstructions.push(new instructions.COPY(size, 7)); 803 | } 804 | else if (index < 147) { 805 | deserializedInstructions.push(new instructions.COPY(index - 127, 7)); 806 | } 807 | else if (index === 147) { 808 | ({ value: size, position: instructionsPosition } = deserializeInteger(instructionsBuffer, instructionsPosition)); 809 | deserializedInstructions.push(new instructions.COPY(size, 8)); 810 | } 811 | else if (index < 163) { 812 | deserializedInstructions.push(new instructions.COPY(index - 144, 8)); 813 | } 814 | else if (index < 175) { 815 | ({addSize, copySize} = ADD_COPY(index, 163)); 816 | 817 | deserializedInstructions.push(new instructions.ADD(addSize)); 818 | deserializedInstructions.push(new instructions.COPY(copySize, 0)); 819 | } 820 | else if (index < 187) { 821 | ({addSize, copySize} = ADD_COPY(index, 175)); 822 | 823 | deserializedInstructions.push(new instructions.ADD(addSize)); 824 | deserializedInstructions.push(new instructions.COPY(copySize, 1)); 825 | } 826 | else if (index < 199) { 827 | ({addSize, copySize} = ADD_COPY(index, 187)); 828 | 829 | deserializedInstructions.push(new instructions.ADD(addSize)); 830 | deserializedInstructions.push(new instructions.COPY(copySize, 2)); 831 | } 832 | else if (index < 211) { 833 | ({addSize, copySize} = ADD_COPY(index, 199)); 834 | 835 | deserializedInstructions.push(new instructions.ADD(addSize)); 836 | deserializedInstructions.push(new instructions.COPY(copySize, 3)); 837 | } 838 | else if (index < 223) { 839 | ({addSize, copySize} = ADD_COPY(index, 211)); 840 | 841 | deserializedInstructions.push(new instructions.ADD(addSize)); 842 | deserializedInstructions.push(new instructions.COPY(copySize, 4)); 843 | } 844 | else if (index < 235) { 845 | ({addSize, copySize} = ADD_COPY(index, 223)); 846 | 847 | deserializedInstructions.push(new instructions.ADD(addSize)); 848 | deserializedInstructions.push(new instructions.COPY(copySize, 5)); 849 | } 850 | else if (index < 239) { 851 | deserializedInstructions.push(new instructions.ADD(index - 235 + 1)); 852 | deserializedInstructions.push(new instructions.COPY(4, 6)); 853 | } 854 | else if (index < 243) { 855 | deserializedInstructions.push(new instructions.ADD(index - 239 + 1)); 856 | deserializedInstructions.push(new instructions.COPY(4, 7)); 857 | } 858 | else if (index < 247) { 859 | deserializedInstructions.push(new instructions.ADD(index - 243 + 1)); 860 | deserializedInstructions.push(new instructions.COPY(4, 8)); 861 | } 862 | else if (index < 256) { 863 | deserializedInstructions.push(new instructions.COPY(4, index - 247)); 864 | deserializedInstructions.push(new instructions.ADD(1)); 865 | } 866 | else { 867 | throw new Error('Should not get here'); 868 | } 869 | } 870 | 871 | return deserializedInstructions; 872 | } 873 | 874 | function ADD_COPY(index, baseIndex) { 875 | let zeroBased = index - baseIndex; 876 | 877 | // 0,1,2 -> 0 3,4,5 -> 1 etc. 878 | let addSizeIndex = Math.floor(zeroBased / 3); 879 | // offset so size starts at 1 880 | let addSize = addSizeIndex + 1; 881 | 882 | // rotate through 0, 1, and 2 883 | let copySizeIndex = zeroBased % 3; 884 | // offset so size starts at 4 885 | let copySize = copySizeIndex + 4; 886 | 887 | return [addSize, copySize]; 888 | } 889 | 890 | module.exports = tokenizeInstructions; 891 | 892 | /***/ }), 893 | /* 12 */ 894 | /***/ (function(module, exports, __webpack_require__) { 895 | 896 | 'use strict'; 897 | 898 | const deserializeInteger = __webpack_require__(9); 899 | const TypedArray = __webpack_require__(8); 900 | 901 | function ADD(size) { 902 | this.size = size; 903 | } 904 | function COPY(size, mode) { 905 | this.size = size; 906 | this.mode = mode; 907 | } 908 | function RUN(size) { 909 | this.size = size; 910 | } 911 | 912 | ADD.prototype.name = 'ADD'; 913 | COPY.prototype.name = 'COPY'; 914 | RUN.prototype.name = 'RUN'; 915 | 916 | ADD.prototype.execute = function(delta) { 917 | for (let i = 0; i < this.size; i++) { 918 | delta.U.set(delta.UTargetPosition + i, delta.data[delta.dataPosition + i]); 919 | } 920 | delta.dataPosition += this.size; 921 | delta.UTargetPosition += this.size; 922 | }; 923 | 924 | COPY.prototype.execute = function(delta) { 925 | let address, m, next, method; 926 | 927 | if (this.mode === 0) { 928 | address = delta.getNextAddressInteger(); 929 | } 930 | else if (this.mode === 1) { 931 | next = delta.getNextAddressInteger(); 932 | address = delta.UTargetPosition - next; 933 | } 934 | else if ((m = this.mode - 2) >= 0 && (m < delta.nearCache.size)) { 935 | next = delta.getNextAddressInteger(); 936 | address = delta.nearCache.get(m, next); 937 | method = 'near'; 938 | } 939 | // same cache 940 | else { 941 | m = this.mode - (2 + delta.nearCache.size); 942 | next = delta.getNextAddressByte(); 943 | address = delta.sameCache.get(m, next); 944 | method = 'same'; 945 | } 946 | 947 | delta.nearCache.update(address); 948 | delta.sameCache.update(address); 949 | 950 | for (let i = 0; i < this.size; i++) { 951 | delta.U.set(delta.UTargetPosition + i, delta.U.get(address + i)); 952 | } 953 | 954 | delta.UTargetPosition += this.size; 955 | }; 956 | 957 | RUN.prototype.execute = function(delta) { 958 | for (let i = 0; i < this.size; i++) { 959 | // repeat single byte 960 | delta.U[delta.UTargetPosition + i] = delta.data[delta.dataPosition]; 961 | } 962 | // increment to next byte 963 | delta.dataPosition++; 964 | delta.UTargetPosition += this.size; 965 | }; 966 | 967 | let instructions = { 968 | ADD, 969 | COPY, 970 | RUN 971 | }; 972 | 973 | module.exports = instructions; 974 | 975 | /***/ }), 976 | /* 13 */ 977 | /***/ (function(module, exports) { 978 | 979 | 'use strict'; 980 | 981 | function NearCache(size) { 982 | this.size = size; 983 | this.near = new Array(this.size).fill(0); 984 | this.nextSlot = 0; 985 | } 986 | 987 | NearCache.prototype.update = function(address) { 988 | if (this.near.length > 0) { 989 | this.near[this.nextSlot] = address; 990 | this.nextSlot = (this.nextSlot + 1) % this.near.length; 991 | } 992 | }; 993 | 994 | NearCache.prototype.get = function(m, offset) { 995 | let address = this.near[m] + offset; 996 | return address; 997 | }; 998 | 999 | module.exports = NearCache; 1000 | 1001 | /***/ }), 1002 | /* 14 */ 1003 | /***/ (function(module, exports) { 1004 | 1005 | 'use strict'; 1006 | 1007 | function SameCache(size) { 1008 | this.size = size; 1009 | this.same = new Array(this.size * 256).fill(0); 1010 | } 1011 | 1012 | SameCache.prototype.update = function(address) { 1013 | if (this.same.length > 0) { 1014 | this.same[address % (this.size * 256)] = address; 1015 | } 1016 | }; 1017 | 1018 | SameCache.prototype.get = function(m, offset) { 1019 | let address = this.same[m * 256 + offset]; 1020 | return address; 1021 | }; 1022 | 1023 | module.exports = SameCache; 1024 | 1025 | /***/ }) 1026 | /******/ ]); --------------------------------------------------------------------------------