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
docs: browser pubsub example #1060
Closed
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
9b39a13
feat: enable pubsub in the browser
3972599
fix: tests
0e0d3a6
fix: use included querystring module
5d1d27a
chore: appease linter
7b03fdf
chore: update interface-ipfs-core
34fd801
chore: skip missing endpoint tests
2f261c1
chore: skip test
34682d4
fix: skip in the right place
370a801
refactor: more readable code for consuming message stream
3a0fe8a
fix: add workaround for subscribe in Firefox
da1a3c4
docs: add browser pubsub example
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,194 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<title>Pubsub in the browser</title> | ||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/tachyons.min.css"/> | ||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/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 URL</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" /> | ||
<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" /> | ||
<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" /> | ||
<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="./index.js"></script> | ||
<script> | ||
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') | ||
|
||
const consoleEl = document.getElementById('console') | ||
|
||
function log (message) { | ||
const container = document.createElement('div') | ||
container.innerHTML = message | ||
consoleEl.appendChild(container) | ||
consoleEl.scrollTop = consoleEl.scrollHeight | ||
} | ||
|
||
function clear () { | ||
consoleEl.innerHTML = '' | ||
} | ||
|
||
let topic | ||
let peerId | ||
|
||
async function nodeConnect (url) { | ||
clear() | ||
log(`Connecting to ${url}`) | ||
window.ipfs = IpfsHttpClient(url) | ||
const { id, agentVersion } = await window.ipfs.id() | ||
peerId = id | ||
log(`<span class="green">Success!</span>`) | ||
log(`Version ${agentVersion}`) | ||
log(`Peer ID ${id}`) | ||
} | ||
|
||
const sleep = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
async function peerConnect (addr) { | ||
if (!addr) throw new Error('Missing peer multiaddr') | ||
if (!window.ipfs) throw new Error('Connect to a node first') | ||
log(`Connecting to peer ${addr}`) | ||
await window.ipfs.swarm.connect(addr) | ||
log(`<span class="green">Success!</span>`) | ||
log('Listing swarm peers...') | ||
await sleep() | ||
const peers = await window.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 (!window.ipfs) throw new Error('Connect to a node first') | ||
|
||
const lastTopic = topic | ||
|
||
if (topic) { | ||
topic = null | ||
log(`Unsubscribing from topic ${lastTopic}`) | ||
await window.ipfs.pubsub.unsubscribe(lastTopic) | ||
} | ||
|
||
log(`Subscribing to ${nextTopic}...`) | ||
|
||
await window.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(catchLog(() => subscribe(nextTopic)), 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 (!window.ipfs) throw new Error('Connect to a node first') | ||
|
||
log(`Sending message to ${topic}...`) | ||
await window.ipfs.pubsub.publish(topic, IpfsHttpClient.Buffer.from(msg)) | ||
log(`<span class="green">Success!</span>`) | ||
} | ||
|
||
function catchLog (fn) { | ||
return async (...args) => { | ||
try { | ||
await fn(...args) | ||
} catch (err) { | ||
console.error(err) | ||
log(`<span class="red">${err.message}</span>`) | ||
} | ||
} | ||
} | ||
|
||
const createOnEnterPress = fn => { | ||
return e => { | ||
if (event.which == 13 || event.keyCode == 13) { | ||
e.preventDefault() | ||
fn() | ||
} | ||
} | ||
} | ||
|
||
const onNodeConnectClick = catchLog(() => nodeConnect(apiUrlInput.value)) | ||
apiUrlInput.addEventListener('keydown', createOnEnterPress(onNodeConnectClick)) | ||
nodeConnectBtn.addEventListener('click', onNodeConnectClick) | ||
|
||
const onPeerConnectClick = catchLog(() => peerConnect(peerAddrInput.value)) | ||
peerAddrInput.addEventListener('keydown', createOnEnterPress(onPeerConnectClick)) | ||
peerConnectBtn.addEventListener('click', onPeerConnectClick) | ||
|
||
const onSubscribeClick = catchLog(() => subscribe(topicInput.value)) | ||
topicInput.addEventListener('keydown', createOnEnterPress(onSubscribeClick)) | ||
subscribeBtn.addEventListener('click', onSubscribeClick) | ||
|
||
const onSendClick = catchLog(async () => { | ||
await send(messageInput.value) | ||
messageInput.value = '' | ||
}) | ||
messageInput.addEventListener('keydown', createOnEnterPress(onSendClick)) | ||
sendBtn.addEventListener('click', onSendClick) | ||
} | ||
main() | ||
</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
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,17 @@ | ||
'use strict' | ||
|
||
module.exports = (fn, opts) => { | ||
opts = opts || {} | ||
// Min number of non-callback args | ||
opts.minArgs = opts.minArgs == null ? 0 : opts.minArgs | ||
|
||
return (...args) => { | ||
const cb = args[args.length - 1] | ||
|
||
if (typeof cb !== 'function' || args.length === opts.minArgs) { | ||
return fn(...args) | ||
} | ||
|
||
fn(...args.slice(0, -1)).then(res => cb(null, res), cb) | ||
} | ||
} |
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,45 @@ | ||
'use strict' | ||
/* eslint-env browser */ | ||
|
||
const { toUri } = require('./multiaddr') | ||
|
||
// Set default configuration and call create function with them | ||
module.exports = create => config => { | ||
config = config || {} | ||
|
||
if (typeof config === 'string') { | ||
config = { apiAddr: config } | ||
} else if (config.constructor && config.constructor.isMultiaddr) { | ||
config = { apiAddr: config } | ||
} else { | ||
config = { ...config } | ||
} | ||
|
||
config.fetch = config.fetch || require('./fetch').fetch | ||
config.apiAddr = (config.apiAddr || getDefaultApiAddr(config)).toString() | ||
config.apiAddr = config.apiAddr.startsWith('/') | ||
? toUri(config.apiAddr) | ||
: config.apiAddr | ||
config.apiPath = config.apiPath || config['api-path'] || '/api/v0' | ||
|
||
if (config.apiPath.endsWith('/')) { | ||
config.apiPath = config.apiPath.slice(0, -1) | ||
} | ||
|
||
config.headers = new Headers(config.headers) | ||
|
||
return create(config) | ||
} | ||
|
||
function getDefaultApiAddr ({ protocol, host, port }) { | ||
if (!protocol) { | ||
protocol = location.protocol.startsWith('http') | ||
? location.protocol.split(':')[0] | ||
: 'http' | ||
} | ||
|
||
host = host || location.hostname | ||
port = port || location.port | ||
|
||
return `${protocol}://${host}${port ? ':' + port : ''}` | ||
} |
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,43 @@ | ||
'use strict' | ||
|
||
const { Headers } = require('node-fetch') | ||
const { toUri } = require('./multiaddr') | ||
const pkg = require('../../package.json') | ||
|
||
// Set default configuration and call create function with them | ||
module.exports = create => config => { | ||
config = config || {} | ||
|
||
if (typeof config === 'string') { | ||
config = { apiAddr: config } | ||
} else if (config.constructor && config.constructor.isMultiaddr) { | ||
config = { apiAddr: config } | ||
} else { | ||
config = { ...config } | ||
} | ||
|
||
config.fetch = config.fetch || require('./fetch').fetch | ||
|
||
if (config.protocol || config.host || config.port) { | ||
const port = config.port ? `:${config.port}` : '' | ||
config.apiAddr = `${config.protocol || 'http'}://${config.host || 'localhost'}${port}` | ||
} | ||
|
||
config.apiAddr = (config.apiAddr || 'http://localhost:5001').toString() | ||
config.apiAddr = config.apiAddr.startsWith('/') | ||
? toUri(config.apiAddr) | ||
: config.apiAddr | ||
config.apiPath = config.apiPath || config['api-path'] || '/api/v0' | ||
|
||
if (config.apiPath.endsWith('/')) { | ||
config.apiPath = config.apiPath.slice(0, -1) | ||
} | ||
|
||
config.headers = new Headers(config.headers) | ||
|
||
if (!config.headers.has('User-Agent')) { | ||
config.headers.append('User-Agent', `${pkg.name}/${pkg.version}`) | ||
} | ||
|
||
return create(config) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now accepts strings.