[browser] peer messaging fails sporadically, `ICE failure`

Hello all,

I am building peer messaging inside a browser process, and on occasion my code functions with no problems. I am able to make connections and send messages from one browser tab to a second one.

Sadly, my code usually breaks during the “addressing” stage, and displays this error repeatedly:

WebRTC: ICE failed, add a TURN server and see about:webrtc for more details

You can see this error if you open two browser tabs and sign in, on

Here’s a dump of my code, in peer.js.
When I run my program, I call in order:

  • peer(callback) → generate peer ID.
  • (exchange peer ids manually using an intermediary server)
  • address(peer_id, callback, callback) → discover peer address and protocols
  • dial(peer_id) → dial the discovered protocol
  • handle(callback) → scan for inbound messages

And finally, here is my code:

import Libp2p from "libp2p";
import Websockets from "libp2p-websockets";
import WebRTCStar from "libp2p-webrtc-star";
import { NOISE } from "libp2p-noise";
import Mplex from "libp2p-mplex";
import Bootstrap from "libp2p-bootstrap";
import KadDHT from "libp2p-kad-dht";

import PeerId from "peer-id";
import pushable from "it-pushable";
import pipe from "it-pipe";

var channel = '/ds.assembled.app/0.0.1'
var p2p = null
var queue = null

async function peer (callback) {
  var key = await PeerId.create({ bits: 1024, keyType: 'RSA' })

  p2p = await Libp2p.create({
    peerId: key,
    addresses: {
      listen: [
        `/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star/p2p/${key.toB58String()}`,
        `/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star/p2p/${key.toB58String()}`,
      ],
    },
    modules: {
      transport: [Websockets, WebRTCStar],
      discovery: [WebRTCStar.discovery],
      connEncryption: [NOISE],
      streamMuxer: [Mplex],
      peerDiscovery: [Bootstrap],
      dht: KadDHT,
    },
    config: {
      peerDiscovery: {
        [Bootstrap.tag]: {
          enabled: true,
          list: [
            "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
            "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
            "/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp",
            "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
            "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
          ],
        },
      },
      dht: {
        enabled: true,
      },
    },
  })
  await p2p.start()
  callback(p2p.peerId.toB58String())
}

async function address(peer_key, found_callback, channel_callback) {
  let peerId = PeerId.parse(peer_key);
  var response = null
  var rounds = 6

  while(rounds > 0) {
    try {
      response = await p2p.peerRouting.findPeer(peerId);
    } catch(e) {
      console.error(e)
      rounds -= 1
    }
  }
  console.log(response)
  if(!response) return null

  var addresses = response.multiaddrs
    .map(x => new TextDecoder().decode(x.bytes))

  found_callback({
    otherPeerMultiaddrs: addresses,
    channel,
  })

  p2p.peerStore.protoBook.get(peerId)
  .then(response => channel_callback({ otherPeerProtocols: response }))
}

async function dial(peer_key) {
  let peerId = PeerId.parse(peer_key);
  const { stream, protocol } = await p2p.dialProtocol(peerId, channel);

  queue = pushable();
  pipe(
    queue,
    (source) => {
      return (async function* () {
        for await (const msg of source) yield str2array(msg);
      })();
    },
    stream
  );

  return queue
}

// based on https://www.dreamincode.net/forums/topic/353343-how-to-convert-uint8array-to-uint16array-or-string/
export function array2str(uint8buf) {
  let buf = new ArrayBuffer(uint8buf.length);
  let bufView = new Uint16Array(buf);
  let count = 0;
  for (let i = 0; i < bufView.length; i++) {
    bufView[i] = uint8buf[count++] + (uint8buf[count++] << 8);
  }
  return String.fromCharCode.apply(null, bufView);
}

// based on str2ab from https://gist.github.com/skratchdot/e095036fad80597f1c1a
export function str2array(str) {
  let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
  let bufView = new Uint16Array(buf);
  for (var i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return new Uint8Array(bufView.buffer, bufView.byteOffset, bufView.byteLength);
}

var handle = (callback) => {
  if(!p2p) return false

  p2p.handle(channel, ({ connection, stream, protocol }) => {
    var peer_key = connection.remoteAddr.getPeerId()

    pipe(
      stream,
      source => (async function* () {
        for await (const buf of source)
          yield array2str(buf.slice())
      })(),
      async (source) => {
        for await (const msg of source)
          callback(peer_key, msg)
      }
    )
  })
}

export { peer, address, dial, handle }