WebRTC one to one without signaling server
To make peer to peer connection users should exchange so called sdp
which determines a way to exchange data directly between browsers without servers
Usually in all demos there is websocket server which is used to handle this exchange but for better understanding of how it works we are going to exchange this by hands
How WebRTC connection established
Note that this is kind of simplified (there is no exchange of ice candidates) but in general it is how it works
So from initiator side we are:
const offer = await connection.createOffer()
await connection.setLocalDescription(offer)
Then sending this offer
to peer and on his side we are:
await connection.setRemoteDescription(offer)
const answer = await connection.createAnswer()
await connection.setLocalDescription(answer)
Then sending this answer
back to initiator and on his side we are:
await connection.setRemoteDescription(answer)
Note that I have missed ice candidates exchanges here to reduce example size and give better understanding of what is going on
And here is full code
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>webrtc</title>
</head>
<body>
<script>
let channel = null
const connection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] })
connection.ondatachannel = (event) => {
console.log('ondatachannel')
channel = event.channel
// channel.onopen = event => console.log('onopen', event);
// channel.onmessage = event => console.log('onmessage', event);
channel.onmessage = (event) => alert(event.data)
}
connection.onconnectionstatechange = (event) => (document.getElementById('connectionState').innerText = connection.connectionState) // console.log('onconnectionstatechange', connection.connectionState)
connection.oniceconnectionstatechange = (event) =>
(document.getElementById('iceConnectionState').innerText = connection.iceConnectionState) // console.log('oniceconnectionstatechange', connection.iceConnectionState)
async function step_1_initiator_create_offer() {
channel = connection.createDataChannel('data')
// channel.onopen = event => console.log('onopen', event)
// channel.onmessage = event => console.log('onmessage', event)
channel.onmessage = (event) => alert(event.data)
connection.onicecandidate = (event) => {
// console.log('onicecandidate', event)
if (!event.candidate) {
document.getElementById('createdOffer').value = JSON.stringify(connection.localDescription)
document.getElementById('createdOffer').hidden = false
}
}
const offer = await connection.createOffer()
await connection.setLocalDescription(offer)
}
async function step_2_accept_remote_offer() {
const offer = JSON.parse(document.getElementById('remoteOffer').value)
await connection.setRemoteDescription(offer)
}
async function step_3_create_answer() {
connection.onicecandidate = (event) => {
// console.log('onicecandidate', event)
if (!event.candidate) {
document.getElementById('createdAnswer').value = JSON.stringify(connection.localDescription)
document.getElementById('createdAnswer').hidden = false
}
}
const answer = await connection.createAnswer()
await connection.setLocalDescription(answer)
}
async function step_4_accept_answer() {
const answer = JSON.parse(document.getElementById('remoteAnswer').value)
await connection.setRemoteDescription(answer)
}
async function send_text() {
const text = document.getElementById('text').value
channel.send(text)
}
</script>
<table width="100%" border="1">
<tr>
<th>#</th>
<th>initiator</th>
<th>peer</th>
</tr>
<tr>
<td>step 1</td>
<td>
<input type="button" value="create offer" onclick="step_1_initiator_create_offer()" />
<input id="createdOffer" type="text" hidden />
</td>
<td></td>
</tr>
<tr>
<td>step 2</td>
<td></td>
<td>
<input id="remoteOffer" type="text" placeholder="offer from initiator" />
<input type="button" value="accept offer" onclick="step_2_accept_remote_offer()" />
</td>
</tr>
<tr>
<td>step 3</td>
<td></td>
<td>
<input type="button" value="create answer" onclick="step_3_create_answer()" />
<input id="createdAnswer" type="text" hidden />
</td>
</tr>
<tr>
<td>step 4</td>
<td>
<input id="remoteAnswer" type="text" placeholder="answer from peer" />
<input type="button" value="accept answer" onclick="step_4_accept_answer()" />
</td>
<td></td>
</tr>
</table>
<hr />
<input id="text" type="text" />
<input type="button" value="send" onclick="send_text()" />
<hr />
<table border="1">
<tr>
<th colspan="2">connection</th>
</tr>
<tr>
<th>connectionState</th>
<td id="connectionState">unknown</td>
</tr>
<tr>
<th>iceConnectionState</th>
<td id="iceConnectionState">unknown</td>
</tr>
</table>
</body>
</html>