Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active June 18, 2023 13:46
Show Gist options
  • Save bellbind/981fe90b937dad423c076efe9fa8883f to your computer and use it in GitHub Desktop.
Save bellbind/981fe90b937dad423c076efe9fa8883f to your computer and use it in GitHub Desktop.
[helia][browser] Simple example of using Helia IPFS node on browser
<!doctype html>
<html>
<head>
<link rel="icon" href="data:image/x-icon," />
<script type="module" src="./multi-nodes-cdn.js"></script>
</head>
<body>
Open JavaScript console to check running logs
</body>
</html>
// This example runs 2 helia nodes for publishing and receiving data with cid string
// from CDN
import "https://cdn.jsdelivr.net/npm/helia@^1.3.2/dist/index.min.js";
import "https://cdn.jsdelivr.net/npm/@helia/unixfs@^1.3.0/dist/index.min.js";
import "https://cdn.jsdelivr.net/npm/multiformats@^12.0.1/dist/index.min.js";
const helia = Helia, {unixfs} = HeliaUnixfs, {CID} = Multiformats;
//import * as helia from "helia";
//import {unixfs} from "@helia/unixfs";
//import {CID} from "multiformats/cid";
//NOTE: current browsers not yet implement ReadableStream[Symbol.asyncIterator]
const rsWithAi = rs => { // supply ReadableStream[Symbol.asyncIterator]
if (!(Symbol.asyncIterator in rs)) rs[Symbol.asyncIterator] = async function *() {
const reader = rs.getReader();
try {
while (true) {
const {value, done} = await reader.read();
if (done) return;
yield value;
}
} finally {
reader.releaseLock();
}
};
return rs;
};
//[1. start]
const node1 = await helia.createHelia(); // default: use memory strores (non-persistent)
const node2 = await helia.createHelia();
const node1fs = unixfs(node1);
const node2fs = unixfs(node2);
console.log("[createHelia]");
//[2. wait network established]
//IMPORTANT: For call dial() each other, must await libp2p.getMultiaddrs().length > 0
// - It is a big diffrence from js-ipfs behavior of `await createIpfs()`
while (node1.libp2p.getMultiaddrs().length === 0) await new Promise(f => setTimeout(f, 500));
while (node2.libp2p.getMultiaddrs().length === 0) await new Promise(f => setTimeout(f, 500));
console.log("[libp2p.getMultiaddrs]1", node1.libp2p.getMultiaddrs().map(ma => `${ma}`));
console.log("[libp2p.getMultiaddrs]2", node2.libp2p.getMultiaddrs().map(ma => `${ma}`));
// connect nodes directly with dial()
for (const ma of node2.libp2p.getMultiaddrs()) {
try {
//IMPORTANT: some of transports may fail to dial() directly
await node1.libp2p.dial(ma);
console.log(`[libp2p.dial] success: ${ma}`);
break;
} catch (error) {
console.log(`[libp2p.dial] failed: ${ma}`);
} // dial to other ma
}
//[3. feed content]
// example data
const blob = new Blob([new TextEncoder().encode("Hello World!")], {type: "text/plain;charset=utf-8"});
console.log("[Blob]", blob);
// publish blob as CID with addByteStream()
const cid = await node1fs.addByteStream(rsWithAi(blob.stream()));
//const cid = await node1fs.addBytes(new Uint8Array(await blob.arrayBuffer()));
console.log("[unixfs.addByteStream]", cid);
const cidStr = cid.toString();
const cidAlt = CID.parse(cidStr);
//NOTE: pins only accept when block data existed in node; cannot reserve pinning cid in advance
const ret1 = await node1.pins.add(cidAlt); //NOTE: pins.add() not accept CID string
console.log("[pins.add]", ret1);
//[4. retrieve content]
// NOTE: helia's pins needs stored blocks in blockstore (e.g. cannnot pin before stat()/ls()/cat())
const stat = await node2fs.stat(cidStr);
console.log("[unixfs.stat]", stat);
const ret2 = await node2.pins.add(cidAlt); //NOTE: accept pins.add(cid) after stat(cid) successed
console.log("[pins.add]", ret2);
// get CID object from CID string with ls() from @helia/unixfs
for await (const entry of node2fs.ls(cidStr)) {
console.log("[unixfs.ls]", entry.cid);
}
// retrieve data with cat(cid or cid-string)
const u8as = [];
for await (const u8a of node2fs.cat(cidStr)) {
console.log("[unixfs.cat]", u8a);
u8as.push(u8a.slice()); //NOTE: create non shared Uint8Array instance with u8a.slice()
//IMPORTANT: u8a returned maybe shared in helia impl, so it would crush when u8a.buffer is detached by some action
// - ReadableStream controller.enqueue(u8a)
// - postMessage(msg, [u8a.buffer])
}
console.log("[Blob.text]", await (new Blob(u8as).text()));
//[5. stop]
// stop only helia nodes; unixfs is just a wrapper
console.log("[helia.stop]", await Promise.all([node1.stop(), node2.stop()]));
@bellbind
Copy link
Author

bellbind commented Jun 18, 2023

demo: https://gist.githack.com/bellbind/981fe90b937dad423c076efe9fa8883f/raw/multi-nodes-cdn.html

This example do:

  1. create 2 helia nodes in the same browser tab
  2. wait establishing connection to somewhere (quic/webrtc?), repeatedly check if node.libp2p.getMultiaddrs().length > 0
  3. connect 2nodes directly with node.libp2p.dial(ma)
  4. init data as standard Blob
  5. publish data on node1 with unixfs.addByteStream(rs) then node.pins.add(cid)
  6. fetch data on node2 with unixfs.stat(cid) then node.pins.add(cid)
  7. retrieve data as Blob on node2 with unixfs.cat(cid)
  8. stop nodes
  • NOTE: helia libraries spawn many "WebSocket connection to '' failed"/"Failed to establish a connection to ..." logs, ignore them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment