├── .gitignore ├── LICENSE ├── README.md ├── examples └── use-from-another-page │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public+sw │ ├── css │ │ └── styles.css │ ├── index.html │ └── index.js │ ├── public │ ├── css │ │ └── styles.css │ ├── index.html │ └── index.js │ ├── sw │ └── index.js │ └── webpack.config.js ├── package-lock.json ├── package.json └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | examples/use-from-another-page/public+sw/bundle.js 39 | examples/use-from-another-page/public+sw/service-worker-bundle.js 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 IPFS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > 3 | > # ⛔️ DEPRECATED 4 | > 5 | > _This example is out of date and not be maintained anymore._ 6 | > 7 | > This is an old PoC from 2017-2018. 8 | > 9 | > See the modern end-to-end example of using Helia in `ServiceWorker` at 10 | 11 | --- 12 | 13 | 14 | # Demo: use `js-ipfs` within a Service Worker 15 | 16 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) 17 | [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) 18 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) 19 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 20 | 28 | 29 | > Run an IPFS node inside a service worker and serve all your IPFS URLs directly from IPFS! 30 | 31 | ## BEWARE BEWARE there may be dragons! 🐉 32 | 33 | This module is still experimental because it is indeed an experiment part of the [ipfs/in-web-browsers](https://github.com/ipfs/in-web-browsers) endeavour. 34 | 35 | Follow latest developments in [in-web-browsers/issues/55](https://github.com/ipfs/in-web-browsers/issues/55). 36 | 37 | ## Usage 38 | 39 | This project is a how to, that you can use as a basis for building an application with `js-ipfs` (`>=0.33.1`) running in a service worker. 40 | 41 | Note, the following code snippets are edited from the actual source code for brevity. 42 | 43 | ### Service worker code 44 | 45 | The service worker code lives in `src/index.js`. This is the code that will run as a service worker. It boots up an IPFS node, responds to requests and exposes the running node for use by web pages within the scope of the service worker. 46 | 47 | The IPFS node is created when the service worker ['activate' event](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onactivate) is fired: 48 | 49 | ```js 50 | const IPFS = require('ipfs') 51 | 52 | self.addEventListener('activate', () => { 53 | ipfs = new IPFS({ /* ...config... */ }) 54 | }) 55 | ``` 56 | 57 | The service worker listens for ['fetch' events](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) so that it can [respond](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) to requests: 58 | 59 | ```js 60 | self.addEventListener('fetch', (event) => { 61 | const hash = event.request.url.split('/ipfs/')[1] 62 | event.respondWith(catAndRespond(hash)) 63 | }) 64 | 65 | async function catAndRespond (hash) { 66 | const data = await ipfs.files.cat(hash) 67 | return new Response(data, { status: 200, statusText: 'OK', headers: {} }) 68 | } 69 | ``` 70 | 71 | Finally, the IPFS node is exposed for use by web pages/apps. Service workers are permitted to talk to web pages via a messaging API so we can use [`ipfs-postmsg-proxy`](https://github.com/tableflip/ipfs-postmsg-proxy) to talk to the IPFS node running in the worker. We create a "proxy server" for this purpose: 72 | 73 | ```js 74 | const { createProxyServer } = require('ipfs-postmsg-proxy') 75 | // Setup a proxy server that talks to our real IPFS node 76 | createProxyServer(() => ipfs, { /* ...config... */ }) 77 | ``` 78 | 79 | ### Application code 80 | 81 | The application code lives in `examples/use-from-another-page/public+sw`. It registers the service worker and talks to the IPFS node that runs in it. 82 | 83 | First we feature detect that the client's browser supports service workers, and then we [register the service worker](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register). 84 | 85 | ```js 86 | if ('serviceWorker' in navigator) { 87 | navigator.serviceWorker.register('service-worker-bundle.js') 88 | .then((reg) => console.log('Successful service worker register')) 89 | .catch((err) => console.error('Failed service worker register', err)) 90 | } 91 | ``` 92 | 93 | Once the service worker is registered, we can start talking to the IPFS node that it is running. To do this we create a "proxy client" which can talk to our "proxy server" over the messaging API: 94 | 95 | ```js 96 | if ('serviceWorker' in navigator) { 97 | navigator.serviceWorker.register('service-worker-bundle.js') 98 | .then(async () => { 99 | ipfs = createProxyClient({ /* ...config... */ }) 100 | 101 | // Now use `ipfs` as usual! e.g. 102 | const { agentVersion, id } = await ipfs.id() 103 | }) 104 | } 105 | ``` 106 | -------------------------------------------------------------------------------- /examples/use-from-another-page/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /examples/use-from-another-page/README.md: -------------------------------------------------------------------------------- 1 | # Learn how to use the IPFS Service Worker from another page! 2 | 3 | This directory contains two examples: 4 | 5 | - `public` - loads image from local HTTP gateway 6 | - `public+sw` - loads image from js-ipfs running in service worker 7 | 8 | How to play with it? 9 | 10 | 1. `npm ci` 11 | 2. `npm run build` 12 | 3. `npm start` 13 | - Open http://127.0.0.1:9042/public/ 14 | - Open http://127.0.0.1:9042/public%2Bsw/ 15 | -------------------------------------------------------------------------------- /examples/use-from-another-page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-from-another-page", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "http-server -p 9042 .", 10 | "build": "webpack" 11 | }, 12 | "author": "David Dias ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "ipfs-postmsg-proxy": "^3.1.1" 16 | }, 17 | "devDependencies": { 18 | "http-server": "^0.11.1", 19 | "standard": "^12.0.1", 20 | "webpack": "^4.25.1", 21 | "webpack-cli": "3.1.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public+sw/css/styles.css: -------------------------------------------------------------------------------- 1 | #container { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | justify-content: space-around; 6 | } 7 | 8 | li { 9 | margin-bottom: 1em; 10 | } 11 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public+sw/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

