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

Commit 1b8b1b8

Browse files
Gozalalidelachingbrain
authored
feat: share IPFS node between browser tabs (#3081)
This pull request adds 3 (sub)packages: 1. `ipfs-message-port-client` - Provides an API to an IPFS node over the [message channel][MessageChannel]. 2. `ipfs-message-port-server` - Provides an IPFS node over [message channel][MessageChannel]. 3. `ipfs-message-port-protocol` - Shared code between client / server mostly related to wire protocol encoding / decoding. Fixes #3022 [MessageChannel]:https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel Co-authored-by: Marcin Rataj <lidel@lidel.org> Co-authored-by: Alex Potsides <alex@achingbrain.net>
1 parent 09735ca commit 1b8b1b8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+5330
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Sharing js-ipfs node across browsing contexts (tabs) using [SharedWorker][]
2+
3+
> In this example, you will find a boilerplate you can use to set up a js-ipfs
4+
> node in the [SharedWorker] and use it from multiple tabs.
5+
6+
## Before you start
7+
8+
First clone this repo, install dependencies in the project root and build the project.
9+
10+
```bash
11+
git clone https://github.com/ipfs/js-ipfs.git
12+
cd js-ipfs/examples/browser-sharing-node-across-tabs
13+
npm install
14+
```
15+
16+
## Running the example
17+
18+
Run the following command within this folder:
19+
20+
```bash
21+
npm start
22+
```
23+
24+
Now open your browser at `http://localhost:3000`
25+
26+
You should see the following:
27+
28+
![Screen Shot](./Screen Shot.png)
29+
30+
31+
### Run tests
32+
33+
```bash
34+
npm test
35+
```
36+
37+
38+
[SharedWorker]:https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<html>
2+
3+
<head>
4+
<title>Sample App</title>
5+
</head>
6+
7+
<body>
8+
<div id='root'></div>
9+
<script src="/static/bundle.js"></script>
10+
</body>
11+
12+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "expample-browser-sharing-node-across-tabs",
3+
"description": "Sharing IPFS node across browsing contexts",
4+
"version": "1.0.0",
5+
"private": true,
6+
"scripts": {
7+
"clean": "rm -rf ./dist",
8+
"build": "webpack",
9+
"start": "node server.js",
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": "^4.43.0",
21+
"webpack-cli": "^3.3.11",
22+
"webpack-dev-server": "^3.11.0",
23+
"worker-plugin": "4.0.3"
24+
},
25+
"dependencies": {
26+
"ipfs": "^0.47.0",
27+
"ipfs-message-port-client": "^0.0.1",
28+
"ipfs-message-port-server": "^0.0.1"
29+
},
30+
"browserslist": [
31+
">1%",
32+
"not dead",
33+
"not ie <= 11",
34+
"not op_mini all"
35+
]
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const webpack = require('webpack')
4+
const WebpackDevServer = require('webpack-dev-server')
5+
const config = require('./webpack.config')
6+
7+
const wds = new WebpackDevServer(webpack(config), {
8+
hot: true,
9+
historyApiFallback: true
10+
})
11+
12+
wds.listen(3000, 'localhost', (err) => {
13+
if (err) {
14+
throw err
15+
}
16+
17+
console.log('Listening at localhost:3000')
18+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict'
2+
3+
import IPFSClient from "ipfs-message-port-client"
4+
5+
6+
const main = async () => {
7+
// connect / spawn shared ipfs worker & create a client.
8+
const worker = new SharedWorker('./worker.js', { type: 'module' })
9+
const ipfs = IPFSClient.from(worker.port)
10+
11+
const path = location.hash.slice(1)
12+
if (path.startsWith('/ipfs/')) {
13+
await viewer(ipfs, path)
14+
} else {
15+
await uploader(ipfs)
16+
}
17+
}
18+
19+
const uploader = async (ipfs) => {
20+
document.body.outerHTML += '<div>Adding "hello world!" to shared IPFS node</div>'
21+
const entry = await ipfs.add(ipfs, new Blob(['hello world!'], { type: "text/plain" }))
22+
const path = `/ipfs/${entry.cid}/`
23+
document.body.outerHTML += `<div class="ipfs-add">File was added:
24+
<a target="_blank" href="${new URL(`#${path}`, location)}">${path}</a>
25+
</div>`
26+
}
27+
28+
const viewer = async (ipfs, path) => {
29+
document.body.outerHTML += `<div class="loading">Loading ${path}</div>`
30+
try {
31+
const chunks = []
32+
for await (const chunk of await ipfs.cat(path)) {
33+
chunks.push(chunk)
34+
}
35+
const blob = new Blob(chunks)
36+
const url = URL.createObjectURL(blob)
37+
document.body.outerHTML +=
38+
`<iframe id="content" sandbox src=${url} style="background:white;top:0;left:0;border:0;width:100%;height:100%;position:absolute;z-index:2;"></iframe>`
39+
40+
} catch(error) {
41+
document.body.outerHTML += `<div class="error">${error}</div>`
42+
}
43+
}
44+
45+
onload = main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict'
2+
3+
import IPFS from 'ipfs'
4+
import { Server, IPFSService } from 'ipfs-message-port-server'
5+
6+
const main = async () => {
7+
// start listening to all the incoming connections (browsing contexts that
8+
// which run new SharedWorker...)
9+
// Note: It is important to start listening before we do any await to ensure
10+
// that connections aren't missed while awaiting.
11+
const connections = listen(self, 'connect')
12+
13+
// Start an IPFS node & create server that will expose it's API to all clients
14+
// over message channel.
15+
const ipfs = await IPFS.create()
16+
const service = new IPFSService(ipfs)
17+
const server = new Server(service)
18+
19+
// connect every queued and future connection to the server.
20+
for await (const event of connections) {
21+
const port = event.ports[0]
22+
if (port) {
23+
server.connect(port)
24+
}
25+
}
26+
}
27+
28+
/**
29+
* Creates an AsyncIterable<Event> for all the events on the given `target` for
30+
* the given event `type`. It is like `target.addEventListener(type, listener, options)`
31+
* but instead of passing listener you get `AsyncIterable<Event>` instead.
32+
* @param {EventTarget} target
33+
* @param {string} type
34+
* @param {AddEventListenerOptions} options
35+
*/
36+
const listen = function (target, type, options) {
37+
const events = []
38+
let resume
39+
let ready = new Promise(resolve => (resume = resolve))
40+
41+
const write = event => {
42+
events.push(event)
43+
resume()
44+
}
45+
const read = async () => {
46+
await ready
47+
ready = new Promise(resolve => (resume = resolve))
48+
return events.splice(0)
49+
}
50+
51+
const reader = async function * () {
52+
try {
53+
while (true) {
54+
yield * await read()
55+
}
56+
} finally {
57+
target.removeEventListener(type, write, options)
58+
}
59+
}
60+
61+
target.addEventListener(type, write, options)
62+
return reader()
63+
}
64+
65+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict'
2+
3+
const pkg = require('./package.json')
4+
5+
module.exports = {
6+
[pkg.name]: (browser) => {
7+
browser
8+
.url(process.env.IPFS_EXAMPLE_TEST_URL)
9+
.waitForElementVisible('.ipfs-add')
10+
11+
browser.expect.element('.ipfs-add a').text.to.contain('/ipfs/')
12+
browser.click('.ipfs-add a')
13+
14+
browser.windowHandle(({ value }) => {
15+
browser.windowHandles(({ value: handles }) => {
16+
const [handle] = handles.filter(handle => handle != value)
17+
browser.switchWindow(handle)
18+
})
19+
})
20+
21+
browser.waitForElementVisible('.loading')
22+
browser.expect.element('.loading').text.to.contain('Loading /ipfs/')
23+
24+
browser.waitForElementVisible('#content').pause(5000)
25+
browser.element('css selector', '#content', frame => {
26+
browser.frame({ ELEMENT: frame.value.ELEMENT }, () => {
27+
browser.waitForElementPresent('body')
28+
browser.expect.element('body').text.to.contain('hello world!')
29+
browser.end()
30+
})
31+
})
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict'
2+
3+
var path = require('path')
4+
var webpack = require('webpack')
5+
const WorkerPlugin = require('worker-plugin')
6+
7+
module.exports = {
8+
devtool: 'source-map',
9+
entry: [
10+
'webpack-dev-server/client?http://localhost:3000',
11+
'webpack/hot/only-dev-server',
12+
'./src/main'
13+
],
14+
output: {
15+
path: path.join(__dirname, 'dist'),
16+
filename: 'static/bundle.js'
17+
},
18+
plugins: [
19+
new WorkerPlugin({
20+
sharedWorker: true,
21+
globalObject: 'self'
22+
}),
23+
new webpack.HotModuleReplacementPlugin()
24+
],
25+
module: {
26+
rules: [
27+
{
28+
test: /\.js$/,
29+
exclude: /node_modules/,
30+
use: {
31+
loader: 'babel-loader',
32+
options: {
33+
presets: ['@babel/preset-env']
34+
}
35+
}
36+
}
37+
]
38+
},
39+
node: {
40+
fs: 'empty',
41+
net: 'empty',
42+
tls: 'empty'
43+
}
44+
}

examples/traverse-ipld-graphs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"dependencies": {
1616
"cids": "^0.8.3",
1717
"ipfs": "^0.48.0",
18-
"ipld-block": "^0.9.1",
18+
"ipld-block": "^0.9.2",
1919
"ipld-dag-pb": "^0.19.0",
2020
"multihashing-async": "^1.0.0"
2121
}

packages/interface-ipfs-core/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
"ipfs-unixfs": "^1.0.3",
4343
"ipfs-unixfs-importer": "^2.0.2",
4444
"ipfs-utils": "^2.2.2",
45-
"ipld-block": "^0.9.1",
46-
"ipld-dag-cbor": "^0.15.2",
45+
"ipld-block": "^0.9.2",
46+
"ipld-dag-cbor": "^0.15.3",
4747
"ipld-dag-pb": "^0.19.0",
4848
"is-ipfs": "^1.0.3",
4949
"iso-random-stream": "^1.1.1",

packages/interface-ipfs-core/src/object/links.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ module.exports = (common, options) => {
5959
const node1bCid = await ipfs.object.put(node1b)
6060

6161
const links = await ipfs.object.links(node1bCid)
62-
expect(links).to.be.an('array').that.has.property('length', 1)
63-
expect(node1b.Links).to.be.deep.equal(links)
62+
63+
expect(links).to.have.lengthOf(1)
64+
expect(node1b.Links).to.deep.equal(links)
6465
})
6566

6667
it('should get links by base58 encoded multihash', async () => {

packages/ipfs-http-client/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
"form-data": "^3.0.0",
5050
"ipfs-core-utils": "^0.3.0",
5151
"ipfs-utils": "^2.2.2",
52-
"ipld-block": "^0.9.1",
53-
"ipld-dag-cbor": "^0.15.2",
52+
"ipld-block": "^0.9.2",
53+
"ipld-dag-cbor": "^0.15.3",
5454
"ipld-dag-pb": "^0.19.0",
5555
"ipld-raw": "^5.0.0",
5656
"iso-url": "^0.4.7",
@@ -67,7 +67,7 @@
6767
"nanoid": "^3.0.2",
6868
"node-fetch": "^2.6.0",
6969
"parse-duration": "^0.4.4",
70-
"stream-to-it": "^0.2.0"
70+
"stream-to-it": "^0.2.1"
7171
},
7272
"devDependencies": {
7373
"aegir": "^23.0.0",

0 commit comments

Comments
 (0)