loading...
Cover image for Code Roaster: WebRTC

Code Roaster: WebRTC

hoffmann profile image Peter Hoffmann ・3 min read

Introduction

I want to start a little series with contributions from other people that is called "Code Roaster". This is an invitation to share your code for constructive criticism.

The sentence I hear most often wenn getting shown other peoples code is

I know it's a total mess but it's working and I am going to do it the right way some time in the future.

Let's try to do it right this time and open the lids of our pots and take a look at other peoples' work. Taste it and add your grain of salt.

About me and my work

Hy, I'm working as a full stack developer in a German university and constantly try to improve my skills in programming while keeping up to date with current developments in web technologies. My greatest struggle in IT is the never ending fight against support for outdated browsers (like IE10) and their technological backlog.

Today's project

In 2013 Chris Ball published a serverless WebRTC example that let signaling be done by the user e.g. via IM. The code was maintained for three years until no further development was done. My target was to minimize the example to the smallest working code base using modern web techniques in vanilla js.

I took the code, threw away everything that was unnecessary in my eyes and tried to separate webrtc logic from the user interface. I thereby lost all design as I really suck at that (invitations to anybody who is looking for an ui/ux exercise).

Design decisions

I separated the login into two classes/modules that interact through events. The coupling seems unnecessary complicated:

    const webRtc = new WebRTC(stunServers)
    const gui = new GUI(webRtc)
    webRtc.weave(gui)

Perhaps anybody has an idea how to polish this? Aside from this I don't see any way to improve my code. I invite you to drop your 2¢ for nicer, easier readable, faster running, reusable, maintainable, well structured code.
Fire up the roaster!

Code

The full project can be found at Github

GUI module

/* global EventTarget, CustomEvent */

function timestamp () {
  return (new Date()).toTimeString().substr(0, 8)
}

export default class GUI extends EventTarget {
  constructor (target) {
    super()
    this.target = target

    this.connect = document.querySelector('.connect')
    this.connect.querySelector('button').addEventListener('click', _ => this.clickConnect())
    this.descriptionBox = this.connect.querySelector('textarea')
    this.descriptionBox.value = ''

    this.chat = document.querySelector('.chat')
    this.chat.querySelector('button').addEventListener('click', _ => this.sendMessage())
    this.messageBox = this.chat.querySelector('input[type="text"]')
    this.chatBox = this.chat.querySelector('.chatlog')

    this.addEventListener('candidate', candidateEvent => (this.descriptionBox.value = JSON.stringify(candidateEvent.detail)))
    this.addEventListener('connect', connectEvent => {
      this.connect.style.display = 'none'
      this.chat.style.display = 'initial'
    })
    this.addEventListener('message', messageEvent => this._addChatLine(messageEvent.detail, 'you'))
  }

  trigger (name, detail) {
    this.target.dispatchEvent(new CustomEvent(name, { bubbles: true, detail: detail }))
  }

  clickConnect () {
    const description = this.descriptionBox.value
    try {
      this.trigger('connect', JSON.parse(description))
    } catch (e) {
      this.trigger('connect')
    }
    this.descriptionBox.value = ''
  }

  sendMessage () {
    const message = this.messageBox.value
    if (message.length) {
      this._addChatLine(message, 'me')
      this.trigger('message', message)
    }
    this.messageBox.value = ''
  }

  _addChatLine (text, type) {
    this.chatBox.insertAdjacentHTML('beforeend', `<p class="from-${type}">[${timestamp()}] ${text}</p>`)
    this.chatBox.scrollTop = this.chatBox.scrollHeight
  }
}

WebRTC module

/* global EventTarget, RTCPeerConnection, CustomEvent */

export default class webRTC extends EventTarget {
  constructor (iceServers) {
    super()
    this.connection = new RTCPeerConnection({ 'iceServers': iceServers })
    this.connection.addEventListener('icecandidate',
      ICECandidateEvent => ICECandidateEvent.candidate === null && this.trigger('candidate', this.connection.localDescription))
    this.connection.addEventListener('datachannel',
      ChannelEvent => this.connectChannel(ChannelEvent.channel))
    this.addEventListener('connect', this.connectHandler)
  }

  weave (target) {
    this.target = target
  }

  trigger (name, detail) {
    this.target.dispatchEvent(new CustomEvent(name, { bubbles: true, detail: detail }))
  }

  connectHandler (connectEvent) {
    switch (this.connection.signalingState) {
      case 'stable':
        if (connectEvent.detail === null) {
          this.setupChannel()
          this.connection.createOffer()
            .then(desc => this.connection.setLocalDescription(desc))
        } else {
          this.connection.setRemoteDescription(connectEvent.detail)
          this.connection.createAnswer()
            .then(desc => this.connection.setLocalDescription(desc))
        }
        break
      case 'have-local-offer':
        this.connection.setRemoteDescription(connectEvent.detail)
    }
  }

  setupChannel () {
    this.connectChannel(this.connection.createDataChannel('channel'))
  }

  connectChannel (channel) {
    channel.addEventListener('open', _ => this.trigger('connect'))
    channel.addEventListener('message', messageEvent => this.trigger('message', messageEvent.data))
    this.addEventListener('message', message => channel.send(message.detail))
  }
}

Discussion

pic
Editor guide
Collapse
ramesh profile image
Ramesh Elaiyavalli

Hi Peter - cool concept. Roasting developer's code. 😀

Your code looks simple enough. For a good screen share experience, you want to offer/ adjust FPS. Better frame rate, better screencast experience.

Killer feature: Adjust zoom level at participant size.
Everyone I know codes in VS code. With dark mode as a default, it is hard for the participants to see code and follow along. Instead of constantly asking the guy sharing screen to zoom in, it would be great if remote viewers can do it on their end. This way developers focus on coding, and others watching adjust to their preferred zoom levels.

webRTC looks surprisingly simple with RTCPeerConnection. Ends up surprisingly complex, when you add more and more features & scale.

Good luck!

Collapse
97amarnathk profile image
Amarnath Karthi

You should checkout gitDuck. It has a similar comcept.

Collapse
hoffmann profile image
Peter Hoffmann Author

Just checked it out, sounds good but is still in beta.
If anyone is interested in GitDuck too, you can support me by registering for beta.