Browser nodes cannot use the majority of public nodes as relay?

Im new to Libp2p and still learning the ropes. Feel free to correct me. Im trying to establish a libp2p connection between a browser libp2p node and a private go libp2p node behind NAT/Firewall.

I set up my go libp2p node to make a reservation on a public node.

func main() {
	var relayAddress = os.Args[1]
	multiAddr, _ := ma.NewMultiaddr(relayAddress)
	relayinfo, _ := peer.AddrInfoFromP2pAddr(multiAddr)

	host, err := libp2p.New(

	reservation, err := client.Reserve(context.Background(), host, *relayinfo)
	if err != nil {
		log.Printf("host failed to receive a relay reservation from relay. %v", err)

	relayaddr, _ := ma.NewMultiaddr("/p2p-circuit/p2p/" + host.ID().String())
	log.Printf("Reservation done. Now we can be reached via relay at %s%s", reservation.Addrs[0], relayaddr)

> go run . /ip4/
Host created. We are: 12D3KooWMH2F5UixcgUQ9kmnMT2EXDgnTPU8n8812NTTYoqbaNEN
Our listeners:  [...]
Reservation done. Now we can be reached via relay at /ip4/

No problem setting up go libp2p node. The advertised address, I will share/announce via a side channel

Now on the browser side, I pass in go node’s multiaddr via an arbitrary method. I used URL query params.

const params = new Proxy(new URLSearchParams(, {
  get: (searchParams, prop) => searchParams.get(prop),

document.addEventListener('DOMContentLoaded', async () => {
  const libp2p = await createLibp2p({
    transports: [
        // use content routing to find a circuit relay server we can reserve a
        // slot on
        discoverRelays: 1
    // We are dialling localhost addr, set to false
    connectionGater: {
      denyDialMultiaddr: async () => false
    connectionEncryption: [noise()],
    streamMuxers: [yamux(), mplex()],
    peerDiscovery: [
        list: [
    services: {
      identify: identifyService(),

      // Using own bootstrap node, KadDHT not required?
      // // the DHT is used to find circuit relay servers we can reserve a slot on
      // dht: kadDHT({
      //   // browser node ordinarily shouldn't be DHT servers
      //   clientMode: true
      // })

  const ma = multiaddr(params.get("multiaddr"))

  const stream = await libp2p.dialProtocol(ma, "customprotocol")

If I dial the address shown in the Now we can be reached line from the go node output, the browser node will tell me that there are no valid addresses available to dial.
If I pass into my go node, a WebSocket relay that I have set up for testing, then my scenario works fine.

Which leads me back to my post’s title.

It is understandable why the browser node threw no valid addresses available to dial error. Browser nodes can only dial nodes that offer websocket transports. But the vast majority of public nodes do not have websocket transport. All nodes I have tried in the bootstrap list here only have raw TCP / UDP sockets.

Which means that I have to host a websocket relay server for my scenario? I intend to use the relayed connection to exchange WebRTC SDP data for video sharing. After SDP exchange, the relayed connection can be closed, as hole punching will be handled by WebRTC I would like to avoid hosting any server wherever posible, which for the purpose of SDP exchange would be overkill. Any other way around this problem? Or maybe wait a while longer for new developments in Libp2p WebRTC?

I am aware that js-libp2p already has WebRTC and WebRTCDirect transport, and go-libp2p’s WebRTC is under active development. I believe it relies on Kad DHT or PubSub to find the multiaddr of the opposite peer and pretend to exchange SDP, by assembling the SDP locally. Is the latency and user experience comparable to dialling a node via a multiaddr?