-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
109 lines (94 loc) · 3.3 KB
/
Copy pathserver.js
File metadata and controls
109 lines (94 loc) · 3.3 KB
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
// FileBeam signaling server
//
// This server never sees file contents. It only relays small WebRTC
// handshake messages (offer/answer/ICE candidates) so two browsers can
// find each other and open a direct (or TURN-relayed) data channel.
// The moment that data channel opens, the file itself flows
// browser-to-browser, not through this process.
const path = require('path');
const http = require('http');
const express = require('express');
const { Server } = require('socket.io');
const PORT = process.env.PORT || 3000;
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
const server = http.createServer(app);
const io = new Server(server, {
maxHttpBufferSize: 1e4 // signaling messages only — files never pass through here
});
// code -> { hostSocketId }
const rooms = new Map();
function generateCode() {
// 6-digit numeric code, easy to read aloud or type on a second device
let code;
do {
code = Math.floor(100000 + Math.random() * 900000).toString();
} while (rooms.has(code));
return code;
}
// Self-hosters can set these env vars to add a TURN server, which is what
// makes transfers reliable across two different networks (e.g. mobile data
// to home WiFi) when a direct connection is blocked by NAT/firewalls.
// STUN alone (used by default) is enough for same-network transfers and many
// cross-network ones, but a TURN relay is the only thing that guarantees a
// connection in every case.
app.get('/api/ice-servers', (req, res) => {
const iceServers = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
];
if (process.env.TURN_URL) {
iceServers.push({
urls: process.env.TURN_URL,
username: process.env.TURN_USERNAME,
credential: process.env.TURN_CREDENTIAL
});
}
res.json({ iceServers });
});
io.on('connection', (socket) => {
let joinedCode = null;
let role = null; // 'host' | 'guest'
socket.on('host:create', (_payload, ack) => {
const code = generateCode();
rooms.set(code, { hostSocketId: socket.id });
socket.join(code);
joinedCode = code;
role = 'host';
if (typeof ack === 'function') ack({ code });
});
socket.on('guest:join', (code, ack) => {
const room = rooms.get(code);
if (!room) {
if (typeof ack === 'function') ack({ error: 'That code was not found. Check it and try again.' });
return;
}
const roomSockets = io.sockets.adapter.rooms.get(code);
if (roomSockets && roomSockets.size >= 2) {
if (typeof ack === 'function') ack({ error: 'That code is already paired with another device.' });
return;
}
socket.join(code);
joinedCode = code;
role = 'guest';
if (typeof ack === 'function') ack({ ok: true });
io.to(room.hostSocketId).emit('guest:joined');
});
// Relay WebRTC signaling payloads (offer, answer, ICE candidates)
// verbatim to the other peer in the same room.
socket.on('signal', ({ code, data }) => {
if (!code || joinedCode !== code) return;
socket.to(code).emit('signal', data);
});
socket.on('disconnect', () => {
if (joinedCode) {
socket.to(joinedCode).emit('peer:left');
if (role === 'host') {
rooms.delete(joinedCode);
}
}
});
});
server.listen(PORT, () => {
console.log(`FileBeam signaling server listening on port ${PORT}`);
});