Repository: bahmutov/bottle-service
Branch: master
Commit: b44a23fbedcc
Files: 14
Total size: 27.5 KB
Directory structure:
gitextract_eg55b8px/
├── .gitignore
├── .npmrc
├── .travis.yml
├── Procfile
├── README.md
├── deploy.json
├── dist/
│ ├── app.css
│ ├── bottle-service.js
│ ├── bottle.js
│ └── index.html
├── package.json
├── src/
│ ├── bottle-service.js
│ └── bottle.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules/
.grunt/
.DS_Store
npm-debug.log
================================================
FILE: .npmrc
================================================
registry=http://registry.npmjs.org/
save-exact=true
================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- '4'
before_install:
- npm i -g npm@^2.0.0
before_script:
- npm prune
after_success:
- npm run semantic-release
branches:
except:
- "/^v\\d+\\.\\d+\\.\\d+$/"
================================================
FILE: Procfile
================================================
web: npm start
================================================
FILE: README.md
================================================
# bottle-service
> Instant web applications restored from ServiceWorker cache
[![NPM][bottle-service-icon] ][bottle-service-url]
[![Build status][bottle-service-ci-image] ][bottle-service-ci-url]
[![semantic-release][semantic-image] ][semantic-url]
[Live demo](https://glebbahmutov.com/bottle-service/) - please use Chrome or Opera Desktop
[Instant app demo](https://instant-todo.herokuapp.com/) - TodoMVC that is instant on
page reload, hosted on free Heroku dyno, and needs Chrome browser for now

## Browser support
### Chrome
* Nothing to do, `ServiceWorker` should be enabled by default
### Firefox
* Open `about:config`
* Set the `dom.serviceWorkers.enabled` setting to **true**
* Set the `dom.serviceWorkers.interception.enabled` setting to **true**
## Api
This library attaches itself as `bottleService` object to the 'window'. Every time
you want to store HTML snapshot for an element with id 'myApp', call
```js
// "my-app-name" will be used in the future to allow separate parts of the
// page to be saved separately
// <div id="myApp"> // controlled by the web app </div>
// application has been rendered
bottleService.refill('my-app-name', 'myApp')
```
There are a couple of secondary api calls
```js
bottleService.clear('my-app-name'); // delete whatever is stored in the ServiceWorker cache
bottleService.print('my-app-name'); // prints the stored text to console
```
## Example
See [dist/index.html](dist/index.html) that includes the "application" code.
Every time the user clicks "Add item" button, the application code adds a new DOM node,
then tells the bottle service to store the new snapshot
```js
var applicationName = 'bottle-demo'
document.getElementById('add').addEventListener('click', function () {
var el = document.getElementById('app')
var div = document.createElement('div')
var text = document.createTextNode('hi there')
div.appendChild(text)
el.appendChild(div)
// store HTML snapshot
bottleService.refill(applicationName, 'app')
})
```
When the page loads, the ServiceWorker will intercept `index.html` and will insert
the saved HTML snapshot into the page before returning it back to the browser for rendering.
Thus there is no page rewriting on load, no flicker, etc.
## Related
* Instant web apps without page flicker or loading screens,
[blog post](http://glebbahmutov.com/blog/instant-web-application/),
[source repo](https://github.com/bahmutov/instant-vdom-todo)
* Dynamic page rewriting on start [hydrate-vue-todo](https://github.com/bahmutov/hydrate-vue-todo)
* Fast application start from pre-rendered HTML
[hydrate-vdom-todo](https://github.com/bahmutov/hydrate-vdom-todo)
## Related projects using ServiceWorkers
* [express-service](https://github.com/bahmutov/express-service) - run ExpressJS server
inside ServiceWorker
* [service-turtle](https://github.com/bahmutov/service-turtle) - Flexible http request
interception using ServiceWorker
### Small print
Author: Gleb Bahmutov © 2015
* [@bahmutov](https://twitter.com/bahmutov)
* [glebbahmutov.com](http://glebbahmutov.com)
* [blog](http://glebbahmutov.com/blog/)
License: MIT - do anything with the code, but don't blame me if it does not work.
Spread the word: tweet, star on github, etc.
Support: if you find any problems with this module, email / tweet /
[open issue](https://github.com/bahmutov/bottle-service/issues) on Github
## MIT License
Copyright (c) 2015 Gleb Bahmutov
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[bottle-service-icon]: https://nodei.co/npm/bottle-service.png?downloads=true
[bottle-service-url]: https://npmjs.org/package/bottle-service
[bottle-service-ci-image]: https://travis-ci.org/bahmutov/bottle-service.png?branch=master
[bottle-service-ci-url]: https://travis-ci.org/bahmutov/bottle-service
[semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-url]: https://github.com/semantic-release/semantic-release
================================================
FILE: deploy.json
================================================
{
"gh-pages": {
"options": {
"base": "dist"
},
"src": [
"index.html",
"*.js",
"*.css"
]
}
}
================================================
FILE: dist/app.css
================================================
.hidden {
visibility: hidden;
display: none;
}
body {
padding: 2em;
font-family: Didot, 'Didot LT STD', 'Hoefler Text', 'Garamond', 'Times New Roman', serif;
font-size: larger;
color: #333333;
}
button {
padding: 5px 5px;
background-color: white;
font-size: large;
}
a {
text-decoration: none;
font-family: monospace;
font-size: large;
}
.controls {
margin-top: 1em;
padding: 1em 1em;
border: 1px solid #dddddd;
border-radius: 5px;
background-color: #fefefe;
}
================================================
FILE: dist/bottle-service.js
================================================
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict'
/*
This is ServiceWorker code
*/
/* global self, Response, Promise, location, fetch */
var myName = 'bottle-service'
console.log(myName, 'startup')
function dataStore () {
var cachesStorage = __webpack_require__(1)
return cachesStorage(myName)
}
self.addEventListener('install', function (event) {
console.log(myName, 'installed')
})
self.addEventListener('activate', function () {
console.log(myName, 'activated')
})
var baseHref = location.href.substr(0, location.href.indexOf('bottle-service.js'))
function isIndexPageRequest (event) {
return event &&
event.request &&
event.request.url === baseHref
}
self.addEventListener('fetch', function (event) {
if (!isIndexPageRequest(event)) {
return fetch(event.request)
}
console.log(myName, 'fetching index page', event.request.url)
event.respondWith(
fetch(event.request)
.then(function (response) {
return dataStore()
.then(function (store) {
return store.getItem('contents')
})
.then(function (contents) {
if (contents && contents.html && contents.id) {
console.log('fetched latest', response.url, 'need to update')
console.log('element "%s" with html "%s" ...',
contents.id, contents.html.substr(0, 15))
var copy = response.clone()
return copy.text().then(function (pageHtml) {
console.log('inserting our html')
// HACK using id in the CLOSING TAG to find fragment
var toReplaceStart = '<div id="' + contents.id + '">'
var toReplaceFinish = '</div id="' + contents.id + '">'
var startIndex = pageHtml.indexOf(toReplaceStart)
var finishIndex = pageHtml.indexOf(toReplaceFinish)
if (startIndex !== -1 && finishIndex > startIndex) {
console.log('found fragment')
pageHtml = pageHtml.substr(0, startIndex + toReplaceStart.length) +
'\n' + contents.html + '\n' +
pageHtml.substr(finishIndex)
}
// console.log('page html')
// console.log(pageHtml)
var responseOptions = {
status: 200,
headers: {
'Content-Type': 'text/html charset=UTF-8'
}
}
return new Response(pageHtml, responseOptions)
})
} else {
return response
}
}, function notFound () {
return response
})
})
)
})
// use window.navigator.serviceWorker.controller.postMessage('hi')
// to communicate with this service worker
self.onmessage = function onMessage (event) {
console.log('message to bottle-service worker cmd', event.data && event.data.cmd)
// TODO how to use application name?
dataStore().then(function (store) {
switch (event.data.cmd) {
case 'print': {
return store.getItem('contents')
.then(function (res) {
console.log('bottle service has contents')
console.log(res)
})
}
case 'clear': {
console.log('clearing the bottle')
return store.setItem('contents', {})
}
case 'refill': {
return store.setItem('contents', {
html: event.data.html,
id: event.data.id
}).then(function () {
console.log('saved new html for id', event.data.id)
})
}
default: {
console.error(myName, 'unknown command', event.data)
}
}
})
}
/***/ },
/* 1 */
/***/ function(module, exports) {
// Poor man's async "localStorage" on top of Cache
// https://developer.mozilla.org/en-US/docs/Web/API/Cache
if (typeof caches === 'undefined') {
throw new Error('Cannot find object caches?! Cannot init cache-storage')
}
/* global caches, Response */
function dataStore (name) {
var id = name ? name + '-v1' : 'cache-storage-v1'
return caches.open(id)
.then(function (cache) {
return {
setItem: function (key, data) {
return cache.put(key, new Response(JSON.stringify(data)))
},
getItem: function (key) {
return cache.match(key)
.then(function (res) {
return res &&
res.text().then(JSON.parse)
})
}
}
})
}
module.exports = dataStore
/***/ }
/******/ ]);
================================================
FILE: dist/bottle.js
================================================
!(function startBottleService (root) {
'use strict'
if (!root.navigator) {
console.error('Missing navigator')
return
}
if (!root.navigator.serviceWorker) {
console.error('Sorry, not ServiceWorker feature, maybe enable it?')
console.error('http://jakearchibald.com/2014/using-serviceworker-today/')
return
}
// TODO package lazy-ass and check-more-types using webpack
function toString (x) {
return typeof x === 'string' ? x : JSON.stringify(x)
}
function la (condition) {
if (!condition) {
var args = Array.prototype.slice.call(arguments, 1)
.map(toString)
throw new Error(args.join(' '))
}
}
function isFunction (f) {
return typeof f === 'function'
}
function getCurrentScriptFolder () {
var scriptEls = document.getElementsByTagName('script')
var thisScriptEl = scriptEls[scriptEls.length - 1]
var scriptPath = thisScriptEl.src
return scriptPath.substr(0, scriptPath.lastIndexOf('/') + 1)
}
var serviceScriptUrl = getCurrentScriptFolder() + 'bottle-service.js'
// assume we are running at <domain>/pathname
var scope = window.location.pathname
var send = function mockSend () {
console.error('Bottle service not initialized yet')
}
function registeredWorker (registration) {
la(registration, 'missing service worker registration')
la(registration.active, 'missing active service worker')
la(isFunction(registration.active.postMessage),
'expected function postMessage to communicate with service worker')
send = registration.active.postMessage.bind(registration.active)
var info = '\nbottle-service - .\n' +
'I have a valid service-turtle, use `bottleService` object to update cached page'
console.log(info)
registration.active.onmessage = function messageFromServiceWorker (e) {
console.log('received message from the service worker', e)
}
}
function onError (err) {
if (err.message.indexOf('missing active') !== -1) {
// the service worker is installed
window.location.reload()
} else {
console.error('bottle service error', err)
}
}
root.navigator.serviceWorker.register(serviceScriptUrl, { scope: scope })
.then(registeredWorker)
.catch(onError)
root.bottleService = {
refill: function refill (applicationName, id) {
console.log('bottle-service: html for app %s element %s', applicationName, id)
var el = document.getElementById(id)
la(el, 'could not find element with id', id)
var html = el.innerHTML.trim()
send({
cmd: 'refill',
html: html,
name: applicationName,
id: id
})
},
print: function print (applicationName) {
send({
cmd: 'print',
name: applicationName
})
},
clear: function clear (applicationName) {
send({
cmd: 'clear',
name: applicationName
})
}
}
}(window))
================================================
FILE: dist/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>bottle-service</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
<a href="http://github.com/bahmutov/bottle-service" class="github-corner"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#70B7FD; color:#fff; position: absolute; top: 0; border: 0; right: 0;"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
<h1>bottle-service (tested on Chrome Desktop only)</h1>
<p>This page can rewrite itself inside a ServiceWorker behind this website.
Try adding a couple of DOM nodes using a button below, then reload the page.
Notice that the page <strong>arrives with new DOM nodes</strong> to the browser,
thus there is no blinking / initial pause. If you inspect the fetched page's source
in the Network tab you will see the updated HTML on each reload.</p>
<p>The self-rewriting portion is below</p>
<!-- terrible hack to quickly remove inner contents - set the id on the closing tag -->
<div id="app">some content here already</div id="app">
<div class="controls">
Controls
<button id="add">Add new DOM node to the list</button>
<button id="print">Print current bottle contents</button>
<button id="clear">Clear cached HTML</button>
<ul>
<li>"Add" - inserts a new item into the rewritable portion of this page. Stores the dynamic
HTML portion in the ServiceWorker for the future reload</li>
<li>"Print" - prints what is currently stored for the rewritable portion of the page in the
ServiceWorker (open browser console to view)</li>
<li>"Clear" - clears the stored HTML from the ServiceWorker storage</li>
</ul>
</div>
<div class="instructions">
<h3>How to use</h3>
<p>Load the page first - I assume you have already done this, if you are reading this</p>
<p>Click "Add ..." button several times, you should see new items in the list.</p>
<p>Reload the page - you should see the items you have added right away - they are part
of the loaded page, <strong>not added dynamically later</strong>. You can confirm
this by inspecting the downloaded page's source or the contents of the page in the Network tab.</p>
<h4>Note</h4>
<p>Open browser console to see any messages / errors</p>
</div>
<script src="bottle.js"></script>
<script>
var applicationName = 'bottle-demo'
console.log('page code for', applicationName)
document.getElementById('add').addEventListener('click', function () {
console.log('adding item to the list')
var el = document.getElementById('app')
var div = document.createElement('div')
var text = document.createTextNode('hi there')
div.appendChild(text)
el.appendChild(div)
bottleService.refill(applicationName, 'app')
})
document.getElementById('print')
.addEventListener('click', function () {
console.log('open browser console to see the cached HTML source');
bottleService.print(applicationName)
})
document.getElementById('clear')
.addEventListener('click', function () {
var el = document.getElementById('app')
el.innerHTML = ''
bottleService.clear(applicationName)
})
</script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "bottle-service",
"description": "Instant web applications restored from ServiceWorker cache",
"main": "index.js",
"version": "0.0.0-semantic-release",
"files": [
"dist/*.js"
],
"scripts": {
"test": "npm run lint",
"lint": "standard src/*.js",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"build": "cp src/bottle.js dist/ && npm run webpack",
"webpack": "webpack",
"deploy": "grunty grunt-gh-pages gh-pages deploy.json",
"start": "http-server dist -c-1",
"dev-start": "http-server dist -c-1 -p 3006",
"commit": "commit-wizard",
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
"issues": "git-issues"
},
"repository": {
"type": "git",
"url": "https://github.com/bahmutov/bottle-service.git"
},
"keywords": [
"service",
"worker",
"serviceWorker",
"cache",
"hydrate",
"web",
"performance"
],
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/bahmutov/bottle-service/issues"
},
"homepage": "https://github.com/bahmutov/bottle-service#readme",
"dependencies": {
"caches-storage": "1.1.0",
"http-server": "0.8.5"
},
"devDependencies": {
"git-issues": "1.2.0",
"grunt": "0.4.5",
"grunt-gh-pages": "1.0.0",
"grunty": "0.2.0",
"pre-git": "3.1.1",
"semantic-release": "^4.3.5",
"standard": "5.4.1",
"webpack": "1.12.9"
},
"private": false,
"config": {
"pre-git": {
"commit-msg": [
"simple"
],
"pre-commit": [
"npm run lint"
],
"pre-push": [
"npm run size"
],
"post-commit": [],
"post-merge": []
}
}
}
================================================
FILE: src/bottle-service.js
================================================
'use strict'
/*
This is ServiceWorker code
*/
/* global self, Response, Promise, location, fetch */
var myName = 'bottle-service'
console.log(myName, 'startup')
function dataStore () {
var cachesStorage = require('caches-storage')
return cachesStorage(myName)
}
self.addEventListener('install', function (event) {
console.log(myName, 'installed')
})
self.addEventListener('activate', function () {
console.log(myName, 'activated')
})
var baseHref = location.href.substr(0, location.href.indexOf('bottle-service.js'))
function isIndexPageRequest (event) {
return event &&
event.request &&
event.request.url === baseHref
}
self.addEventListener('fetch', function (event) {
if (!isIndexPageRequest(event)) {
return fetch(event.request)
}
console.log(myName, 'fetching index page', event.request.url)
event.respondWith(
fetch(event.request)
.then(function (response) {
return dataStore()
.then(function (store) {
return store.getItem('contents')
})
.then(function (contents) {
if (contents && contents.html && contents.id) {
console.log('fetched latest', response.url, 'need to update')
console.log('element "%s" with html "%s" ...',
contents.id, contents.html.substr(0, 15))
var copy = response.clone()
return copy.text().then(function (pageHtml) {
console.log('inserting our html')
// HACK using id in the CLOSING TAG to find fragment
var toReplaceStart = '<div id="' + contents.id + '">'
var toReplaceFinish = '</div id="' + contents.id + '">'
var startIndex = pageHtml.indexOf(toReplaceStart)
var finishIndex = pageHtml.indexOf(toReplaceFinish)
if (startIndex !== -1 && finishIndex > startIndex) {
console.log('found fragment')
pageHtml = pageHtml.substr(0, startIndex + toReplaceStart.length) +
'\n' + contents.html + '\n' +
pageHtml.substr(finishIndex)
}
// console.log('page html')
// console.log(pageHtml)
var responseOptions = {
status: 200,
headers: {
'Content-Type': 'text/html charset=UTF-8'
}
}
return new Response(pageHtml, responseOptions)
})
} else {
return response
}
}, function notFound () {
return response
})
})
)
})
// use window.navigator.serviceWorker.controller.postMessage('hi')
// to communicate with this service worker
self.onmessage = function onMessage (event) {
console.log('message to bottle-service worker cmd', event.data && event.data.cmd)
// TODO how to use application name?
dataStore().then(function (store) {
switch (event.data.cmd) {
case 'print': {
return store.getItem('contents')
.then(function (res) {
console.log('bottle service has contents')
console.log(res)
})
}
case 'clear': {
console.log('clearing the bottle')
return store.setItem('contents', {})
}
case 'refill': {
return store.setItem('contents', {
html: event.data.html,
id: event.data.id
}).then(function () {
console.log('saved new html for id', event.data.id)
})
}
default: {
console.error(myName, 'unknown command', event.data)
}
}
})
}
================================================
FILE: src/bottle.js
================================================
!(function startBottleService (root) {
'use strict'
if (!root.navigator) {
console.error('Missing navigator')
return
}
if (!root.navigator.serviceWorker) {
console.error('Sorry, not ServiceWorker feature, maybe enable it?')
console.error('http://jakearchibald.com/2014/using-serviceworker-today/')
return
}
// TODO package lazy-ass and check-more-types using webpack
function toString (x) {
return typeof x === 'string' ? x : JSON.stringify(x)
}
function la (condition) {
if (!condition) {
var args = Array.prototype.slice.call(arguments, 1)
.map(toString)
throw new Error(args.join(' '))
}
}
function isFunction (f) {
return typeof f === 'function'
}
function getCurrentScriptFolder () {
var scriptEls = document.getElementsByTagName('script')
var thisScriptEl = scriptEls[scriptEls.length - 1]
var scriptPath = thisScriptEl.src
return scriptPath.substr(0, scriptPath.lastIndexOf('/') + 1)
}
var serviceScriptUrl = getCurrentScriptFolder() + 'bottle-service.js'
// assume we are running at <domain>/pathname
var scope = window.location.pathname
var send = function mockSend () {
console.error('Bottle service not initialized yet')
}
function registeredWorker (registration) {
la(registration, 'missing service worker registration')
la(registration.active, 'missing active service worker')
la(isFunction(registration.active.postMessage),
'expected function postMessage to communicate with service worker')
send = registration.active.postMessage.bind(registration.active)
var info = '\nbottle-service - .\n' +
'I have a valid service-turtle, use `bottleService` object to update cached page'
console.log(info)
registration.active.onmessage = function messageFromServiceWorker (e) {
console.log('received message from the service worker', e)
}
}
function onError (err) {
if (err.message.indexOf('missing active') !== -1) {
// the service worker is installed
window.location.reload()
} else {
console.error('bottle service error', err)
}
}
root.navigator.serviceWorker.register(serviceScriptUrl, { scope: scope })
.then(registeredWorker)
.catch(onError)
root.bottleService = {
refill: function refill (applicationName, id) {
console.log('bottle-service: html for app %s element %s', applicationName, id)
var el = document.getElementById(id)
la(el, 'could not find element with id', id)
var html = el.innerHTML.trim()
send({
cmd: 'refill',
html: html,
name: applicationName,
id: id
})
},
print: function print (applicationName) {
send({
cmd: 'print',
name: applicationName
})
},
clear: function clear (applicationName) {
send({
cmd: 'clear',
name: applicationName
})
}
}
}(window))
================================================
FILE: webpack.config.js
================================================
module.exports = {
output: {
path: './dist',
filename: 'bottle-service.js'
},
entry: {
library: './src/bottle-service'
}
}
gitextract_eg55b8px/ ├── .gitignore ├── .npmrc ├── .travis.yml ├── Procfile ├── README.md ├── deploy.json ├── dist/ │ ├── app.css │ ├── bottle-service.js │ ├── bottle.js │ └── index.html ├── package.json ├── src/ │ ├── bottle-service.js │ └── bottle.js └── webpack.config.js
SYMBOL INDEX (18 symbols across 4 files)
FILE: dist/bottle-service.js
function __webpack_require__ (line 6) | function __webpack_require__(moduleId) {
function dataStore (line 56) | function dataStore () {
function isIndexPageRequest (line 70) | function isIndexPageRequest (event) {
function dataStore (line 178) | function dataStore (name) {
FILE: dist/bottle.js
function toString (line 17) | function toString (x) {
function la (line 21) | function la (condition) {
function isFunction (line 29) | function isFunction (f) {
function getCurrentScriptFolder (line 33) | function getCurrentScriptFolder () {
function registeredWorker (line 48) | function registeredWorker (registration) {
function onError (line 64) | function onError (err) {
FILE: src/bottle-service.js
function dataStore (line 10) | function dataStore () {
function isIndexPageRequest (line 24) | function isIndexPageRequest (event) {
FILE: src/bottle.js
function toString (line 17) | function toString (x) {
function la (line 21) | function la (condition) {
function isFunction (line 29) | function isFunction (f) {
function getCurrentScriptFolder (line 33) | function getCurrentScriptFolder () {
function registeredWorker (line 48) | function registeredWorker (registration) {
function onError (line 64) | function onError (err) {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (30K chars).
[
{
"path": ".gitignore",
"chars": 46,
"preview": "node_modules/\n.grunt/\n.DS_Store\nnpm-debug.log\n"
},
{
"path": ".npmrc",
"chars": 52,
"preview": "registry=http://registry.npmjs.org/\nsave-exact=true\n"
},
{
"path": ".travis.yml",
"chars": 283,
"preview": "sudo: false\nlanguage: node_js\ncache:\n directories:\n - node_modules\nnotifications:\n email: false\nnode_js:\n - '4'\nbe"
},
{
"path": "Procfile",
"chars": 15,
"preview": "web: npm start\n"
},
{
"path": "README.md",
"chars": 4982,
"preview": "# bottle-service\n> Instant web applications restored from ServiceWorker cache\n\n[![NPM][bottle-service-icon] ][bottle-ser"
},
{
"path": "deploy.json",
"chars": 136,
"preview": "{\n \"gh-pages\": {\n \"options\": {\n \"base\": \"dist\"\n },\n \"src\": [\n \"index.html\",\n \"*.js\",\n \"*.c"
},
{
"path": "dist/app.css",
"chars": 495,
"preview": ".hidden {\n visibility: hidden;\n display: none;\n}\nbody {\n padding: 2em;\n font-family: Didot, 'Didot LT STD', 'Hoefler"
},
{
"path": "dist/bottle-service.js",
"chars": 6032,
"preview": "/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n\n/*"
},
{
"path": "dist/bottle.js",
"chars": 2958,
"preview": "!(function startBottleService (root) {\n 'use strict'\n\n if (!root.navigator) {\n console.error('Missing navigator')\n "
},
{
"path": "dist/index.html",
"chars": 4581,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "package.json",
"chars": 1784,
"preview": "{\n \"name\": \"bottle-service\",\n \"description\": \"Instant web applications restored from ServiceWorker cache\",\n \"main\": \""
},
{
"path": "src/bottle-service.js",
"chars": 3677,
"preview": "'use strict'\n\n/*\n This is ServiceWorker code\n*/\n/* global self, Response, Promise, location, fetch */\nvar myName = 'bot"
},
{
"path": "src/bottle.js",
"chars": 2958,
"preview": "!(function startBottleService (root) {\n 'use strict'\n\n if (!root.navigator) {\n console.error('Missing navigator')\n "
},
{
"path": "webpack.config.js",
"chars": 143,
"preview": "module.exports = {\n output: {\n path: './dist',\n filename: 'bottle-service.js'\n },\n entry: {\n library: './src"
}
]
About this extraction
This page contains the full source code of the bahmutov/bottle-service GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (27.5 KB), approximately 7.5k tokens, and a symbol index with 18 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.