Skip to content

Instantly share code, notes, and snippets.

@bellbind
Created May 25, 2020 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bellbind/9355f4ad87fde72f975d2dad3236232c to your computer and use it in GitHub Desktop.
Save bellbind/9355f4ad87fde72f975d2dad3236232c to your computer and use it in GitHub Desktop.
[js-ipfs][browser] Chat with js-libp2p in js-ipfs node
<!doctype html>
<html>
<head>
<title>Chat on libp2p (js-ipfs-0.40)</title>
<script type="module" src="./main-40.js"></script>
</head>
<body>
<h3 id="myid"></h3>
<div>
to: <input id="to" type="text" size="40"/>
message: <input id="msg" type="text" size="40"/>
<button id="send">send</button>
</div>
<hr/>
<pre id="log"></pre>
</body>
</html>
<!doctype html>
<html>
<head>
<title>Chat on libp2p</title>
<script type="module" src="./main.js"></script>
</head>
<body>
<h3 id="myid"></h3>
<div>
to: <input id="to" type="text" size="40"/>
message: <input id="msg" type="text" size="40"/>
<button id="send">send</button>
</div>
<hr/>
<pre id="log"></pre>
</body>
</html>
// this bundle maybe non ES-module, but it can import to load as `window.Ipfs`
// This example code is for regacy js-ipfs-0.40
import "https://cdn.jsdelivr.net/npm/ipfs@0.40.0/dist/index.min.js";
//console.log(window.Ipfs);
// util1: pull-stream source from ES iterator
const iterableSource = iterable => {
const itor = iterable[Symbol.iterator]();
return (abort, cb) => {
if (abort) {
if (typeof itor.return === "function") itor.return(abort);
return cb(abort);
}
const {value, done} = itor.next();
return cb(done, value);
};
};
// util2: pull-stream sink that turns a pull-stream source as ES async itor
const asyncIterSink = source => ({
[Symbol.asyncIterator]: () => ({
next: () => new Promise(
f => source(false, (done, value) => f({done, value}))),
}),
});
const promisify = func => function (...args) {
return new Promise((f, e) => func.call(
this, ...args, (err, value) => err ? e(err) : f(value)));
};
const main = async () => {
const node = window.node = await Ipfs.create({
repo: `ipfs-${Math.random()}`,
//relay: {enabled: true, hop: {enabled: true, active: true}},
relay: {enabled: true, hop: {enabled: true}},
});
await node.ready;
console.log("IPFS version:", (await node.version()).version);
console.log(`Peer ID:`, (await node.id()).id);
const myid = node.libp2p.peerInfo.id.toB58String();
console.log("libp2p ID:", myid);
for (const ma of node.libp2p.peerInfo.multiaddrs.toArray()) {
console.log("multiaddr:", ma.toString());
}
const protocolId = "chat-example-proto";
node.libp2p.handle(protocolId, async (funcs, conn) => {
const pid = await promisify(conn.getPeerInfo.bind(conn))();
const id = pid.id.toB58String();
const msg = [];
for await (const buf of asyncIterSink(conn.source)) {
msg.push(new TextDecoder().decode(buf.buffer));
}
document.querySelector("#log").prepend(`${id}: ${"".concat(msg)}\n`);
});
document.querySelector("#send").addEventListener("click", ev => {
(async () => {
const id = document.querySelector("#to").value;
const msg = document.querySelector("#msg").value;
const p2pid = `/p2p-circuit/ipfs/${id}`;
const conn = await node.libp2p.dialProtocol(p2pid, protocolId);
conn.sink(iterableSource([new TextEncoder().encode(msg)]));
document.querySelector("#msg").value = "";
document.querySelector("#log").prepend(`${myid}: ${msg}\n`);
})().catch(console.error);
});
document.querySelector("#myid").textContent = myid;
};
main().catch(console.error);
// this bundle maybe non ES-module, but it can import to load as `window.Ipfs`
// Chat example on js-libp2p in js-ipfs (>= 0.41)
//import "https://cdn.jsdelivr.net/npm/ipfs@0.44.0/dist/index.min.js";
import "https://cdn.jsdelivr.net/npm/ipfs/dist/index.min.js";
//console.log(window.Ipfs);
// util1: duplex-stream source from ES iterator
const iterableSource = async function* (iter) {
return yield* iter;
};
const promisify = func => function (...args) {
return new Promise((f, e) => func.call(
this, ...args, (err, value) => err ? e(err) : f(value)));
};
const main = async () => {
const node = window.node = await Ipfs.create({
repo: `ipfs-${Math.random()}`,
relay: {enabled: true, hop: {enabled: true, active: true}},
});
await node.ready;
console.log("IPFS version:", (await node.version()).version);
console.log(`Peer ID:`, (await node.id()).id);
const myid = node.libp2p.peerInfo.id.toB58String();
console.log("libp2p ID:", myid);
// receive
const protocolId = "chat-example-proto";
node.libp2p.handle(protocolId, async ({connection, stream, protocol}) => {
// Connection: https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/connection
//console.log(connection);
const id = connection.remotePeer.toB58String(); // PeerId instance
//console.log(id);
console.log(connection.remoteAddr.toString()); // Multiaddr as "/p2p/Qm..."
// duplex-stream: https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it
//console.log(stream);
const msg = [];
for await (const bl of stream.source) {
// bl: BufferList; see https://github.com/rvagg/bl
//console.log(bl);
//console.log(bl.slice()); // as Uint8Array
msg.push(new TextDecoder().decode(bl.slice()));
}
document.querySelector("#log").prepend(`${id}: ${"".concat(msg)}\n`);
});
// from js-ipfs-0.41.0, swarm.connect() to p2pid with explicit relay required
const connect = async (node, id) => {
// swarm connect with relay
const addrs = new Set((await node.swarm.addrs()).map(({id}) => id));
//console.log(addrs);
if (addrs.has(id)) return;
let relaid = false;
for (const relay of addrs) {
const relayid = `/p2p/${relay}/p2p-circuit/p2p/${id}`;
console.log("relayid", relayid);
try {
await node.swarm.connect(relayid);
relaid = true;
break;
} catch (error) {
//console.log(error);
}
}
if (!relaid) throw Error(`could not relay to ${id}`);
};
// send
document.querySelector("#send").addEventListener("click", ev => {
(async () => {
const id = document.querySelector("#to").value;
const msg = document.querySelector("#msg").value;
const p2pid = `/p2p/${id}`;
console.log("p2pid", p2pid);
await connect(node, id);
// p2pid can avaialbe after dialed from the id
const {stream, protocol} =
await node.libp2p.dialProtocol(p2pid, protocolId);
stream.sink(iterableSource([new TextEncoder().encode(msg)]));
console.log(`dialed to ${p2pid}`);
document.querySelector("#msg").value = "";
document.querySelector("#log").prepend(`${myid}: ${msg}\n`);
})().catch(console.error);
});
document.querySelector("#myid").textContent = myid;
};
main().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment