Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active September 4, 2023 11:46
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/28021e2ff6c99251746a1a46d1a62f68 to your computer and use it in GitHub Desktop.
Save bellbind/28021e2ff6c99251746a1a46d1a62f68 to your computer and use it in GitHub Desktop.
[helia] direct connect case of 2 helia nodes between browser and nodejs

0. Setup

Clone this repo:

$ git clone https://gist.github.com/28021e2ff6c99251746a1a46d1a62f68.git helia-b2l-direct
$ cd helia-b2l-buggy/

Generate npm-browser.js npm package bundle:

$ npm i
$ npm run build

1. Run accessing cid from broser to nodejs case

At fisrt, accessing from nodejs helia to content on browser helia at first, then the browser helia accesses other content on the nodejs helia.

$ node case-b2l-l2b.mjs
...
[text of cid from browser] Hello from browser
...
{ url: '', lineNumber: 19, columnNumber: 10 } [text of cid from nodejs] Hello from nodejs
...

Works well on helia-2.0.1

2. Run accsiing cid from nodejs to browser case

At first, accessing from browser helia to content on nodejs helia at first, then the nodejs helia accesses other content on the browser helia.

$ node case-l2b-b2l.mjs
...
{ url: '', lineNumber: 19, columnNumber: 10 } [text of cid from nodejs] Hello from nodejs
...
[text of cid from browser] Hello from browser
...

Works well on helia-2.0.1

