Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit 384312f

Browse files
Gozalaachingbrain
andauthored
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]>
1 parent 56890c7 commit 384312f

File tree

12 files changed

+1005
-0
lines changed

12 files changed

+1005
-0
lines changed

browser-service-worker/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Using js-ipfs node in [SharedWorker][] from [ServiceWorker][]
2+
3+
> In this example, you will find boilerplate code you can use to set up an IPFS
4+
> node in a [SharedWorker][] and use it from a [ServiceWorker][].
5+
6+
## General Overview
7+
8+
### `src/main.js`
9+
10+
Module is loaded in the main thread (DOM window) and is responsible for wiring
11+
all the pieces together:
12+
13+
1. Activates a [SharedWorker][] that runs an IPFS node.
14+
2. Registers a [ServiceWorker][] to serve IPFS content from.
15+
3. Listens to [MessagePort][] requests from the [ServiceWorker][] and responds
16+
back with a [MessagePort][] of the [SharedWorker][], enabling
17+
it to interact with shaerd IPFS node.
18+
19+
### `src/worker.js`
20+
21+
Module is loaded in the [SharedWorker][]. It demonstrates how to setup the IPFS
22+
node such that it can be used in other browsing contexts.
23+
24+
### `src/service.js`
25+
26+
Module is loaded in the [ServiceWorker][] and responds to all the requests from
27+
the page. It recognizes four different request routes:
28+
29+
1. Routes `/ipfs/...`, `/ipns/...` are served html pages that:
30+
31+
- Contain a full page iframe that has an `src` derived from request path e.g.:
32+
33+
```
34+
/ipfs/Qm...hash/file/name -> /view/Qm...hash/file/name
35+
```
36+
- `src/main.js` script loaded in it.
37+
38+
This way when request from `/view/Qm..hash/file/name` arrives [ServiceWorker][]
39+
can obtain a [MessagePort][] for the [SharedWorker][] by requesting it from
40+
the iframe container.
41+
42+
2. Routes `/view/ipfs/...` and are served corresponding content from IPFS. On
43+
such request message is send to an iframe container (That is why `/ipfs/...`
44+
and `/ipns/...` routes served `iframe` and `src/main.js`), through which
45+
[MessagePort][] for the [SharedWorker][] is obtained and used to retrieve
46+
content from the shared IPFS node and served back.
47+
48+
> There is a stub for `/view/ipns/...` route, which is left as an excercise
49+
> for the reader to fill.
50+
51+
3. All other routes are served by fetchging it from the network.
52+
53+
54+
## Before you start
55+
56+
First clone this repo, cd into the example directory and install the dependencies
57+
58+
```bash
59+
git clone https://github.com/ipfs/js-ipfs.git
60+
cd js-ipfs/examples/browser-service-worker
61+
npm install
62+
```
63+
64+
## Running the example
65+
66+
Run the following command within this folder:
67+
68+
```bash
69+
npm start
70+
```
71+
72+
Now open your browser at `http://localhost:3000`
73+
74+
You should see the following:
75+
76+
![Screen Shot](./index-view.png)
77+
78+
If you navigate to the following address `http://localhost:3000/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/` it should load a
79+
page from ipfs and appear as:
80+
81+
![Screen Shot](./page-view.png)
82+
83+
### Run tests
84+
85+
```bash
86+
npm test
87+
```
88+
89+
90+
[SharedWorker]:https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker
91+
[ServiceWorker]:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
92+
[MessagePort]:https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
100 KB
Loading

