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))
}
}
Top comments (4)
Hi, i am trying to create a simple functionality like this one, where a user creates a room with a roomID and another user joins that room with the same roomID, then the room owner can play a song and the other user will be able to listen to that song as well in sync. I am trying to do this in React.js. Any idea how can i do this?
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!
You should checkout gitDuck. It has a similar comcept.
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.