Skip to content

Commit

Permalink
improve webrtc connection handler
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjoz committed May 25, 2024
1 parent f730f4e commit 2b52003
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 106 deletions.
6 changes: 6 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@solidjs/meta": "^0.29.3",
"@solidjs/router": "^0.13.3",
"@solidjs/start": "^1.0.0-rc.1",
"dexie": "^4.0.5",
"solid-js": "^1.8.17",
"vinxi": "^0.3.11"
},
Expand Down
121 changes: 15 additions & 106 deletions frontend/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,152 +1,61 @@
"use client";
import { For, Show, createEffect, createSignal } from "solid-js";
import Dexie from 'dexie'
import "video.js/dist/video-js.min.css";
import { Connect, connectionState, iceConnectionState, recivedMessages, webRTCManager } from "~/services/connection";

export default function Home() {

const [localDescription, setLocalDescription] = createSignal("")
const [createdAnswer, setCreatedAnswer] = createSignal("----------")
const [connectionState, setConnectionState] = createSignal("Pending")
const [iceConnectionState, setIceConnectionState] = createSignal("Pending")
const [recivedMessages, setRecivedMessages] = createSignal([])

let remoteOffer = ""
let remoteAnswer = ""
let messageTextArea: HTMLTextAreaElement

if(typeof window === 'undefined'){ return <div>!</div> }
let channel: RTCDataChannel

const connection = new RTCPeerConnection({
iceServers: [/*{ urls: 'stun:stun.l.google.com:19302' }*/]
webRTCManager.getOffer().then(offerString => {
setLocalDescription(offerString)
})

connection.onconnectionstatechange = (event) => {
console.log("connectionstatechange", event)
setConnectionState(connection.connectionState)
}

connection.oniceconnectionstatechange = (event) => {
console.log("oniceconnectionstatechange", event)
setIceConnectionState(connection.iceConnectionState)
}

const onmessage = (event: MessageEvent) => {
setRecivedMessages([...recivedMessages(), event.data])
}

connection.ondatachannel = (event) => {
console.log('ondatachannel')
channel = event.channel
channel.onmessage = onmessage
}

connection.onnegotiationneeded = async (ev) => {
console.log('onnegotiationneeded', ev)
}

const acceptRemoteAnswer = async () => {
if(!remoteAnswer){ return }
try {
await connection.setRemoteDescription(JSON.parse(remoteAnswer))
await webRTCManager.connection.setRemoteDescription(JSON.parse(remoteAnswer))
localStorage.setItem("savedRemoteAnswer", remoteAnswer)
} catch (error) {
console.error(error)
console.error('Remote Answer no pudo ser parseada', remoteAnswer)
}
}

async function createOffer() {
const nowTime = Math.floor(Date.now()/1000)
const savedLocalOffer = localStorage.getItem("savedLocalOffer") || ""
const lastSessionEnds = parseInt(localStorage.getItem("lastSessionEnds") || "0")

channel = connection.createDataChannel('data')
channel.onopen = event => console.log('onopen', event)
channel.onmessage = onmessage

let isSavedOffer = false
if(savedLocalOffer && (nowTime - lastSessionEnds < 600)){
const offer = JSON.parse(savedLocalOffer)
try {
await connection.setLocalDescription(new RTCSessionDescription(offer))
isSavedOffer = true
} catch (error) {
console.log('Error setting saved offer', error)
console.log('Invalid Offer::', savedLocalOffer)
}
}
if(!isSavedOffer){
const offer = await connection.createOffer()
console.log("Local offer created:",JSON.stringify(offer))
connection.setLocalDescription(offer).then(() => offer).then(offer2 => {
localStorage.setItem("savedLocalOffer", JSON.stringify(offer2))
})
}

connection.onicecandidate = (event) => {
console.log('localDescription Offer 1::', JSON.stringify(connection.localDescription))
if(connection.localDescription.type == 'offer'){
setLocalDescription(JSON.stringify(connection.localDescription))
setInterval(() => {
localStorage.setItem("lastSessionEnds",String(Math.floor(Date.now()/1000)))
},1000)
}
if (!event.candidate) {
console.log('localDescription Offer 2::', JSON.stringify(connection.localDescription))
}
}
}

createOffer()

const acceptRemoteOffer = async () => {
if(!remoteOffer){ return }
try {
const offer = JSON.parse(remoteOffer)
await connection.setRemoteDescription(offer)
await webRTCManager.connection.setRemoteDescription(offer)

connection.onicecandidate = (event) => {
if(connection.localDescription.type == 'answer'){
setCreatedAnswer(JSON.stringify(connection.localDescription))
webRTCManager.connection.onicecandidate = (event) => {
if(webRTCManager.connection.localDescription.type == 'answer'){
setCreatedAnswer(JSON.stringify(webRTCManager.connection.localDescription))
}
console.log('localDescription Answer 1::', connection.localDescription)
console.log('localDescription Answer 1::', webRTCManager.connection.localDescription)
if (!event.candidate) {
console.log('localDescription Answer 2::', JSON.stringify(connection.localDescription))
console.log('localDescription Answer 2::', JSON.stringify(webRTCManager.connection.localDescription))
}
}

const answer = await connection.createAnswer()
await connection.setLocalDescription(answer)
const answer = await webRTCManager.connection.createAnswer()
await webRTCManager.connection.setLocalDescription(answer)
} catch (error) {
console.error(error)
console.error('Remote Offer no pudo ser parseada', remoteOffer)
}
}

createEffect(() => {
const ws = new WebSocket('https://pv5s7gfoge.execute-api.us-east-1.amazonaws.com/p/');

ws.onopen = () => {
console.log('WebSocket is connected');
ws.send('Hello Server!');
};

ws.onmessage = (event) => {
console.log(`Received: ${event.data}`);
};

ws.onerror = (error) => {
console.log(`WebSocket error: ${error}`);
};

ws.onclose = () => {
console.log('WebSocket connection closed');
};

return () => {
ws.close();
};
Connect()
})

return <div>
Expand Down Expand Up @@ -189,7 +98,7 @@ export default function Home() {
<button onClick={(ev) => {
ev.stopPropagation()
if(messageTextArea && messageTextArea.value){
channel.send(messageTextArea.value)
webRTCManager.channel.send(messageTextArea.value)
messageTextArea.value = ""
}
}}>
Expand Down
152 changes: 152 additions & 0 deletions frontend/src/services/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import Dexie from 'dexie'
import { createSignal } from 'solid-js'

let dexieInitPromise: Promise<void>
let dexiedb: Dexie

const getDexieInstance = async (): Promise<Dexie> => {
if(dexieInitPromise){
await dexieInitPromise
}
if(dexiedb){ return dexiedb }

dexiedb = new Dexie('videomod')
dexiedb.version(1).stores({
config: 'key'
})

console.log("creando db dexie::", dexiedb)

dexieInitPromise = new Promise((resolve, reject) => {
dexiedb.open().then(()=>{
resolve()
}).catch((error) => {
console.log('Error opening dexie', error)
reject()
})
})

await dexieInitPromise
return dexiedb
}

const getClientID = async () => {
const db = await getDexieInstance()
const clientInfo = await db.table('config').get('clientInfo')
if(!clientInfo){
const id = Date.now().toString(36).substring(2) + Math.random().toString(36).substring(2,6)
await db.table('config').put({key: 'clientInfo', id })
return id
} else {
return clientInfo.id
}
}

const getIpFromCandidate = (offer: string) => {
// const ix1 = offer.indexOf('webrtc-datachannel')
const ix1 = offer.indexOf('nc=IN IP4')
const ix2 = offer.indexOf('\\r',ix1+10)
const IP = offer.substring(ix1+10,ix2).trim()
return IP
}

export const [iceConnectionState, setIceConnectionState] = createSignal("Pending")
export const [recivedMessages, setRecivedMessages] = createSignal([])
export const [connectionState, setConnectionState] = createSignal("Pending")

export class WebRTCManager {

connection: RTCPeerConnection
channel: RTCDataChannel
offerString: string
promiseOngoing: Promise<string>

constructor(){
this.connection = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
})

this.connection.onconnectionstatechange = (event) => {
console.log("connectionstatechange", event)
setConnectionState(this.connection.connectionState)
}

this.connection.oniceconnectionstatechange = (event) => {
console.log("oniceconnectionstatechange", event)
setIceConnectionState(this.connection.iceConnectionState)
}

let iceCandidateResolve: (offerString: string) => void
this.promiseOngoing = new Promise(r => { iceCandidateResolve = r })

this.connection.onicecandidate = (event) => {
const offer = JSON.stringify(this.connection.localDescription)
const IP = getIpFromCandidate(offer).split(".").filter(x => x)
if(IP[0] === 'localhost' || IP[0] === '0' || IP[0] === '127'){
return
}
//LA OFFER QUE VALE ES LA DE AQUÍ!!
// console.log('offer ip', getIpFromCandidate(offer))
// console.log('onicecandidate', JSON.stringify(this.connection.localDescription))
this.offerString = JSON.stringify(this.connection.localDescription)
console.log("resolviendo offer string::", this.offerString)
iceCandidateResolve(this.offerString)
this.promiseOngoing = null
}

const onmessage = (event: MessageEvent) => {
setRecivedMessages([...recivedMessages(), event.data])
}

this.connection.ondatachannel = (event) => {
console.log('ondatachannel')
this.channel = event.channel
this.channel.onmessage = onmessage
}

this.connection.onnegotiationneeded = async (ev) => {
console.log('onnegotiationneeded', ev)
}

this.channel = this.connection.createDataChannel('data')
this.channel.onopen = event => console.log('onopen', event)
this.channel.onmessage = onmessage

this.connection.createOffer().
then((offer) => {
this.connection.setLocalDescription(offer)
})
}

async getOffer() {
if(this.promiseOngoing){ return await this.promiseOngoing }
if(this.offerString){ return this.offerString }
}
}

export const webRTCManager = new WebRTCManager()

export const Connect = async ()=> {
const clientID = await getClientID()
console.log("offer string::", webRTCManager.getOffer())

console.log("client id obtenido::", clientID)
const ws = new WebSocket('wss://pv5s7gfoge.execute-api.us-east-1.amazonaws.com/p/')

ws.onopen = () => {
console.log('WebSocket is connected')
ws.send('Hello Server!')
}

ws.onmessage = (event) => {
console.log(`Received: ${event.data}`)
}

ws.onerror = (error) => {
console.log(`WebSocket error: ${error}`)
}

ws.onclose = () => {
console.log('WebSocket connection closed')
}
}

0 comments on commit 2b52003

Please sign in to comment.