browser-service-worker/index.html

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<html>
2+
3+
<head>
4+
<title>IPFS Viewer</title>
5+
<style>
6+
@media (prefers-color-scheme: dark) {
7+
8+
a,
9+
body {
10+
background: #000;
11+
color: white;
12+
}
13+
}
14+
15+
@media (prefers-color-scheme: light) {
16+
17+
a,
18+
body {
19+
background: white;
20+
color: #000;
21+
}
22+
}
23+
24+
25+
body {
26+
font-family: -apple-system, BlinkMacSystemFont,
27+
'avenir next', avenir,
28+
'helvetica neue', helvetica,
29+
ubuntu,
30+
roboto, noto,
31+
'segoe ui', arial,
32+
sans-serif;
33+
}
34+
35+
a {
36+
text-decoration: none;
37+
}
38+
39+
a:hover {
40+
text-decoration: underline;
41+
}
42+
43+
.dt {
44+
display: table;
45+
}
46+
47+
.dtc {
48+
display: table-cell;
49+
}
50+
51+
.fw6 {
52+
font-weight: 600;
53+
}
54+
55+
.vh-100 {
56+
height: 100vh;
57+
}
58+
59+
.w-100 {
60+
width: 100%;
61+
}
62+
63+
.white {
64+
color: #fff;
65+
}
66+
67+
.bg-dark-pink {
68+
background-color: #d5008f;
69+
}
70+
71+
.ph3 {
72+
padding-left: 1rem;
73+
padding-right: 1rem;
74+
}
75+
76+
.tc {
77+
text-align: center;
78+
}
79+
80+
.f6 {
81+
font-size: .875rem;
82+
}
83+
84+
.v-mid {
85+
vertical-align: middle;
86+
}
87+
88+
@media screen and (min-width: 30em) and (max-width: 60em) {
89+
.f2-m {
90+
font-size: 2.25rem;
91+
}
92+
}
93+
94+
@media screen and (min-width: 60em) {
95+
.ph4-l {
96+
padding-left: 2rem;
97+
padding-right: 2rem;
98+
}
99+
100+
.f-subheadline-l {
101+
font-size: 5rem;
102+
}
103+
}
104+
</style>
105+
<script src="/main.js"></script>
106+
</head>
107+
108+
<body>
109+
<article class="vh-100 dt w-100">
110+
<div class="dtc v-mid tc ph3 ph4-l">
111+
<h1 class="f6 f2-m f-subheadline-l fw6 tc">Load content by adding IPFS path to the URL ⤴</h2>
112+
<p>Something like <a
113+
href="/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/">/ipfs/bafybeicqzoixu6ivztffjy4bktwxy6lxaxkvnavkya7kfgwyhx4bund2ga/</a>
114+
</p>
115+
</div>
116+
</center>
117+
</body>
118+
119+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "example-browser-service-worker",
3+
"description": "IPFS with service worker",
4+
"version": "1.0.0",
5+
"private": true,
6+
"scripts": {
7+
"clean": "rm -rf ./dist",
8+
"build": "webpack",
9+
"start": "webpack-dev-server",
10+
"test": "test-ipfs-example"
11+
},
12+
"license": "MIT",
13+
"keywords": [],
14+
"devDependencies": {
15+
"@babel/core": "^7.2.2",
16+
"@babel/preset-env": "^7.3.1",
17+
"babel-loader": "^8.0.5",
18+
"copy-webpack-plugin": "^5.0.4",
19+
"test-ipfs-example": "^2.0.3",
20+
"webpack": "5.4.0",
21+
"webpack-cli": "4.1.0",
22+
"webpack-dev-server": "3.11.0"
23+
},
24+
"dependencies": {
25+
"ipfs": "^0.51.0",
26+
"ipfs-message-port-client": "^0.3.0",
27+
"ipfs-message-port-protocol": "^0.3.0",
28+
"ipfs-message-port-server": "^0.3.0",
29+
"process": "0.11.10"
30+
},
31+
"browserslist": [
32+
">1%",
33+
"not dead",
34+
"not ie <= 11",
35+
"not op_mini all"
36+
]
37+
}
146 KB
Loading

browser-service-worker/src/main.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict'
2+
3+
// This is an entry point to our program.
4+
const main = async () => {
5+
// We start a shared worker where IPFS node is loaded.
6+
const worker = createIPFSWorker()
7+
// @ts-ignore - Store worker in the window so that it's available in console.
8+
window.worker = createIPFSWorker()
9+
10+
// Service workers do not have access to the `SharedWorker` API
11+
// (see https://github.com/w3c/ServiceWorker/issues/678)
12+
// To overcome that limitation the page will listen for the service worker message
13+
// and provide it with a message port to the shared worker, which will enable
14+
// it to use our (shared) IPFS node.
15+
navigator.serviceWorker.onmessage = onServiceWorkerMessage
16+
17+
// @ts-ignore - register expects string but weback requires this URL hack.
18+
await navigator.serviceWorker.register(new URL('./service.js', import.meta.url), { scope: '/' })
19+
20+
await navigator.serviceWorker.ready
21+
22+
// This is just for testing, lets us know when SW is ready.
23+
const meta = document.createElement("meta")
24+
meta.name = "sw-ready"
25+
document.head.appendChild(meta)
26+
27+
// URLs like `localhost:3000/ipfs/Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD`
28+
// are loaded from service worker. However it could be that such a URL is loaded
29+
// before the service worker was registered in which case our server just loads a blank
30+
// page (that doesn't have data-viewer attribute). If that is the case we load
31+
// the actual IPFS content after the SW is ready.
32+
if (document.documentElement.dataset.viewer == null) {
33+
load(location.pathname)
34+
}
35+
}
36+
37+
/**
38+
* @param {string} path
39+
*/
40+
const load = async (path) => {
41+
const [,protocol] = path.split('/')
42+
switch (protocol) {
43+
case 'ipfs':
44+
case 'ipns': {
45+
document.body.innerHTML = `<iframe id="viewer" style="width:100%;height:100%;position:fixed;top:0;left:0;border:none;" src="/view${path}"></iframe>`
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Handles ipfs message port request from service worker and
52+
* responds to it with it.
53+
*
54+
* @param {MessageEvent} event
55+
*/
56+
const onServiceWorkerMessage = (event) => {
57+
/** @type {null|ServiceWorker} */
58+
const serviceWorker = (event.source)
59+
if (serviceWorker == null) return
60+
switch (event.data.method) {
61+
case 'ipfs-message-port': {
62+
// Receives request from service worker, creates a new shared worker and
63+
// responds back with the message port.
64+
// Note: MessagePort can be transferred only once which is why we need to
65+
// create a SharedWorker each time. However a ServiceWorker is only created
66+
// once (in main function) all other creations just create port to it.
67+
const worker = createIPFSWorker()
68+
return serviceWorker.postMessage({
69+
method: 'ipfs-message-port',
70+
id: event.data.id,
71+
port: worker.port
72+
}, [worker.port])
73+
}
74+
}
75+
}
76+
77+
/**
78+
* Creates a shared worker instance that exposes JS-IPFS node over MessagePort.
79+
* @returns {SharedWorker}
80+
*/
81+
const createIPFSWorker = () => new SharedWorker(
82+
// @ts-ignore - Constructor takes string but webpack needs URL
83+
new URL('./worker.js', import.meta.url),
84+
'IPFS'
85+
)
86+
87+
main()

0 commit comments

Comments
 (0)