Skip to content
This repository has been archived by the owner on Mar 10, 2020. It is now read-only.

docs: browser pubsub example #1060

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions examples/pubsub/index.html
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))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now accepts strings.

Suggested change
await window.ipfs.pubsub.publish(topic, IpfsHttpClient.Buffer.from(msg))
await window.ipfs.pubsub.publish(topic, 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>
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"browser": {
"glob": false,
"fs": false,
"stream": "readable-stream"
"stream": "readable-stream",
"./src/lib/configure.js": "./src/lib/configure.browser.js"
},
"repository": "github:ipfs/js-ipfs-http-client",
"scripts": {
Expand All @@ -33,6 +34,7 @@
"coverage": "npx nyc -r html npm run test:node -- --bail"
},
"dependencies": {
"abort-controller": "^3.0.0",
"async": "^2.6.1",
"bignumber.js": "^9.0.0",
"bl": "^3.0.0",
Expand All @@ -44,6 +46,7 @@
"detect-node": "^2.0.4",
"end-of-stream": "^1.4.1",
"err-code": "^1.1.2",
"explain-error": "^1.0.4",
"flatmap": "0.0.3",
"glob": "^7.1.3",
"ipfs-block": "~0.8.1",
Expand All @@ -56,6 +59,7 @@
"is-stream": "^2.0.0",
"iso-stream-http": "~0.1.2",
"iso-url": "~0.4.6",
"iterable-ndjson": "^1.1.0",
"just-kebab-case": "^1.1.0",
"just-map-keys": "^1.1.0",
"kind-of": "^6.0.2",
Expand All @@ -65,6 +69,7 @@
"multicodec": "~0.5.1",
"multihashes": "~0.4.14",
"ndjson": "github:hugomrdias/ndjson#feat/readable-stream3",
"node-fetch": "^2.6.0",
"once": "^1.4.0",
"peer-id": "~0.12.2",
"peer-info": "~0.15.1",
Expand All @@ -86,7 +91,7 @@
"cross-env": "^5.2.0",
"dirty-chai": "^2.0.1",
"go-ipfs-dep": "~0.4.21",
"interface-ipfs-core": "^0.107.1",
"interface-ipfs-core": "github:ipfs/interface-js-ipfs-core#fix/max-pubsub-reqs-browser",
"ipfsd-ctl": "~0.43.0",
"nock": "^10.0.2",
"stream-equal": "^1.1.1"
Expand Down
17 changes: 17 additions & 0 deletions src/lib/callbackify.js
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)
}
}
45 changes: 45 additions & 0 deletions src/lib/configure.browser.js
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 : ''}`
}
43 changes: 43 additions & 0 deletions src/lib/configure.js
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)
}
Loading