This repository has been archived by the owner on Mar 10, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 300
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: browser pubsub example (#1086)
License: MIT Signed-off-by: Alan Shaw <alan.shaw@protocol.ai>
- Loading branch information
Alan Shaw
authored
Aug 29, 2019
1 parent
3515070
commit 69a56cb
Showing
8 changed files
with
327 additions
and
25 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
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 @@ | ||
bundle.js |
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,94 @@ | ||
# Pubsub in the browser | ||
|
||
> Use pubsub in the browser! | ||
This example is a demo web application that allows you to connect to an IPFS node, subscribe to a pubsub topic and send/receive messages. We'll start two IPFS nodes and two browsers and use the `ipfs-http-client` to instruct each node to listen to a pubsub topic and send/receive pubsub messages to/from each other. We're aiming for something like this: | ||
|
||
``` | ||
+-----------+ +-----------+ | ||
| +-------------------> | | ||
| js-ipfs | pubsub | go-ipfs | | ||
| <-------------------+ | | ||
+-----^-----+ +-----^-----+ | ||
| | | ||
| HTTP API | HTTP API | ||
| | | ||
+-------------------+ +-------------------+ | ||
+-------------------+ +-------------------+ | ||
| | | | | ||
| | | | | ||
| Browser 1 | | Browser 2 | | ||
| | | | | ||
| | | | | ||
| | | | | ||
+-------------------+ +-------------------+ | ||
``` | ||
|
||
## 1. Get started | ||
|
||
With Node.js and git installed, clone the repo and install the project dependencies: | ||
|
||
```sh | ||
git clone https://github.com/ipfs/js-ipfs-http-client.git | ||
cd js-ipfs-http-client | ||
npm install # Installs ipfs-http-client dependencies | ||
cd examples/browser-pubsub | ||
npm install # Installs browser-pubsub app dependencies | ||
``` | ||
|
||
Start the example application: | ||
|
||
```sh | ||
npm start | ||
``` | ||
|
||
You should see something similar to the following in your terminal and the web app should now be available if you navigate to http://127.0.0.1:8888 using your browser: | ||
|
||
```sh | ||
Starting up http-server, serving ./ | ||
Available on: | ||
http://127.0.0.1:8888 | ||
``` | ||
|
||
## 2. Start two IPFS nodes | ||
|
||
To demonstrate pubsub we need two nodes running so pubsub messages can be passed between them. | ||
|
||
Right now the easiest way to do this is to install and start a `js-ipfs` and `go-ipfs` node. There are other ways to do this, see [this document on running multiple nodes](https://github.com/ipfs/js-ipfs/tree/master/examples/running-multiple-nodes) for details. | ||
|
||
### Install and start the JS IPFS node | ||
|
||
```sh | ||
npm install -g ipfs | ||
jsipfs init | ||
# Configure CORS to allow ipfs-http-client to access this IPFS node | ||
jsipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["http://127.0.0.1:8888"]' | ||
# Start the IPFS node, enabling pubsub | ||
jsipfs daemon --enable-pubsub-experiment | ||
``` | ||
|
||
### Install and start the Go IPFS node | ||
|
||
Head over to https://dist.ipfs.io/#go-ipfs and hit the "Download go-ipfs" button. Extract the archive and read the instructions to install. | ||
|
||
After installation: | ||
|
||
```sh | ||
ipfs init | ||
# Configure CORS to allow ipfs-http-client to access this IPFS node | ||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["http://127.0.0.1:8888"]' | ||
# Start the IPFS node, enabling pubsub | ||
ipfs daemon --enable-pubsub-experiment | ||
``` | ||
|
||
## 3. Open two browsers and connect to each node | ||
|
||
Now, open up **two** browser windows. This could be two tabs in the same browser or two completely different browsers, it doesn't matter. Navigate to http://127.0.0.1:8888 in both. | ||
|
||
In the "API ADDR" field enter `/ip4/127.0.0.1/tcp/5001` in one browser and `/ip4/127.0.0.1/tcp/5002` in the other and hit the "Connect" button. | ||
|
||
This connects each browser to an IPFS node and now from the comfort of our browser we can instruct each node to listen to a pubsub topic and send/receive pubsub messages to each other. | ||
|
||
> N.B. Since our two IPFS nodes are running on the same network they should have already found each other by MDNS. So you probably won't need to use the "CONNECT TO PEER" field. If you find your pubsub messages aren't getting through, check the output from your `jsipfs daemon` command and find the first address listed in "Swarm listening on" - it'll look like `/ip4/127.0.0.1/tcp/4002/ipfs/Qm...`. Paste this address into the "CONNECT TO PEER" field for the browser that is connected to your go-ipfs node and hit connect. | ||
Finally, use the "SUBSCRIBE TO PUBSUB TOPIC" and "SEND MESSAGE" fields to do some pubsub-ing, you should see messages sent from one browser appear in the log of the other (provided they're both subscribed to the same topic). |
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,42 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>Pubsub in the browser</title> | ||
<link rel="stylesheet" href="https://unpkg.com/tachyons@4.10.0/css/tachyons.min.css"/> | ||
<link rel="stylesheet" href="https://unpkg.com/ipfs-css@0.12.0/ipfs.css"> | ||
</head> | ||
<body class="sans-serif"> | ||
<header class="pv3 ph2 ph3-l bg-navy cf mb4"> | ||
<a href="https://ipfs.io/" title="ipfs.io"> | ||
<img src="https://ipfs.io/images/ipfs-logo.svg" class="v-mid" style="height:50px"> | ||
</a> | ||
<h1 class="aqua fw2 montserrat dib ma0 pv2 ph1 v-mid fr f3 lh-copy">Pubsub</h1> | ||
</header> | ||
<div class="ph3 mb3"> | ||
<div class="fw2 tracked ttu f6 teal-muted mb2">API Addr</div> | ||
<input id="api-url" value="/ip4/127.0.0.1/tcp/5001" class="dib w-50 ph1 pv2 monospace input-reset ba b--black-20 border-box" placeholder="e.g. /ip4/127.0.0.1/tcp/5001" /> | ||
<button id="node-connect" class="dib ph3 pv2 input-reset ba b--black-20 border-box">Connect</button> | ||
</div> | ||
<div class="ph3 mb3"> | ||
<div class="fw2 tracked ttu f6 teal-muted mb2">Connect to peer</div> | ||
<input id="peer-addr" class="dib w-50 ph1 pv2 monospace input-reset ba b--black-20 border-box" placeholder="e.g. /ip4/127.0.0.1/tcp/4002/ipfs/QmPeerId" /> | ||
<button id="peer-connect" class="dib ph3 pv2 input-reset ba b--black-20 border-box">Connect</button> | ||
</div> | ||
<div class="ph3 mb3"> | ||
<div class="fw2 tracked ttu f6 teal-muted mb2">Subscribe to pubsub topic</div> | ||
<input id="topic" class="dib w-50 ph1 pv2 monospace input-reset ba b--black-20 border-box" placeholder="e.g. books" /> | ||
<button id="subscribe" class="dib ph3 pv2 input-reset ba b--black-20 border-box">Subscribe</button> | ||
</div> | ||
<div class="ph3 mb3"> | ||
<div class="fw2 tracked ttu f6 teal-muted mb2">Send pubsub message</div> | ||
<input id="message" class="dib w-50 ph1 pv2 monospace input-reset ba b--black-20 border-box" /> | ||
<button id="send" class="dib ph3 pv2 input-reset ba b--black-20 border-box">Send</button> | ||
</div> | ||
<div class="ph3 mb3"> | ||
<div class="fw2 tracked ttu f6 teal-muted mb2">Console</div> | ||
<div id="console" class="f7 db w-100 ph1 pv2 monospace input-reset ba b--black-20 border-box overflow-scroll" style="height: 300px"> | ||
</div> | ||
</div> | ||
<script src="bundle.js"></script> | ||
</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,135 @@ | ||
'use strict' | ||
|
||
const IpfsHttpClient = require('ipfs-http-client') | ||
const { sleep, Logger, onEnterPress, catchAndLog } = require('./util') | ||
|
||
async function main () { | ||
const apiUrlInput = document.getElementById('api-url') | ||
const nodeConnectBtn = document.getElementById('node-connect') | ||
|
||
const peerAddrInput = document.getElementById('peer-addr') | ||
const peerConnectBtn = document.getElementById('peer-connect') | ||
|
||
const topicInput = document.getElementById('topic') | ||
const subscribeBtn = document.getElementById('subscribe') | ||
|
||
const messageInput = document.getElementById('message') | ||
const sendBtn = document.getElementById('send') | ||
|
||
let log = Logger(document.getElementById('console')) | ||
let ipfs | ||
let topic | ||
let peerId | ||
|
||
async function reset () { | ||
if (ipfs && topic) { | ||
log(`Unsubscribing from topic ${topic}`) | ||
await ipfs.pubsub.unsubscribe(topic) | ||
} | ||
log = Logger(document.getElementById('console')) | ||
topicInput.value = '' | ||
topic = null | ||
peerId = null | ||
ipfs = null | ||
} | ||
|
||
async function nodeConnect (url) { | ||
await reset() | ||
log(`Connecting to ${url}`) | ||
ipfs = IpfsHttpClient(url) | ||
const { id, agentVersion } = await ipfs.id() | ||
peerId = id | ||
log(`<span class="green">Success!</span>`) | ||
log(`Version ${agentVersion}`) | ||
log(`Peer ID ${id}`) | ||
} | ||
|
||
async function peerConnect (addr) { | ||
if (!addr) throw new Error('Missing peer multiaddr') | ||
if (!ipfs) throw new Error('Connect to a node first') | ||
log(`Connecting to peer ${addr}`) | ||
await ipfs.swarm.connect(addr) | ||
log(`<span class="green">Success!</span>`) | ||
log('Listing swarm peers...') | ||
await sleep() | ||
const peers = await ipfs.swarm.peers() | ||
peers.forEach(peer => { | ||
const fullAddr = `${peer.addr}/ipfs/${peer.peer.toB58String()}` | ||
log(`<span class="${addr.endsWith(peer.peer.toB58String()) ? 'teal' : ''}">${fullAddr}</span>`) | ||
}) | ||
log(`(${peers.length} peers total)`) | ||
} | ||
|
||
async function subscribe (nextTopic) { | ||
if (!nextTopic) throw new Error('Missing topic name') | ||
if (!ipfs) throw new Error('Connect to a node first') | ||
|
||
const lastTopic = topic | ||
|
||
if (topic) { | ||
topic = null | ||
log(`Unsubscribing from topic ${lastTopic}`) | ||
await ipfs.pubsub.unsubscribe(lastTopic) | ||
} | ||
|
||
log(`Subscribing to ${nextTopic}...`) | ||
|
||
await ipfs.pubsub.subscribe(nextTopic, msg => { | ||
const from = msg.from | ||
const seqno = msg.seqno.toString('hex') | ||
if (from === peerId) return log(`Ignoring message ${seqno} from self`) | ||
log(`Message ${seqno} from ${from}:`) | ||
try { | ||
log(JSON.stringify(msg.data.toString(), null, 2)) | ||
} catch (_) { | ||
log(msg.data.toString('hex')) | ||
} | ||
}, { | ||
onError: (err, fatal) => { | ||
if (fatal) { | ||
console.error(err) | ||
log(`<span class="red">${err.message}</span>`) | ||
topic = null | ||
log('Resubscribing in 5s...') | ||
setTimeout(catchAndLog(() => subscribe(nextTopic), log), 5000) | ||
} else { | ||
console.warn(err) | ||
} | ||
} | ||
}) | ||
|
||
topic = nextTopic | ||
log(`<span class="green">Success!</span>`) | ||
} | ||
|
||
async function send (msg) { | ||
if (!msg) throw new Error('Missing message') | ||
if (!topic) throw new Error('Subscribe to a topic first') | ||
if (!ipfs) throw new Error('Connect to a node first') | ||
|
||
log(`Sending message to ${topic}...`) | ||
await ipfs.pubsub.publish(topic, msg) | ||
log(`<span class="green">Success!</span>`) | ||
} | ||
|
||
const onNodeConnectClick = catchAndLog(() => nodeConnect(apiUrlInput.value), log) | ||
apiUrlInput.addEventListener('keydown', onEnterPress(onNodeConnectClick)) | ||
nodeConnectBtn.addEventListener('click', onNodeConnectClick) | ||
|
||
const onPeerConnectClick = catchAndLog(() => peerConnect(peerAddrInput.value), log) | ||
peerAddrInput.addEventListener('keydown', onEnterPress(onPeerConnectClick)) | ||
peerConnectBtn.addEventListener('click', onPeerConnectClick) | ||
|
||
const onSubscribeClick = catchAndLog(() => subscribe(topicInput.value), log) | ||
topicInput.addEventListener('keydown', onEnterPress(onSubscribeClick)) | ||
subscribeBtn.addEventListener('click', onSubscribeClick) | ||
|
||
const onSendClick = catchAndLog(async () => { | ||
await send(messageInput.value) | ||
messageInput.value = '' | ||
}, log) | ||
messageInput.addEventListener('keydown', onEnterPress(onSendClick)) | ||
sendBtn.addEventListener('click', onSendClick) | ||
} | ||
|
||
main() |
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,20 @@ | ||
{ | ||
"name": "browser-pubsub-example", | ||
"version": "0.0.0", | ||
"description": "An example demonstrating pubsub in the browser", | ||
"private": true, | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "npm run build && npm run serve", | ||
"build": "browserify index.js > bundle.js", | ||
"serve": "http-server -a 127.0.0.1 -p 8888", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"author": "Alan Shaw", | ||
"license": "MIT", | ||
"dependencies": { | ||
"browserify": "^16.5.0", | ||
"http-server": "^0.11.1", | ||
"ipfs-http-client": "../../" | ||
} | ||
} |
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,31 @@ | ||
exports.sleep = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
exports.Logger = outEl => { | ||
outEl.innerHTML = '' | ||
return message => { | ||
const container = document.createElement('div') | ||
container.innerHTML = message | ||
outEl.appendChild(container) | ||
outEl.scrollTop = outEl.scrollHeight | ||
} | ||
} | ||
|
||
exports.onEnterPress = fn => { | ||
return e => { | ||
if (event.which == 13 || event.keyCode == 13) { | ||
e.preventDefault() | ||
fn() | ||
} | ||
} | ||
} | ||
|
||
exports.catchAndLog = (fn, log) => { | ||
return async (...args) => { | ||
try { | ||
await fn(...args) | ||
} catch (err) { | ||
console.error(err) | ||
log(`<span class="red">${err.message}</span>`) | ||
} | ||
} | ||
} |
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