import {createHelia} from "helia";
import {unixfs} from "@helia/unixfs";
import {CID} from "multiformats/cid";
import {multiaddr} from "@multiformats/multiaddr";
import {peerIdFromString} from "@libp2p/peer-id";
import {createTopology} from "@libp2p/topology";
import {mplex} from "@libp2p/mplex";
import {yamux} from "@chainsafe/libp2p-yamux";
import {bootstrap} from "@libp2p/bootstrap";
import {pubsubPeerDiscovery} from "@libp2p/pubsub-peer-discovery";
import {circuitRelayTransport, circuitRelayServer} from "libp2p/circuit-relay";
import {webRTC, webRTCDirect} from "@libp2p/webrtc";
import {webTransport} from "@libp2p/webtransport";
import {webSockets} from "@libp2p/websockets";
import {all} from "@libp2p/websockets/filters";
import {identifyService} from "libp2p/identify";
import {autoNATService} from "libp2p/autonat";
import {gossipsub} from "@chainsafe/libp2p-gossipsub";
import {kadDHT} from "@libp2p/kad-dht";
import {ipnsSelector} from "ipns/selector";
import {ipnsValidator} from "ipns/validator";
export const createHeliaFromMultiaddrs = async multiaddrs => {
// browser helia node which bootstrap target is localhost helia node
const bootstrapConfig = {list: multiaddrs};
const node = await createHelia({
libp2p: {
// https://github.com/ipfs/helia/blob/main/packages/helia/src/utils/libp2p-defaults.browser.ts#L27
addresses: {
listen: [
"/webrtc", "/wss", "/ws",
],
},
transports: [
webSockets({filter: all}),
webRTC(), webRTCDirect(),
webTransport(),
// https://github.com/libp2p/js-libp2p-websockets#libp2p-usage-example
circuitRelayTransport({discoverRelays: 3}),
],
//streamMuxers: [mplex()],
streamMuxers: [yamux()],
peerDiscovery: [bootstrap(bootstrapConfig), pubsubPeerDiscovery()],
services: {
identify: identifyService(),
autoNAT: autoNATService(),
//pubsub: gossipsub({allowPublishToZeroPeers: true, emitSelf: false, canRelayMessage: true}),
pubsub: gossipsub({allowPublishToZeroPeers: true, emitSelf: true, canRelayMessage: true}),
dht: kadDHT({
clientMode: true,
validators: {ipns: ipnsValidator},
selectors: {ipns: ipnsSelector},
}),
},
// https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-connection-gater
connectionGater: {denyDialMultiaddr: async (...args) => false},
},
});
// print peer connecting info
node.libp2p.addEventListener("peer:connect", ev => {
console.log(`[peer:connect on ${node.libp2p.peerId}]`, ev.detail.toString());
});
// js-ipfs-bitswap/src/network.ts
await node.libp2p.register("/ipfs/bitswap/1.2.0", createTopology({
onConnect: (peerId, conn) => {console.log(`[/ipfs/bitswap/1.2.0 on ${node.libp2p.peerId}] onConnect`, `${peerId}`);},
onDisconnect: peerId => {console.log(`[/ipfs/bitswap/1.2.0 on ${node.libp2p.peerId}] onDisconnect`, `${peerId}`);},
}));
// wait to connect bootstrap
while (node.libp2p.getMultiaddrs().length === 0) await new Promise(f => setTimeout(f, 500));
const nodefs = unixfs(node);
return {node, nodefs, CID, peerIdFromString, multiaddr}; // returns helia node and imported utils
};
// Example of indirect connect of 2 helia noeds between browser and browser connected to same helia on nodejs
// resolve cid from browser to browser
import {CID} from "multiformats/cid";
import {unixfs} from "@helia/unixfs";
import {chromium} from "playwright";
import {createServer} from "http-server";
import {createNode} from "./nodejs.js";
// [1. nodejs helia node]
const node = await createNode();
console.log("[peerId on nodejs]", `${node.libp2p.peerId}`);
// [2. http server for url to playwright]
const httpServer = createServer();
await new Promise(f => httpServer.server.listen(f));
const pageUrl = `http://localhost:${httpServer.server.address().port}/index.html`; // importmap only html
console.log("[page server url]", pageUrl);
// [3. helia node on playwright]
const browser = await chromium.launch();
const page1 = await browser.newPage();
await page1.goto(pageUrl);
page1.on("console", msg => {
if (msg.type() === "log") console.log(msg.location(), msg.text());
//if (msg.type() === "error") console.log(msg.location(), msg.text());
});
const page2 = await browser.newPage();
await page2.goto(pageUrl);
page2.on("console", msg => {
if (msg.type() === "log") console.log(msg.location(), msg.text());
//if (msg.type() === "error") console.log(msg.location(), msg.text());
});
const cidStr = await page1.evaluate(({multiaddrs}) => (async () => {
const {createHeliaFromMultiaddrs} = await import("./browser.js");
globalThis.ctx = await createHeliaFromMultiaddrs(multiaddrs);
console.log("[peerId on browser1]", `${ctx.node.libp2p.peerId}`);
// [4. serve content on browser helia node]
const cid = await ctx.nodefs.addBytes(new TextEncoder().encode("Hello from browser"));
try {ctx.node.pins.add(cid);} catch (error) {}
return `${cid}`;
})(), {multiaddrs: node.libp2p.getMultiaddrs().map(ma => `${ma}`)});
console.log("[serving cid on browser1]", cidStr);
await page2.evaluate(({multiaddrs}) => (async () => {
const {createHeliaFromMultiaddrs} = await import("./browser.js");
globalThis.ctx = await createHeliaFromMultiaddrs(multiaddrs);
console.log("[peerId on browser2]", `${ctx.node.libp2p.peerId}`);
})(), {multiaddrs: node.libp2p.getMultiaddrs().map(ma => `${ma}`)});
// [5. dialProtocol from browser2 to browser1] required before access on helia-2.0.1
const multiaddrs = await await page1.evaluate(() => (async () => {
return ctx.node.libp2p.getMultiaddrs().map(ma => `${ma}`);
})());
console.log("[browser1 multiaddrs]", multiaddrs);
await page2.evaluate(({multiaddrs}) => (async () => {
for (const ma of multiaddrs) {
try {
const stream = await ctx.node.libp2p.dialProtocol(ctx.multiaddr(ma), "/ipfs/bitswap/1.2.0", {runOnTransientConnection: true});
const isWebRTCStream = stream.constructor.name === "WebRTCStream";
console.log("[dialProtocol from browser2 to browser1]", ma, stream, stream.constructor.name);
//await stream.close();
break;
} catch (error) {
console.log("[dialProtocol error]", error);
//TBD: how to set runOnTransientConnection = true?
}
}
})(), {multiaddrs});
// [6. access cid from browser to browser]
await page2.evaluate(({cidStr}) => (async () => {
const cid = ctx.CID.parse(cidStr);
// print result of findProviders(cid)
for await (const peer of ctx.node.libp2p.contentRouting.findProviders(cid)) {
console.log("[findProviders peer.id]", `${peer.id}`);
console.log("[findProviders peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[findProviders peer.multiaddrs]", `${ma}`);
console.log("[findProviders peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[findProviders peer.protocols]", `${proto}`);
break;
}
const stat = await ctx.nodefs.stat(cid); // halt
console.log("[stat of cid from other browser]", stat);
const decoder = new TextDecoder();
const texts = [];
for await (const chunk of ctx.nodefs.cat(cid)) {
texts.push(decoder.decode(chunk, {stream: true}));
}
console.log("[text of cid from other browser]", texts.join(""));
})(), {cidStr});
// [7. closing]
console.log("[closing...]");
await page1.evaluate(() => ctx.node.stop());
await page2.evaluate(() => ctx.node.stop());
await browser.close();
await new Promise(f => httpServer.server.close(f));
await node.stop();
// Example of direct connect of 2 helia noeds between browser and nodejs
// resolve cid from browser to nodejs, then resolve other cid from nodejs to browser
import {CID} from "multiformats/cid";
import {unixfs} from "@helia/unixfs";
import {chromium} from "playwright";
import {createServer} from "http-server";
import {createNode} from "./nodejs.js";
// [1. nodejs helia node]
const node = await createNode();
console.log("[peerId on nodejs]", `${node.libp2p.peerId}`);
// [2. http server for url to playwright]
const httpServer = createServer();
await new Promise(f => httpServer.server.listen(f));
const pageUrl = `http://localhost:${httpServer.server.address().port}/index.html`; // importmap only html
console.log("[page server url]", pageUrl);
// [3. helia node on playwright]
const browser = await chromium.launch();
const page1 = await browser.newPage();
await page1.goto(pageUrl);
page1.on("console", msg => {
if (msg.type() === "log") console.log(msg.location(), msg.text());
//if (msg.type() === "error") console.log(msg.location(), msg.text());
});
const cidStr = await page1.evaluate(({multiaddrs}) => (async () => {
const {createHeliaFromMultiaddrs} = await import("./browser.js");
globalThis.ctx = await createHeliaFromMultiaddrs(multiaddrs);
console.log("[peerId on browser]", `${ctx.node.libp2p.peerId}`);
// [4. serve content on browser helia node]
const cid = await ctx.nodefs.addBytes(new TextEncoder().encode("Hello from browser"));
try {ctx.node.pins.add(cid);} catch (error) {}
return `${cid}`;
})(), {multiaddrs: node.libp2p.getMultiaddrs().map(ma => `${ma}`)});
console.log("[serving cid on browser]", cidStr);
const nodefs = unixfs(node);
// [5. resolve cid from nodejs to browser]
{
const cid = CID.parse(cidStr);
const stat = await nodefs.stat(cid);
console.log("[stat of cid from browser]", stat);
const decoder = new TextDecoder();
const texts = [];
for await (const chunk of nodefs.cat(cid)) {
texts.push(decoder.decode(chunk, {stream: true}));
}
console.log("[text of cid from browser]", texts.join(""));
// print findProviders on nodejs
if (1) for await (const peer of node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
}
// [6. serve content on nodejs helia node]
const cid = await nodefs.addBytes(new TextEncoder().encode("Hello from nodejs"));
try {node.pins.add(cid);} catch (error) {}
console.log("[served from browser]")
// [7. access cid from browser to nodejs]
await page1.evaluate(({cidStr}) => (async () => {
const cid = ctx.CID.parse(cidStr);
// print result of findProviders(cid)
for await (const peer of ctx.node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
const stat = await ctx.nodefs.stat(cid); // halt
console.log("[stat of cid from nodejs]", stat);
const decoder = new TextDecoder();
const texts = [];
for await (const chunk of ctx.nodefs.cat(cid)) {
texts.push(decoder.decode(chunk, {stream: true}));
}
console.log("[text of cid from nodejs]", texts.join(""));
// print findProviders on browser
if (1) for await (const peer of ctx.node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
})(), {cidStr: `${cid}`});
// [8. closing]
console.log("[closing...]");
await page1.evaluate(() => ctx.node.stop());
await browser.close();
await new Promise(f => httpServer.server.close(f));
await node.stop();
// Example of direct connect of 2 helia noeds between browser and nodejs
// resolve cid from nodejs to browser, then resolve other cid from browser to nodejs
import {CID} from "multiformats/cid";
import {unixfs} from "@helia/unixfs";
import {chromium} from "playwright";
import {createServer} from "http-server";
import {createNode} from "./nodejs.js";
// [1. nodejs helia node]
const node = await createNode();
console.log("[peerId on nodejs]", `${node.libp2p.peerId}`);
// [2. http server for url to playwright]
const httpServer = createServer();
await new Promise(f => httpServer.server.listen(f));
const pageUrl = `http://localhost:${httpServer.server.address().port}/index.html`; // importmap only html
console.log("[page server url]", pageUrl);
// [3. helia node on playwright]
const browser = await chromium.launch();
const page1 = await browser.newPage();
await page1.goto(pageUrl);
page1.on("console", msg => {
if (msg.type() === "log") console.log(msg.location(), msg.text());
//if (msg.type() === "error") console.log(msg.location(), msg.text());
});
const cidStr = await page1.evaluate(({multiaddrs}) => (async () => {
const {createHeliaFromMultiaddrs} = await import("./browser.js");
globalThis.ctx = await createHeliaFromMultiaddrs(multiaddrs);
console.log("[peerId on browser]", `${ctx.node.libp2p.peerId}`);
// [4. serve content on browser helia node]
const cid = await ctx.nodefs.addBytes(new TextEncoder().encode("Hello from browser"));
try {ctx.node.pins.add(cid);} catch (error) {}
return `${cid}`;
})(), {multiaddrs: node.libp2p.getMultiaddrs().map(ma => `${ma}`)});
console.log("[serving cid on browser]", cidStr);
const nodefs = unixfs(node);
// [5. serve content on nodejs helia node]
const cid = await nodefs.addBytes(new TextEncoder().encode("Hello from nodejs"));
try {node.pins.add(cid);} catch (error) {}
console.log("[served from browser]")
// [6. access cid from browser to nodejs]
await page1.evaluate(({cidStr}) => (async () => {
const cid = ctx.CID.parse(cidStr);
// print result of findProviders(cid)
for await (const peer of ctx.node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
const stat = await ctx.nodefs.stat(cid); // halt
console.log("[stat of cid from nodejs]", stat);
const decoder = new TextDecoder();
const texts = [];
for await (const chunk of ctx.nodefs.cat(cid)) {
texts.push(decoder.decode(chunk, {stream: true}));
}
console.log("[text of cid from nodejs]", texts.join(""));
// print findProviders on browser
if (1) for await (const peer of ctx.node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
})(), {cidStr: `${cid}`});
// [7. resolve cid from nodejs to browser]
{
const cid = CID.parse(cidStr);
const stat = await nodefs.stat(cid);
console.log("[stat of cid from browser]", stat);
const decoder = new TextDecoder();
const texts = [];
for await (const chunk of nodefs.cat(cid)) {
texts.push(decoder.decode(chunk, {stream: true}));
}
console.log("[text of cid from browser]", texts.join(""));
// print findProviders on nodejs
if (1) for await (const peer of node.libp2p.contentRouting.findProviders(cid)) {
console.log("[peer.id]", `${peer.id}`);
console.log("[peer.multiaddrs.length]", peer.multiaddrs.length);
for (const ma of peer.multiaddrs) console.log("[peer.multiaddrs]", `${ma}`);
console.log("[peer.protocolss.length]", peer.protocols.length);
for (const proto of peer.protocols) console.log("[peer.protocols]", `${proto}`);
break;
}
}
// [8. closing]
console.log("[closing...]");
await page1.evaluate(() => ctx.node.stop());
await browser.close();
await new Promise(f => httpServer.server.close(f));
await node.stop();
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script type="importmap">
{
"imports": {
"helia": "./npm-browser.js",
"libp2p/circuit-relay": "./npm-browser.js",
"@libp2p/peer-id": "./npm-browser.js",
"@libp2p/topology": "./npm-browser.js",
"@libp2p/mplex": "./npm-browser.js",
"@chainsafe/libp2p-yamux": "./npm-browser.js",
"@libp2p/bootstrap": "./npm-browser.js",
"@libp2p/pubsub-peer-discovery": "./npm-browser.js",
"@libp2p/webtransport": "./npm-browser.js",
"@libp2p/webrtc": "./npm-browser.js",
"@libp2p/websockets": "./npm-browser.js",
"@libp2p/websockets/filters": "./npm-browser.js",
"@libp2p/circuit-relay": "./npm-browser.js",
"libp2p/identify": "./npm-browser.js",
"libp2p/autonat": "./npm-browser.js",
"@chainsafe/libp2p-gossipsub": "./npm-browser.js",
"@libp2p/kad-dht": "./npm-browser.js",
"ipns/selector": "./npm-browser.js",
"ipns/validator": "./npm-browser.js",
"@multiformats/multiaddr": "./npm-browser.js",
"multiformats/cid": "./npm-browser.js",
"@helia/unixfs": "./npm-browser.js"
}
}
</script>
<link rel="icon" href="data:image/x-icon;" />
</head>
<body></body>
</html>
import * as http from "node:http";
import {createHelia} from "helia";
import {createTopology} from "@libp2p/topology";
import {tcp} from "@libp2p/tcp";
import {webSockets} from "@libp2p/websockets";
import {webRTC, webRTCDirect} from "@libp2p/webrtc";
import {circuitRelayTransport, circuitRelayServer} from "libp2p/circuit-relay";
import {mdns} from "@libp2p/mdns";
import {bootstrap} from "@libp2p/bootstrap";
import {pubsubPeerDiscovery} from "@libp2p/pubsub-peer-discovery";
import {ipniContentRouting} from "@libp2p/ipni-content-routing";
import {identifyService} from "libp2p/identify";
import {autoNATService} from "libp2p/autonat";
import {uPnPNATService} from "libp2p/upnp-nat";
import {gossipsub} from "@chainsafe/libp2p-gossipsub";
import {kadDHT} from "@libp2p/kad-dht";
import {ipnsSelector} from "ipns/selector";
import {ipnsValidator} from "ipns/validator";
export const createNode = async () => {
// localhost helia node for using bootstrap target from browser helia
const node = await createHelia({
libp2p: {
addresses: {
listen: [
"/ip4/0.0.0.0/tcp/0",
"/ip4/0.0.0.0/tcp/0/ws",
"/webrtc",
]
},
transports: [
tcp(),
webSockets({websocket: {rejectUnauthorized: false}}),
circuitRelayTransport({discoverRelays: 3}),
webRTC(),
webRTCDirect(),
],
//streamMuxers: [mplex()],
//streamMuxers: [yamux()],
services: {
identify: identifyService(),
autoNAT: autoNATService(),
upnp: uPnPNATService(),
pubsub: gossipsub({allowPublishToZeroPeers: true, emitSelf: true, canRelayMessage: true}),
dht: kadDHT({
validators: {ipns: ipnsValidator},
selectors: {ipns: ipnsSelector},
}),
relay: circuitRelayServer({advertise: true}),
},
},
});
// uncomment for print peer connecting info
node.libp2p.addEventListener("peer:connect", ev => {
//console.log("[peer:connect]", ev.detail);
});
node.libp2p.addEventListener("peer:discovery", ev => {
//console.log("[peer:discovery]", ev.detail);
});
await node.libp2p.register("/ipfs/bitswap/1.2.0", createTopology({
onConnect: (peerId, conn) => {
//console.log("[/ipfs/bitswap/1.2.0] onConnect", peerId);
},
onDisonnect: peerId => {
//console.log("[/ipfs/bitswap/1.2.0] onDisonnect", peerId);
},
}));
return node;
};
// esbuild to single js file for mapping each module by importmap in HTML
export {createHelia} from "helia";
export {unixfs} from "@helia/unixfs";
export {CID} from "multiformats/cid";
export {multiaddr} from "@multiformats/multiaddr";
export {peerIdFromString} from "@libp2p/peer-id";
export {createTopology} from "@libp2p/topology";
export {mplex} from "@libp2p/mplex";
export {yamux} from "@chainsafe/libp2p-yamux";
export {bootstrap} from "@libp2p/bootstrap";
export {pubsubPeerDiscovery} from "@libp2p/pubsub-peer-discovery";
export {circuitRelayTransport, circuitRelayServer} from "libp2p/circuit-relay";
export {webRTC, webRTCDirect} from "@libp2p/webrtc";
export {webTransport} from "@libp2p/webtransport";
export {webSockets} from "@libp2p/websockets";
export {all} from "@libp2p/websockets/filters";
// services
export {identifyService} from "libp2p/identify";
export {autoNATService} from "libp2p/autonat";
export {gossipsub} from "@chainsafe/libp2p-gossipsub";
export {kadDHT} from "@libp2p/kad-dht";
export {ipnsSelector} from "ipns/selector";
export {ipnsValidator} from "ipns/validator";
{
"type": "module",
"dependencies": {
"@helia/unixfs": "^1.4.1",
"@libp2p/pubsub-peer-discovery": "^8.0.4",
"esbuild": "^0.19.2",
"helia": "^2.0.1",
"http-server": "^14.1.1",
"playwright": "^1.36.2"
},
"scripts": {
"b2l": "node case-b2l-l2b.mjs",
"l2b": "node case-l2b-b2l.mjs",
"build": "esbuild npm-libs.js --bundle --format=esm --target=chrome115 --define:global=globalThis --define:process.env.NODE_DEBUG=false --outfile=npm-browser.js"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment