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

Commit

Permalink
feat: implement service worker example (#3374)
Browse files Browse the repository at this point in the history
Adds example demonstrating how to use IPFS node in shared worker from service worker.

Co-authored-by: Alex Potsides <[email protected]>
  • Loading branch information
Gozala and achingbrain authored Nov 27, 2020
1 parent 56890c7 commit 384312f
Show file tree
Hide file tree
Showing 12 changed files with 1,005 additions and 0 deletions.
92 changes: 92 additions & 0 deletions browser-service-worker/README.md
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
Binary file added browser-service-worker/index-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 119 additions & 0 deletions browser-service-worker/index.html
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>
37 changes: 37 additions & 0 deletions browser-service-worker/package.json
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"
]
}
Binary file added browser-service-worker/page-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions browser-service-worker/src/main.js
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()
Loading

0 comments on commit 384312f

Please sign in to comment.