js-ipfs in a Service Worker

5 |

Sample image CID: QmSGfXuqtuGX5FLdBEH5hRQJc6ZrZWV6rqExLXjfpsihV2

6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public+sw/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { createProxyClient } = require('ipfs-postmsg-proxy') 4 | let node 5 | 6 | if ('serviceWorker' in navigator) { 7 | navigator.serviceWorker.register('service-worker-bundle.js') 8 | .then((registration) => { 9 | console.log('-> Registered the service worker successfuly') 10 | 11 | node = createProxyClient({ 12 | addListener: navigator.serviceWorker.addEventListener.bind(navigator.serviceWorker), 13 | removeListener: navigator.serviceWorker.removeEventListener.bind(navigator.serviceWorker), 14 | postMessage: (data) => navigator.serviceWorker.controller.postMessage(data) 15 | }) 16 | }) 17 | .catch((err) => { 18 | console.log('-> Failed to register:', err) 19 | }) 20 | } 21 | 22 | document.querySelector('#id').addEventListener('click', async () => { 23 | if (!node) return alert('Service worker not registered') 24 | const { agentVersion, id } = await node.id() 25 | alert(`${agentVersion} ${id}`) 26 | }) 27 | 28 | document.querySelector('#show').addEventListener('click', () => { 29 | const multihash = document.querySelector('#input').value 30 | let imgElement = document.createElement('img') 31 | 32 | // imgElement.src = multihash 33 | imgElement.src = '/ipfs/' + multihash 34 | // imgElement.src = 'https://ipfs.io/ipfs/' + multihash 35 | document.querySelector('#display').appendChild(imgElement) 36 | }) 37 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public/css/styles.css: -------------------------------------------------------------------------------- 1 | #container { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | justify-content: space-around; 6 | } 7 | 8 | li { 9 | margin-bottom: 1em; 10 | } 11 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

No js-ipfs nor service worker here!

5 |

Image will be loaded from local gateway at http://127.0.0.1:8080/ipfs/

6 |

Sample image CID: QmSGfXuqtuGX5FLdBEH5hRQJc6ZrZWV6rqExLXjfpsihV2

7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/use-from-another-page/public/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | document.querySelector('#show').addEventListener('click', () => { 4 | const multihash = document.querySelector('#input').value 5 | let imgElement = document.createElement('img') 6 | 7 | imgElement.src = 'http://localhost:8080' + '/ipfs/' + multihash 8 | document.querySelector('#display').appendChild(imgElement) 9 | }) 10 | -------------------------------------------------------------------------------- /examples/use-from-another-page/sw/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('../../../src') // replace in your own code by: require('ipfs-service-worker') 4 | -------------------------------------------------------------------------------- /examples/use-from-another-page/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 3 | module.exports = { 4 | mode: 'development', // use only during development 5 | devtool: 'inline-source-map', // use only during development 6 | entry: { 7 | 'service-worker-bundle': './sw/index.js', 8 | bundle: './public+sw/index.js' 9 | }, 10 | optimization: { 11 | minimizer: [ 12 | // Default flags break js-ipfs: https://github.com/ipfs-shipyard/ipfs-companion/issues/521 13 | new UglifyJsPlugin({ 14 | parallel: true, 15 | extractComments: true, 16 | uglifyOptions: { 17 | compress: { unused: false }, 18 | mangle: true 19 | } 20 | }) 21 | ] 22 | }, 23 | output: { 24 | path: path.resolve(__dirname), 25 | filename: 'public+sw/[name].js' 26 | }, 27 | node: { 28 | fs: 'empty', 29 | net: 'empty', 30 | tls: 'empty' 31 | }, 32 | resolve: { 33 | alias: { 34 | zlib: 'browserify-zlib-next' 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-service-worker", 3 | "version": "0.0.0", 4 | "description": "Run an IPFS node inside a service worker and serve all your IPFS Urls directly from IPFS!", 5 | "main": "src/index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ipfs/ipfs-service-worker.git" 13 | }, 14 | "keywords": [ 15 | "IPFS", 16 | "Service", 17 | "Worker", 18 | "Distributed" 19 | ], 20 | "author": "David Dias ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/ipfs/ipfs-service-worker/issues" 24 | }, 25 | "homepage": "https://github.com/ipfs/ipfs-service-worker#readme", 26 | "dependencies": { 27 | "ipfs": "^0.33.1", 28 | "ipfs-postmsg-proxy": "^3.1.1" 29 | }, 30 | "devDependencies": { 31 | "standard": "^12.0.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* global self, Response */ 2 | 3 | 'use strict' 4 | 5 | const IPFS = require('ipfs') 6 | const { createProxyServer } = require('ipfs-postmsg-proxy') 7 | 8 | let node 9 | 10 | self.addEventListener('install', (event) => { 11 | console.log('install step') 12 | 13 | event.waitUntil(self.skipWaiting()) 14 | }) 15 | 16 | self.addEventListener('activate', (event) => { 17 | console.log('activate step') 18 | 19 | node = new IPFS({ 20 | config: { 21 | Addresses: { 22 | Swarm: [] 23 | } 24 | } 25 | }) 26 | node.on('ready', () => console.log('js-ipfs node is ready')) 27 | node.on('error', (err) => console.log('js-ipfs node errored', err)) 28 | 29 | event.waitUntil(self.clients.claim()) 30 | }) 31 | 32 | self.addEventListener('fetch', (event) => { 33 | if (!event.request.url.startsWith(self.location.origin + '/ipfs')) { 34 | return console.log('Fetch not in scope', event.request.url) 35 | } 36 | 37 | console.log('Handling fetch event for', event.request.url) 38 | 39 | const multihash = event.request.url.split('/ipfs/')[1] 40 | event.respondWith(catAndRespond(multihash)) 41 | }) 42 | 43 | async function catAndRespond (hash) { 44 | const data = await node.files.cat(hash) 45 | const headers = { status: 200, statusText: 'OK', headers: {} } 46 | return new Response(data, headers) 47 | } 48 | 49 | createProxyServer(() => node, { 50 | addListener: self.addEventListener.bind(self), 51 | removeListener: self.removeEventListener.bind(self), 52 | async postMessage (data) { 53 | // TODO: post back to the client that sent the message? 54 | const clients = await self.clients.matchAll() 55 | clients.forEach(client => client.postMessage(data)) 56 | } 57 | }) 58 | --------------------------------------------------------------------------------