This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement service worker example (#3374)
Adds example demonstrating how to use IPFS node in shared worker from service worker. Co-authored-by: Alex Potsides <[email protected]>
- Loading branch information
1 parent
56890c7
commit 384312f
Showing
12 changed files
with
1,005 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Using js-ipfs node in [SharedWorker][] from [ServiceWorker][] | ||
|
||
> In this example, you will find boilerplate code you can use to set up an IPFS | ||
> node in a [SharedWorker][] and use it from a [ServiceWorker][]. | ||
## General Overview | ||
|
||
### `src/main.js` | ||
|
||
Module is loaded in the main thread (DOM window) and is responsible for wiring | ||
all the pieces together: | ||
|
||
1. Activates a [SharedWorker][] that runs an IPFS node. | ||
2. Registers a [ServiceWorker][] to serve IPFS content from. | ||
3. Listens to [MessagePort][] requests from the [ServiceWorker][] and responds | ||
back with a [MessagePort][] of the [SharedWorker][], enabling | ||
it to interact with shaerd IPFS node. | ||
|
||
### `src/worker.js` | ||
|
||
Module is loaded in the [SharedWorker][]. It demonstrates how to setup the IPFS | ||
node such that it can be used in other browsing contexts. | ||
|
||
### `src/service.js` | ||
|
||
Module is loaded in the [ServiceWorker][] and responds to all the requests from | ||
the page. It recognizes four different request routes: | ||
|
||
1. Routes `/ipfs/...`, `/ipns/...` are served html pages that: | ||
|
||
- Contain a full page iframe that has an `src` derived from request path e.g.: | ||
|
||
``` | ||
/ipfs/Qm...hash/file/name -> /view/Qm...hash/file/name | ||
``` | ||
- `src/main.js` script loaded in it. | ||
|
||
This way when request from `/view/Qm..hash/file/name` arrives [ServiceWorker][] | ||
can obtain a [MessagePort][] for the [SharedWorker][] by requesting it from | ||
the iframe container. | ||
|
||
2. Routes `/view/ipfs/...` and are served corresponding content from IPFS. On | ||
such request message is send to an iframe container (That is why `/ipfs/...` | ||
and `/ipns/...` routes served `iframe` and `src/main.js`), through which | ||
[MessagePort][] for the [SharedWorker][] is obtained and used to retrieve | ||
content from the shared IPFS node and served back. | ||
|
||
> There is a stub for `/view/ipns/...` route, which is left as an excercise | ||
> for the reader to fill. | ||
3. All other routes are served by fetchging it from the network. | ||
|
||
|
||
## Before you start | ||
|
||
First clone this repo, cd into the example directory and install the dependencies | ||
|
||
```bash | ||
git clone https://github.com/ipfs/js-ipfs.git | ||
cd js-ipfs/examples/browser-service-worker | ||
npm install | ||
``` | ||
|
||
## Running the example | ||
|
||
Run the following command within this folder: | ||
|
||
```bash | ||
npm start | ||
``` | ||
|
||
Now open your browser at `http://localhost:3000` | ||
|
||
You should see the following: | ||
|
||
![Screen Shot](./index-view.png) | ||
|
||
If you navigate to the following address `http://localhost:3000/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/` it should load a | ||
page from ipfs and appear as: | ||
|
||
![Screen Shot](./page-view.png) | ||
|
||
### Run tests | ||
|
||
```bash | ||
npm test | ||
``` | ||
|
||
|
||
[SharedWorker]:https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker | ||
[ServiceWorker]:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API | ||
[MessagePort]:https://developer.mozilla.org/en-US/docs/Web/API/MessagePort |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<html> | ||
|
||
<head> | ||
<title>IPFS Viewer</title> | ||
<style> | ||
@media (prefers-color-scheme: dark) { | ||
|
||
a, | ||
body { | ||
background: #000; | ||
color: white; | ||
} | ||
} | ||
|
||
@media (prefers-color-scheme: light) { | ||
|
||
a, | ||
body { | ||
background: white; | ||
color: #000; | ||
} | ||
} | ||
|
||
|
||
body { | ||
font-family: -apple-system, BlinkMacSystemFont, | ||
'avenir next', avenir, | ||
'helvetica neue', helvetica, | ||
ubuntu, | ||
roboto, noto, | ||
'segoe ui', arial, | ||
sans-serif; | ||
} | ||
|
||
a { | ||
text-decoration: none; | ||
} | ||
|
||
a:hover { | ||
text-decoration: underline; | ||
} | ||
|
||
.dt { | ||
display: table; | ||
} | ||
|
||
.dtc { | ||
display: table-cell; | ||
} | ||
|
||
.fw6 { | ||
font-weight: 600; | ||
} | ||
|
||
.vh-100 { | ||
height: 100vh; | ||
} | ||
|
||
.w-100 { | ||
width: 100%; | ||
} | ||
|
||
.white { | ||
color: #fff; | ||
} | ||
|
||
.bg-dark-pink { | ||
background-color: #d5008f; | ||
} | ||
|
||
.ph3 { | ||
padding-left: 1rem; | ||
padding-right: 1rem; | ||
} | ||
|
||
.tc { | ||
text-align: center; | ||
} | ||
|
||
.f6 { | ||
font-size: .875rem; | ||
} | ||
|
||
.v-mid { | ||
vertical-align: middle; | ||
} | ||
|
||
@media screen and (min-width: 30em) and (max-width: 60em) { | ||
.f2-m { | ||
font-size: 2.25rem; | ||
} | ||
} | ||
|
||
@media screen and (min-width: 60em) { | ||
.ph4-l { | ||
padding-left: 2rem; | ||
padding-right: 2rem; | ||
} | ||
|
||
.f-subheadline-l { | ||
font-size: 5rem; | ||
} | ||
} | ||
</style> | ||
<script src="/main.js"></script> | ||
</head> | ||
|
||
<body> | ||
<article class="vh-100 dt w-100"> | ||
<div class="dtc v-mid tc ph3 ph4-l"> | ||
<h1 class="f6 f2-m f-subheadline-l fw6 tc">Load content by adding IPFS path to the URL ⤴</h2> | ||
<p>Something like <a | ||
href="/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/">/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/</a> | ||
</p> | ||
</div> | ||
</center> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "example-browser-service-worker", | ||
"description": "IPFS with service worker", | ||
"version": "1.0.0", | ||
"private": true, | ||
"scripts": { | ||
"clean": "rm -rf ./dist", | ||
"build": "webpack", | ||
"start": "webpack-dev-server", | ||
"test": "test-ipfs-example" | ||
}, | ||
"license": "MIT", | ||
"keywords": [], | ||
"devDependencies": { | ||
"@babel/core": "^7.2.2", | ||
"@babel/preset-env": "^7.3.1", | ||
"babel-loader": "^8.0.5", | ||
"copy-webpack-plugin": "^5.0.4", | ||
"test-ipfs-example": "^2.0.3", | ||
"webpack": "5.4.0", | ||
"webpack-cli": "4.1.0", | ||
"webpack-dev-server": "3.11.0" | ||
}, | ||
"dependencies": { | ||
"ipfs": "^0.51.0", | ||
"ipfs-message-port-client": "^0.3.0", | ||
"ipfs-message-port-protocol": "^0.3.0", | ||
"ipfs-message-port-server": "^0.3.0", | ||
"process": "0.11.10" | ||
}, | ||
"browserslist": [ | ||
">1%", | ||
"not dead", | ||
"not ie <= 11", | ||
"not op_mini all" | ||
] | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
'use strict' | ||
|
||
// This is an entry point to our program. | ||
const main = async () => { | ||
// We start a shared worker where IPFS node is loaded. | ||
const worker = createIPFSWorker() | ||
// @ts-ignore - Store worker in the window so that it's available in console. | ||
window.worker = createIPFSWorker() | ||
|
||
// Service workers do not have access to the `SharedWorker` API | ||
// (see https://github.com/w3c/ServiceWorker/issues/678) | ||
// To overcome that limitation the page will listen for the service worker message | ||
// and provide it with a message port to the shared worker, which will enable | ||
// it to use our (shared) IPFS node. | ||
navigator.serviceWorker.onmessage = onServiceWorkerMessage | ||
|
||
// @ts-ignore - register expects string but weback requires this URL hack. | ||
await navigator.serviceWorker.register(new URL('./service.js', import.meta.url), { scope: '/' }) | ||
|
||
await navigator.serviceWorker.ready | ||
|
||
// This is just for testing, lets us know when SW is ready. | ||
const meta = document.createElement("meta") | ||
meta.name = "sw-ready" | ||
document.head.appendChild(meta) | ||
|
||
// URLs like `localhost:3000/ipfs/Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD` | ||
// are loaded from service worker. However it could be that such a URL is loaded | ||
// before the service worker was registered in which case our server just loads a blank | ||
// page (that doesn't have data-viewer attribute). If that is the case we load | ||
// the actual IPFS content after the SW is ready. | ||
if (document.documentElement.dataset.viewer == null) { | ||
load(location.pathname) | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} path | ||
*/ | ||
const load = async (path) => { | ||
const [,protocol] = path.split('/') | ||
switch (protocol) { | ||
case 'ipfs': | ||
case 'ipns': { | ||
document.body.innerHTML = `<iframe id="viewer" style="width:100%;height:100%;position:fixed;top:0;left:0;border:none;" src="/view${path}"></iframe>` | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Handles ipfs message port request from service worker and | ||
* responds to it with it. | ||
* | ||
* @param {MessageEvent} event | ||
*/ | ||
const onServiceWorkerMessage = (event) => { | ||
/** @type {null|ServiceWorker} */ | ||
const serviceWorker = (event.source) | ||
if (serviceWorker == null) return | ||
switch (event.data.method) { | ||
case 'ipfs-message-port': { | ||
// Receives request from service worker, creates a new shared worker and | ||
// responds back with the message port. | ||
// Note: MessagePort can be transferred only once which is why we need to | ||
// create a SharedWorker each time. However a ServiceWorker is only created | ||
// once (in main function) all other creations just create port to it. | ||
const worker = createIPFSWorker() | ||
return serviceWorker.postMessage({ | ||
method: 'ipfs-message-port', | ||
id: event.data.id, | ||
port: worker.port | ||
}, [worker.port]) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Creates a shared worker instance that exposes JS-IPFS node over MessagePort. | ||
* @returns {SharedWorker} | ||
*/ | ||
const createIPFSWorker = () => new SharedWorker( | ||
// @ts-ignore - Constructor takes string but webpack needs URL | ||
new URL('./worker.js', import.meta.url), | ||
'IPFS' | ||
) | ||
|
||
main() |
Oops, something went wrong.