-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsignal.js
133 lines (113 loc) · 4.1 KB
/
signal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Signaling server for starting WebRTC connections
import WebSocket from 'ws'
import { Router } from 'express'
export const router = Router()
const sessions = {}
// util function to generate a session code
// https://stackoverflow.com/questions/1349404/#1349426
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"
const charsN = chars.length;
function makeCode() {
let result = ""
for(var i = 0; i < 5; i++) {
result += chars.charAt(Math.floor(Math.random() * charsN));
}
return result;
}
/* --- HTTP session api --- */
// base page
router.get("/", (req, res) => {
console.log(`[signal] ${req.url}`)
res.send(`hello! this is the <a href="https://voxilon.penguinspy.dev">Voxilon</a> WebRTC signaling server`)
})
router.get("/new_session", (req, res) => {
res.send("nope, only a websocket connection to this URI makes sense :)")
})
router.get("/:code", (req, res) => {
// todo: send back autogenerated embed page w/ JS redirect
const session = sessions[req.params.code];
if(session) {
res.send(`woah it's a session with ${session.clients.length + 1} ppl connected`)
} else {
res.status(404).send("Session not found :(")
}
})
/* --- Websocket signaling server --- */
function tryParseMsg(msg) {
try {
return JSON.parse(msg)
} catch(e) {
ws.close(1002, "JSON parse error")
return false
}
}
function handleHostMessage(ws, data) {
//console.log(`[${this.code}] HOST → ${data.to} |`, data)
const client = ws.session.clients[data.to]
if(!client || client.readyState !== WebSocket.OPEN) {
ws.close(1002, "Invalid client specified")
console.warn(`[${ws.code}] host closed due to invalid client specified: \n\t`, data)
return
}
delete data.to // client knows it's to them
client.send(JSON.stringify(data))
}
function handleClientMessage(ws, data) {
//console.log(`[${this.code}] ${this.id} → HOST |`, data)
if(!ws.session.host/* || session.host.readyState !== ws.OPEN*/) { ws.close(1002, "Invalid host") }
ws.session.host.send(JSON.stringify({
from: ws.id,
...data
}))
}
const wsServer = new WebSocket.Server({ noServer: true });
// Initalize websocket & add to session
wsServer.on('connection', (ws, code) => {
if(code === null) { // host
code = "AAAAA" //makeCode()
ws.session = sessions[code] = { host: ws, clients: [] }
ws.on('message', msg => {
const data = tryParseMsg(msg)
if(!data) return console.warn(`[${code}] host closed due to JSON parse error: \n\t${msg}`)
handleHostMessage(ws, data)
})
ws.on('close', (close_code, reason) => {
console.log(`[signal] host of ${code} disconnected: ${close_code} | ${reason}`)
for(const client of ws.session.clients) {
client.close(1001, "Host disconnected.")
}
delete sessions[code]
})
ws.send(`{"type":"hello","join_code":"${code}"}`)
console.log(`[signal] host joined ${code}`)
} else { // client
ws.session = sessions[code]
ws.id = ws.session.clients.push(ws) - 1
ws.on('message', msg => {
const data = tryParseMsg(msg)
if(!data) return console.warn(`[${code}] client #${ws.id} closed due to JSON parse error: \n\t${msg}`)
handleClientMessage(ws, data)
})
ws.send(`{"type":"hello","id":${ws.id}}`)
console.log(`[signal] client #${ws.id} joined ${code}`)
}
})
const signalUriRegex = /^\/([A-HJ-NP-Z0-9]{5})$/;
export function upgrade(req, sock, head) {
console.log(`[signal] connection to '${req.url}' on ${new Date().toLocaleString('en-US', { hour12: false, timeZone: 'UTC' })}`)
const [join_session, code] = req.url.match(signalUriRegex) || [false, null]
const new_session = req.url === "/new_session"
if(!join_session && !new_session) {
// respond with a 400 status so clients don't hang waiting for the response
sock.end(`HTTP/${req.httpVersion} 400 Bad Request\r\n\r\n`)
return
}
if(join_session && !sessions[code]) {
// the host must first create a session
sock.end(`HTTP/${req.httpVersion} 404 Not Found\r\n\r\nA session was not found with the requested code.`)
return
}
wsServer.handleUpgrade(req, sock, head, ws => {
wsServer.emit('connection', ws, code)
})
}