uniwrtc 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -30
- package/client-browser.js +20 -16
- package/client.js +14 -12
- package/demo.html +46 -22
- package/package.json +1 -1
- package/server.js +42 -37
- package/src/client-cloudflare.js +11 -9
- package/src/room.js +7 -10
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A universal WebRTC signaling service that provides a simple and flexible WebSocket-based signaling server for WebRTC applications.
|
|
4
4
|
|
|
5
|
+
Available on npm: https://www.npmjs.com/package/uniwrtc
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- 🚀 **Simple WebSocket-based signaling** - Easy to integrate with any WebRTC application
|
|
8
|
-
- 🏠 **
|
|
10
|
+
- 🏠 **Session-based architecture** - Support for multiple sessions with isolated peer groups
|
|
9
11
|
- 🔌 **Flexible client library** - Ready-to-use JavaScript client for browser and Node.js
|
|
10
12
|
- 📡 **Real-time messaging** - Efficient message routing between peers
|
|
11
13
|
- 🔄 **Auto-reconnection** - Built-in reconnection logic for reliable connections
|
|
@@ -56,9 +58,9 @@ Open `demo.html` in your web browser to try the interactive demo:
|
|
|
56
58
|
1. Start the server with `npm start` (local signaling at `ws://localhost:8080`), **or** use the deployed Workers endpoint `wss://signal.peer.ooo`.
|
|
57
59
|
2. Open `demo.html` in your browser.
|
|
58
60
|
3. Click "Connect" to connect to the signaling server.
|
|
59
|
-
4. Enter a
|
|
61
|
+
4. Enter a session ID and click "Join Session".
|
|
60
62
|
5. Open another browser window/tab with the same demo page.
|
|
61
|
-
6. Join the same
|
|
63
|
+
6. Join the same session to see peer connections in action and P2P data channels open.
|
|
62
64
|
|
|
63
65
|
## Usage
|
|
64
66
|
|
|
@@ -68,19 +70,19 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
68
70
|
|
|
69
71
|
#### Client → Server Messages
|
|
70
72
|
|
|
71
|
-
**Join a
|
|
73
|
+
**Join a session:**
|
|
72
74
|
```json
|
|
73
75
|
{
|
|
74
76
|
"type": "join",
|
|
75
|
-
"
|
|
77
|
+
"sessionId": "session-123"
|
|
76
78
|
}
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
**Leave a
|
|
81
|
+
**Leave a session:**
|
|
80
82
|
```json
|
|
81
83
|
{
|
|
82
84
|
"type": "leave",
|
|
83
|
-
"
|
|
85
|
+
"sessionId": "session-123"
|
|
84
86
|
}
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -90,7 +92,7 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
90
92
|
"type": "offer",
|
|
91
93
|
"offer": { /* RTCSessionDescription */ },
|
|
92
94
|
"targetId": "peer-client-id",
|
|
93
|
-
"
|
|
95
|
+
"sessionId": "session-123"
|
|
94
96
|
}
|
|
95
97
|
```
|
|
96
98
|
|
|
@@ -100,7 +102,7 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
100
102
|
"type": "answer",
|
|
101
103
|
"answer": { /* RTCSessionDescription */ },
|
|
102
104
|
"targetId": "peer-client-id",
|
|
103
|
-
"
|
|
105
|
+
"sessionId": "session-123"
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
@@ -110,7 +112,7 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
110
112
|
"type": "ice-candidate",
|
|
111
113
|
"candidate": { /* RTCIceCandidate */ },
|
|
112
114
|
"targetId": "peer-client-id",
|
|
113
|
-
"
|
|
115
|
+
"sessionId": "session-123"
|
|
114
116
|
}
|
|
115
117
|
```
|
|
116
118
|
|
|
@@ -132,11 +134,11 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
132
134
|
}
|
|
133
135
|
```
|
|
134
136
|
|
|
135
|
-
**
|
|
137
|
+
**Session joined confirmation:**
|
|
136
138
|
```json
|
|
137
139
|
{
|
|
138
140
|
"type": "joined",
|
|
139
|
-
"
|
|
141
|
+
"sessionId": "session-123",
|
|
140
142
|
"clientId": "abc123",
|
|
141
143
|
"clients": ["xyz789", "def456"]
|
|
142
144
|
}
|
|
@@ -146,8 +148,8 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
146
148
|
```json
|
|
147
149
|
{
|
|
148
150
|
"type": "peer-joined",
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
+
"sessionId": "session-123",
|
|
152
|
+
"peerId": "new-peer-id"
|
|
151
153
|
}
|
|
152
154
|
```
|
|
153
155
|
|
|
@@ -155,8 +157,8 @@ The signaling server accepts WebSocket connections and supports the following me
|
|
|
155
157
|
```json
|
|
156
158
|
{
|
|
157
159
|
"type": "peer-left",
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
+
"sessionId": "session-123",
|
|
161
|
+
"peerId": "departed-peer-id"
|
|
160
162
|
}
|
|
161
163
|
```
|
|
162
164
|
|
|
@@ -166,9 +168,8 @@ Use directly from npm:
|
|
|
166
168
|
```javascript
|
|
167
169
|
// ESM
|
|
168
170
|
import { UniWRTCClient } from 'uniwrtc/client-browser.js';
|
|
169
|
-
// or CommonJS
|
|
170
|
-
const { UniWRTCClient } = require('uniwrtc/client
|
|
171
|
-
// For Node.js signaling client use 'uniwrtc/client.js'
|
|
171
|
+
// or CommonJS (Node)
|
|
172
|
+
const { UniWRTCClient } = require('uniwrtc/client.js');
|
|
172
173
|
```
|
|
173
174
|
|
|
174
175
|
The `client.js` library provides a convenient wrapper for the signaling protocol:
|
|
@@ -183,7 +184,7 @@ client.on('connected', (data) => {
|
|
|
183
184
|
});
|
|
184
185
|
|
|
185
186
|
client.on('joined', (data) => {
|
|
186
|
-
console.log('Joined
|
|
187
|
+
console.log('Joined session:', data.sessionId);
|
|
187
188
|
console.log('Existing peers:', data.clients);
|
|
188
189
|
});
|
|
189
190
|
|
|
@@ -294,9 +295,9 @@ client.on('ice-candidate', async (data) => {
|
|
|
294
295
|
}
|
|
295
296
|
});
|
|
296
297
|
|
|
297
|
-
// Connect and join
|
|
298
|
+
// Connect and join session
|
|
298
299
|
await client.connect();
|
|
299
|
-
client.
|
|
300
|
+
client.joinSession('my-video-session');
|
|
300
301
|
```
|
|
301
302
|
|
|
302
303
|
## API Reference
|
|
@@ -318,8 +319,8 @@ new UniWRTCClient(serverUrl, options)
|
|
|
318
319
|
|
|
319
320
|
- `connect()`: Connect to the signaling server (returns Promise)
|
|
320
321
|
- `disconnect()`: Disconnect from the server
|
|
321
|
-
- `
|
|
322
|
-
- `
|
|
322
|
+
- `joinSession(sessionId)`: Join a specific session
|
|
323
|
+
- `leaveSession()`: Leave the current session
|
|
323
324
|
- `sendOffer(offer, targetId)`: Send a WebRTC offer
|
|
324
325
|
- `sendAnswer(answer, targetId)`: Send a WebRTC answer
|
|
325
326
|
- `sendIceCandidate(candidate, targetId)`: Send an ICE candidate
|
|
@@ -342,7 +343,7 @@ new UniWRTCClient(serverUrl, options)
|
|
|
342
343
|
|
|
343
344
|
## Health Check
|
|
344
345
|
|
|
345
|
-
The server provides an HTTP health check endpoint:
|
|
346
|
+
The server provides an HTTP health check endpoint for monitoring:
|
|
346
347
|
|
|
347
348
|
```bash
|
|
348
349
|
curl http://localhost:8080/health
|
|
@@ -358,12 +359,12 @@ Response:
|
|
|
358
359
|
|
|
359
360
|
## Architecture
|
|
360
361
|
|
|
361
|
-
###
|
|
362
|
+
### Session Management
|
|
362
363
|
|
|
363
|
-
- Each
|
|
364
|
-
- Clients can join/leave
|
|
365
|
-
- Messages can be sent to specific peers or broadcast to all peers in a
|
|
366
|
-
- Empty
|
|
364
|
+
- Each session is identified by a unique session ID (string)
|
|
365
|
+
- Clients can join/leave sessions dynamically
|
|
366
|
+
- Messages can be sent to specific peers or broadcast to all peers in a session
|
|
367
|
+
- Empty sessions are automatically cleaned up
|
|
367
368
|
|
|
368
369
|
### Message Flow
|
|
369
370
|
|
package/client-browser.js
CHANGED
|
@@ -8,7 +8,7 @@ class UniWRTCClient {
|
|
|
8
8
|
this.serverUrl = serverUrl;
|
|
9
9
|
this.ws = null;
|
|
10
10
|
this.clientId = null;
|
|
11
|
-
this.
|
|
11
|
+
this.sessionId = null;
|
|
12
12
|
this.peers = new Map();
|
|
13
13
|
this._connectedOnce = false;
|
|
14
14
|
this.options = {
|
|
@@ -93,24 +93,26 @@ class UniWRTCClient {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
// Prevent duplicate join calls for the same
|
|
98
|
-
if (this.
|
|
99
|
-
this.
|
|
96
|
+
joinSession(sessionId) {
|
|
97
|
+
// Prevent duplicate join calls for the same session
|
|
98
|
+
if (this.sessionId === sessionId) return;
|
|
99
|
+
this.sessionId = sessionId;
|
|
100
|
+
|
|
101
|
+
// Send join message
|
|
100
102
|
this.send({
|
|
101
103
|
type: 'join',
|
|
102
|
-
|
|
104
|
+
sessionId: sessionId,
|
|
103
105
|
peerId: this.clientId
|
|
104
106
|
});
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
if (this.
|
|
109
|
+
leaveSession() {
|
|
110
|
+
if (this.sessionId) {
|
|
109
111
|
this.send({
|
|
110
112
|
type: 'leave',
|
|
111
|
-
|
|
113
|
+
sessionId: this.sessionId
|
|
112
114
|
});
|
|
113
|
-
this.
|
|
115
|
+
this.sessionId = null;
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -127,7 +129,7 @@ class UniWRTCClient {
|
|
|
127
129
|
type: 'offer',
|
|
128
130
|
offer: offer,
|
|
129
131
|
targetId: targetId,
|
|
130
|
-
|
|
132
|
+
sessionId: this.sessionId
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
135
|
|
|
@@ -136,7 +138,7 @@ class UniWRTCClient {
|
|
|
136
138
|
type: 'answer',
|
|
137
139
|
answer: answer,
|
|
138
140
|
targetId: targetId,
|
|
139
|
-
|
|
141
|
+
sessionId: this.sessionId
|
|
140
142
|
});
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -145,7 +147,7 @@ class UniWRTCClient {
|
|
|
145
147
|
type: 'ice-candidate',
|
|
146
148
|
candidate: candidate,
|
|
147
149
|
targetId: targetId,
|
|
148
|
-
|
|
150
|
+
sessionId: this.sessionId
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
153
|
|
|
@@ -187,9 +189,9 @@ class UniWRTCClient {
|
|
|
187
189
|
console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
|
|
188
190
|
break;
|
|
189
191
|
case 'joined':
|
|
190
|
-
this.
|
|
192
|
+
this.sessionId = message.sessionId;
|
|
191
193
|
this.emit('joined', {
|
|
192
|
-
|
|
194
|
+
sessionId: message.sessionId,
|
|
193
195
|
peerId: message.peerId,
|
|
194
196
|
clientId: message.clientId,
|
|
195
197
|
clients: message.clients
|
|
@@ -197,11 +199,13 @@ class UniWRTCClient {
|
|
|
197
199
|
break;
|
|
198
200
|
case 'peer-joined':
|
|
199
201
|
this.emit('peer-joined', {
|
|
202
|
+
sessionId: message.sessionId,
|
|
200
203
|
peerId: message.peerId
|
|
201
204
|
});
|
|
202
205
|
break;
|
|
203
206
|
case 'peer-left':
|
|
204
207
|
this.emit('peer-left', {
|
|
208
|
+
sessionId: message.sessionId,
|
|
205
209
|
peerId: message.peerId
|
|
206
210
|
});
|
|
207
211
|
break;
|
|
@@ -237,7 +241,7 @@ class UniWRTCClient {
|
|
|
237
241
|
this.emit('chat', {
|
|
238
242
|
text: message.text,
|
|
239
243
|
peerId: message.peerId,
|
|
240
|
-
|
|
244
|
+
sessionId: message.sessionId
|
|
241
245
|
});
|
|
242
246
|
break;
|
|
243
247
|
default:
|
package/client.js
CHANGED
|
@@ -8,7 +8,7 @@ class UniWRTCClient {
|
|
|
8
8
|
this.serverUrl = serverUrl;
|
|
9
9
|
this.ws = null;
|
|
10
10
|
this.clientId = null;
|
|
11
|
-
this.
|
|
11
|
+
this.sessionId = null;
|
|
12
12
|
this.peers = new Map();
|
|
13
13
|
this.options = {
|
|
14
14
|
autoReconnect: true,
|
|
@@ -85,21 +85,21 @@ class UniWRTCClient {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
this.
|
|
88
|
+
joinSession(sessionId) {
|
|
89
|
+
this.sessionId = sessionId;
|
|
90
90
|
this.send({
|
|
91
91
|
type: 'join',
|
|
92
|
-
|
|
92
|
+
sessionId: sessionId
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
if (this.
|
|
96
|
+
leaveSession() {
|
|
97
|
+
if (this.sessionId) {
|
|
98
98
|
this.send({
|
|
99
99
|
type: 'leave',
|
|
100
|
-
|
|
100
|
+
sessionId: this.sessionId
|
|
101
101
|
});
|
|
102
|
-
this.
|
|
102
|
+
this.sessionId = null;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -108,7 +108,7 @@ class UniWRTCClient {
|
|
|
108
108
|
type: 'offer',
|
|
109
109
|
offer: offer,
|
|
110
110
|
targetId: targetId,
|
|
111
|
-
|
|
111
|
+
sessionId: this.sessionId
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -117,7 +117,7 @@ class UniWRTCClient {
|
|
|
117
117
|
type: 'answer',
|
|
118
118
|
answer: answer,
|
|
119
119
|
targetId: targetId,
|
|
120
|
-
|
|
120
|
+
sessionId: this.sessionId
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -126,7 +126,7 @@ class UniWRTCClient {
|
|
|
126
126
|
type: 'ice-candidate',
|
|
127
127
|
candidate: candidate,
|
|
128
128
|
targetId: targetId,
|
|
129
|
-
|
|
129
|
+
sessionId: this.sessionId
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -152,18 +152,20 @@ class UniWRTCClient {
|
|
|
152
152
|
break;
|
|
153
153
|
case 'joined':
|
|
154
154
|
this.emit('joined', {
|
|
155
|
-
|
|
155
|
+
sessionId: message.sessionId,
|
|
156
156
|
clientId: message.clientId,
|
|
157
157
|
clients: message.clients
|
|
158
158
|
});
|
|
159
159
|
break;
|
|
160
160
|
case 'peer-joined':
|
|
161
161
|
this.emit('peer-joined', {
|
|
162
|
+
sessionId: message.sessionId,
|
|
162
163
|
peerId: message.peerId
|
|
163
164
|
});
|
|
164
165
|
break;
|
|
165
166
|
case 'peer-left':
|
|
166
167
|
this.emit('peer-left', {
|
|
168
|
+
sessionId: message.sessionId,
|
|
167
169
|
peerId: message.peerId
|
|
168
170
|
});
|
|
169
171
|
break;
|
package/demo.html
CHANGED
|
@@ -282,11 +282,11 @@
|
|
|
282
282
|
</div>
|
|
283
283
|
|
|
284
284
|
<div class="card">
|
|
285
|
-
<h2>Room
|
|
285
|
+
<h2>Room / Session</h2>
|
|
286
286
|
|
|
287
287
|
<div class="connection-controls" style="margin-bottom: 15px;">
|
|
288
|
-
<input type="text" id="roomId" placeholder="Enter room ID" value="demo-room">
|
|
289
|
-
<button class="btn-primary" id="joinBtn" onclick="
|
|
288
|
+
<input type="text" id="roomId" placeholder="Enter room/session ID" value="demo-room">
|
|
289
|
+
<button class="btn-primary" id="joinBtn" onclick="joinSession()" disabled>Join</button>
|
|
290
290
|
</div>
|
|
291
291
|
|
|
292
292
|
<div id="roomInfo">
|
|
@@ -430,18 +430,26 @@
|
|
|
430
430
|
log('Connecting to signaling server...', 'info');
|
|
431
431
|
|
|
432
432
|
const customPeerId = document.getElementById('customPeerId').value.trim();
|
|
433
|
-
|
|
433
|
+
|
|
434
|
+
// For Cloudflare (signal.peer.ooo), append room param to URL
|
|
435
|
+
let finalUrl = serverUrl;
|
|
436
|
+
const roomId = document.getElementById('roomId').value;
|
|
437
|
+
if (serverUrl.includes('signal.peer.ooo') && roomId) {
|
|
438
|
+
finalUrl = serverUrl + (serverUrl.includes('?') ? '&' : '?') + `room=${roomId}`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
client = new UniWRTCClient(finalUrl, { customPeerId: customPeerId || null });
|
|
434
442
|
|
|
435
443
|
client.on('connected', (data) => {
|
|
436
444
|
log(`Connected with ID: ${data.clientId}`, 'success');
|
|
437
445
|
updateStatus(true);
|
|
438
446
|
|
|
439
|
-
// Auto-join
|
|
447
|
+
// Auto-join session after connecting
|
|
440
448
|
const roomId = document.getElementById('roomId').value;
|
|
441
449
|
if (roomId) {
|
|
442
450
|
setTimeout(() => {
|
|
443
|
-
log(`Auto-joining
|
|
444
|
-
client.
|
|
451
|
+
log(`Auto-joining session: ${roomId}...`, 'info');
|
|
452
|
+
client.joinSession(roomId);
|
|
445
453
|
}, 500);
|
|
446
454
|
}
|
|
447
455
|
});
|
|
@@ -455,15 +463,21 @@
|
|
|
455
463
|
let knownPeers = [];
|
|
456
464
|
|
|
457
465
|
client.on('joined', (data) => {
|
|
458
|
-
log(`Joined
|
|
466
|
+
log(`Joined session: ${data.sessionId}`, 'success');
|
|
459
467
|
knownPeers = data.clients || [];
|
|
460
468
|
updatePeersList(knownPeers);
|
|
461
469
|
|
|
462
|
-
// Update
|
|
463
|
-
updateRoomInfo(data.
|
|
470
|
+
// Update session info with dynamic peer count
|
|
471
|
+
updateRoomInfo(data.sessionId, client.clientId);
|
|
464
472
|
});
|
|
465
473
|
|
|
466
474
|
client.on('peer-joined', (data) => {
|
|
475
|
+
// Only connect to peers in the same session (if we have one set)
|
|
476
|
+
if (client.sessionId && data.sessionId !== client.sessionId) {
|
|
477
|
+
log(`Ignoring peer from different session: ${data.peerId}`, 'warning');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
467
481
|
log(`Peer joined: ${data.peerId}`, 'success');
|
|
468
482
|
// Add to known peers if not already there
|
|
469
483
|
if (data.peerId && !knownPeers.includes(data.peerId)) {
|
|
@@ -471,8 +485,8 @@
|
|
|
471
485
|
updatePeersList(knownPeers);
|
|
472
486
|
}
|
|
473
487
|
|
|
474
|
-
// Update
|
|
475
|
-
updateRoomInfo(
|
|
488
|
+
// Update session info with new peer count
|
|
489
|
+
updateRoomInfo(data.sessionId, client.clientId);
|
|
476
490
|
|
|
477
491
|
// Auto-initiate P2P connection
|
|
478
492
|
setTimeout(() => {
|
|
@@ -481,14 +495,19 @@
|
|
|
481
495
|
});
|
|
482
496
|
|
|
483
497
|
client.on('peer-left', (data) => {
|
|
498
|
+
// Only process if from current session (if we have one set)
|
|
499
|
+
if (client.sessionId && data.sessionId !== client.sessionId) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
484
503
|
const peerId = data.peerId;
|
|
485
504
|
log(`Peer left: ${peerId}`, 'warning');
|
|
486
505
|
// Remove from known peers
|
|
487
506
|
knownPeers = knownPeers.filter(id => id !== peerId);
|
|
488
507
|
updatePeersList(knownPeers);
|
|
489
508
|
|
|
490
|
-
// Update
|
|
491
|
-
updateRoomInfo(
|
|
509
|
+
// Update session info with new peer count
|
|
510
|
+
updateRoomInfo(data.sessionId, client.clientId);
|
|
492
511
|
|
|
493
512
|
// Close peer connection
|
|
494
513
|
const pc = peerConnections.get(peerId);
|
|
@@ -580,8 +599,8 @@
|
|
|
580
599
|
return;
|
|
581
600
|
}
|
|
582
601
|
|
|
583
|
-
if (!client || !client.
|
|
584
|
-
log('Not connected to a
|
|
602
|
+
if (!client || !client.sessionId) {
|
|
603
|
+
log('Not connected to a session', 'error');
|
|
585
604
|
return;
|
|
586
605
|
}
|
|
587
606
|
|
|
@@ -590,10 +609,10 @@
|
|
|
590
609
|
document.getElementById('manualPeerId').value = '';
|
|
591
610
|
}
|
|
592
611
|
|
|
593
|
-
function
|
|
594
|
-
const
|
|
612
|
+
function joinSession() {
|
|
613
|
+
const sessionId = document.getElementById('roomId').value;
|
|
595
614
|
|
|
596
|
-
if (!
|
|
615
|
+
if (!sessionId) {
|
|
597
616
|
log('Please enter a room ID', 'error');
|
|
598
617
|
return;
|
|
599
618
|
}
|
|
@@ -603,8 +622,8 @@
|
|
|
603
622
|
return;
|
|
604
623
|
}
|
|
605
624
|
|
|
606
|
-
log(`Joining
|
|
607
|
-
client.
|
|
625
|
+
log(`Joining session: ${sessionId}...`, 'info');
|
|
626
|
+
client.joinSession(sessionId);
|
|
608
627
|
}
|
|
609
628
|
|
|
610
629
|
function listRooms() {
|
|
@@ -619,12 +638,15 @@
|
|
|
619
638
|
|
|
620
639
|
async function createPeerConnection(peerId, initiator = false) {
|
|
621
640
|
if (peerConnections.has(peerId)) {
|
|
641
|
+
log(`Peer connection already exists for ${peerId.substring(0, 6)}`, 'info');
|
|
622
642
|
return peerConnections.get(peerId);
|
|
623
643
|
}
|
|
624
644
|
|
|
625
645
|
// Determine who should initiate based on peer IDs (lexicographic comparison)
|
|
626
646
|
// Only the peer with smaller ID creates the offer
|
|
627
647
|
const shouldInitiate = client.clientId < peerId;
|
|
648
|
+
|
|
649
|
+
log(`Creating peer connection with ${peerId.substring(0, 6)} (shouldInitiate: ${shouldInitiate})`, 'info');
|
|
628
650
|
|
|
629
651
|
const pc = new RTCPeerConnection({
|
|
630
652
|
iceServers: [
|
|
@@ -640,6 +662,7 @@
|
|
|
640
662
|
};
|
|
641
663
|
|
|
642
664
|
pc.ondatachannel = (event) => {
|
|
665
|
+
log(`Received data channel from ${peerId.substring(0, 6)}`, 'info');
|
|
643
666
|
setupDataChannel(peerId, event.channel);
|
|
644
667
|
};
|
|
645
668
|
|
|
@@ -651,6 +674,8 @@
|
|
|
651
674
|
await pc.setLocalDescription(offer);
|
|
652
675
|
client.sendOffer(offer, peerId);
|
|
653
676
|
log(`Sent offer to ${peerId.substring(0, 6)}...`, 'success');
|
|
677
|
+
} else {
|
|
678
|
+
log(`Waiting for offer from ${peerId.substring(0, 6)}...`, 'info');
|
|
654
679
|
}
|
|
655
680
|
|
|
656
681
|
peerConnections.set(peerId, pc);
|
|
@@ -660,7 +685,6 @@
|
|
|
660
685
|
function setupDataChannel(peerId, dataChannel) {
|
|
661
686
|
dataChannel.onopen = () => {
|
|
662
687
|
log(`Data channel open with ${peerId.substring(0, 6)}...`, 'success');
|
|
663
|
-
dataChannels.set(peerId, dataChannel);
|
|
664
688
|
};
|
|
665
689
|
|
|
666
690
|
dataChannel.onmessage = (event) => {
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -54,9 +54,12 @@ function log(message, data = '') {
|
|
|
54
54
|
console.log(`[${timestamp}] ${message}`, data);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function broadcastToRoom(
|
|
58
|
-
const room = rooms.get(
|
|
59
|
-
if (!room)
|
|
57
|
+
function broadcastToRoom(sessionId, message, excludeClient = null) {
|
|
58
|
+
const room = rooms.get(sessionId);
|
|
59
|
+
if (!room) {
|
|
60
|
+
log(`WARNING: Attempted to broadcast to non-existent session: ${sessionId}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
60
63
|
|
|
61
64
|
room.clients.forEach(client => {
|
|
62
65
|
if (client !== excludeClient && client.readyState === WebSocket.OPEN) {
|
|
@@ -164,33 +167,33 @@ function handleSetId(ws, message) {
|
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
function handleJoin(ws, message) {
|
|
167
|
-
const {
|
|
170
|
+
const { sessionId } = message;
|
|
168
171
|
|
|
169
|
-
if (!
|
|
170
|
-
sendToClient(ws, { type: 'error', message: '
|
|
172
|
+
if (!sessionId) {
|
|
173
|
+
sendToClient(ws, { type: 'error', message: 'Session ID is required' });
|
|
171
174
|
return;
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
// Leave current
|
|
177
|
+
// Leave current session if in one
|
|
175
178
|
if (ws.room) {
|
|
176
|
-
handleLeave(ws, {
|
|
179
|
+
handleLeave(ws, { sessionId: ws.room });
|
|
177
180
|
}
|
|
178
181
|
|
|
179
|
-
// Create
|
|
180
|
-
if (!rooms.has(
|
|
181
|
-
rooms.set(
|
|
182
|
-
id:
|
|
182
|
+
// Create session if it doesn't exist
|
|
183
|
+
if (!rooms.has(sessionId)) {
|
|
184
|
+
rooms.set(sessionId, {
|
|
185
|
+
id: sessionId,
|
|
183
186
|
clients: new Set(),
|
|
184
187
|
createdAt: Date.now()
|
|
185
188
|
});
|
|
186
|
-
log('
|
|
189
|
+
log('Session created:', sessionId);
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
const room = rooms.get(
|
|
192
|
+
const room = rooms.get(sessionId);
|
|
190
193
|
room.clients.add(ws);
|
|
191
|
-
ws.room =
|
|
194
|
+
ws.room = sessionId;
|
|
192
195
|
|
|
193
|
-
log(`Client ${ws.clientId} joined
|
|
196
|
+
log(`Client ${ws.clientId} joined session ${sessionId}`);
|
|
194
197
|
|
|
195
198
|
// Get list of existing clients in the room
|
|
196
199
|
const existingClients = Array.from(room.clients)
|
|
@@ -200,50 +203,52 @@ function handleJoin(ws, message) {
|
|
|
200
203
|
// Notify the joining client
|
|
201
204
|
sendToClient(ws, {
|
|
202
205
|
type: 'joined',
|
|
203
|
-
|
|
206
|
+
sessionId: sessionId,
|
|
204
207
|
clientId: ws.clientId,
|
|
205
208
|
clients: existingClients
|
|
206
209
|
});
|
|
207
210
|
|
|
208
|
-
// Notify other clients in the
|
|
209
|
-
broadcastToRoom(
|
|
211
|
+
// Notify other clients in the session
|
|
212
|
+
broadcastToRoom(sessionId, {
|
|
210
213
|
type: 'peer-joined',
|
|
214
|
+
sessionId: sessionId,
|
|
211
215
|
peerId: ws.clientId
|
|
212
216
|
}, ws);
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
function handleLeave(ws, message) {
|
|
216
|
-
const {
|
|
220
|
+
const { sessionId } = message;
|
|
217
221
|
|
|
218
|
-
if (!
|
|
222
|
+
if (!sessionId || !rooms.has(sessionId)) {
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
221
225
|
|
|
222
|
-
const room = rooms.get(
|
|
226
|
+
const room = rooms.get(sessionId);
|
|
223
227
|
room.clients.delete(ws);
|
|
224
228
|
|
|
225
|
-
log(`Client ${ws.clientId} left
|
|
229
|
+
log(`Client ${ws.clientId} left session ${sessionId}`);
|
|
226
230
|
|
|
227
231
|
// Notify other clients
|
|
228
|
-
broadcastToRoom(
|
|
232
|
+
broadcastToRoom(sessionId, {
|
|
229
233
|
type: 'peer-left',
|
|
234
|
+
sessionId: sessionId,
|
|
230
235
|
peerId: ws.clientId
|
|
231
236
|
});
|
|
232
237
|
|
|
233
|
-
// Clean up empty
|
|
238
|
+
// Clean up empty sessions
|
|
234
239
|
if (room.clients.size === 0) {
|
|
235
|
-
rooms.delete(
|
|
236
|
-
log('
|
|
240
|
+
rooms.delete(sessionId);
|
|
241
|
+
log('Session deleted:', sessionId);
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
ws.room = null;
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
function handleSignaling(ws, message) {
|
|
243
|
-
const { targetId,
|
|
248
|
+
const { targetId, sessionId } = message;
|
|
244
249
|
|
|
245
250
|
if (!ws.room) {
|
|
246
|
-
sendToClient(ws, { type: 'error', message: 'Not in a
|
|
251
|
+
sendToClient(ws, { type: 'error', message: 'Not in a session' });
|
|
247
252
|
return;
|
|
248
253
|
}
|
|
249
254
|
|
|
@@ -272,29 +277,29 @@ function handleSignaling(ws, message) {
|
|
|
272
277
|
}
|
|
273
278
|
|
|
274
279
|
function handleChat(ws, message) {
|
|
275
|
-
const {
|
|
280
|
+
const { sessionId, text } = message;
|
|
276
281
|
|
|
277
|
-
if (!
|
|
278
|
-
sendToClient(ws, { type: 'error', message: '
|
|
282
|
+
if (!sessionId || !text) {
|
|
283
|
+
sendToClient(ws, { type: 'error', message: 'Session ID and text are required' });
|
|
279
284
|
return;
|
|
280
285
|
}
|
|
281
286
|
|
|
282
|
-
log(`Chat message in
|
|
287
|
+
log(`Chat message in session ${sessionId}: ${text.substring(0, 50)}`);
|
|
283
288
|
|
|
284
|
-
const room = rooms.get(
|
|
289
|
+
const room = rooms.get(sessionId);
|
|
285
290
|
if (!room) {
|
|
286
|
-
sendToClient(ws, { type: 'error', message: '
|
|
291
|
+
sendToClient(ws, { type: 'error', message: 'Session not found' });
|
|
287
292
|
return;
|
|
288
293
|
}
|
|
289
294
|
|
|
290
|
-
// Broadcast chat to ALL clients in the
|
|
295
|
+
// Broadcast chat to ALL clients in the session (including sender)
|
|
291
296
|
room.clients.forEach(client => {
|
|
292
297
|
if (client.readyState === WebSocket.OPEN) {
|
|
293
298
|
client.send(JSON.stringify({
|
|
294
299
|
type: 'chat',
|
|
295
300
|
text: text,
|
|
296
301
|
peerId: ws.clientId,
|
|
297
|
-
|
|
302
|
+
sessionId: sessionId
|
|
298
303
|
}));
|
|
299
304
|
}
|
|
300
305
|
});
|
package/src/client-cloudflare.js
CHANGED
|
@@ -109,14 +109,14 @@ class UniWRTCClient {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
// Durable Objects handle
|
|
112
|
+
joinSession(sessionId) {
|
|
113
|
+
this.sessionId = sessionId;
|
|
114
|
+
// Durable Objects handle session joining automatically via room parameter
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
if (this.
|
|
119
|
-
this.
|
|
117
|
+
leaveSession() {
|
|
118
|
+
if (this.sessionId) {
|
|
119
|
+
this.sessionId = null;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -188,20 +188,22 @@ class UniWRTCClient {
|
|
|
188
188
|
console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
|
|
189
189
|
break;
|
|
190
190
|
case 'joined':
|
|
191
|
-
this.
|
|
191
|
+
this.sessionId = message.sessionId;
|
|
192
192
|
this.emit('joined', {
|
|
193
|
-
|
|
193
|
+
sessionId: message.sessionId,
|
|
194
194
|
clientId: message.clientId,
|
|
195
195
|
clients: message.clients
|
|
196
196
|
});
|
|
197
197
|
break;
|
|
198
198
|
case 'peer-joined':
|
|
199
199
|
this.emit('peer-joined', {
|
|
200
|
+
sessionId: message.sessionId,
|
|
200
201
|
peerId: message.peerId
|
|
201
202
|
});
|
|
202
203
|
break;
|
|
203
204
|
case 'peer-left':
|
|
204
205
|
this.emit('peer-left', {
|
|
206
|
+
sessionId: message.sessionId,
|
|
205
207
|
peerId: message.peerId
|
|
206
208
|
});
|
|
207
209
|
break;
|
|
@@ -237,7 +239,7 @@ class UniWRTCClient {
|
|
|
237
239
|
this.emit('chat', {
|
|
238
240
|
text: message.text,
|
|
239
241
|
peerId: message.peerId,
|
|
240
|
-
|
|
242
|
+
sessionId: message.sessionId
|
|
241
243
|
});
|
|
242
244
|
break;
|
|
243
245
|
default:
|
package/src/room.js
CHANGED
|
@@ -17,7 +17,7 @@ export class Room {
|
|
|
17
17
|
const clientId = crypto.randomUUID().substring(0, 9);
|
|
18
18
|
this.clients.set(clientId, server);
|
|
19
19
|
|
|
20
|
-
console.log(`[Room] Client ${clientId}
|
|
20
|
+
console.log(`[Room] Client ${clientId} connected (total: ${this.clients.size})`);
|
|
21
21
|
|
|
22
22
|
// Send welcome message
|
|
23
23
|
server.send(JSON.stringify({
|
|
@@ -26,11 +26,7 @@ export class Room {
|
|
|
26
26
|
message: 'Connected to UniWRTC signaling room'
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
this.broadcast({
|
|
31
|
-
type: 'peer-joined',
|
|
32
|
-
clientId: clientId
|
|
33
|
-
}, clientId);
|
|
29
|
+
// NOTE: peer-joined is sent when client explicitly joins via 'join' message
|
|
34
30
|
|
|
35
31
|
server.onmessage = async (event) => {
|
|
36
32
|
try {
|
|
@@ -45,10 +41,10 @@ export class Room {
|
|
|
45
41
|
server.onclose = () => {
|
|
46
42
|
console.log(`[Room] Client ${clientId} left`);
|
|
47
43
|
this.clients.delete(clientId);
|
|
44
|
+
// Note: sessionId should be tracked per client if needed
|
|
48
45
|
this.broadcast({
|
|
49
46
|
type: 'peer-left',
|
|
50
|
-
peerId: clientId
|
|
51
|
-
clientId: clientId
|
|
47
|
+
peerId: clientId
|
|
52
48
|
});
|
|
53
49
|
};
|
|
54
50
|
|
|
@@ -81,7 +77,7 @@ export class Room {
|
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
async handleJoin(clientId, message) {
|
|
84
|
-
const {
|
|
80
|
+
const { sessionId, peerId } = message;
|
|
85
81
|
|
|
86
82
|
// Get list of other peers
|
|
87
83
|
const peers = Array.from(this.clients.keys())
|
|
@@ -92,7 +88,7 @@ export class Room {
|
|
|
92
88
|
// Send joined confirmation (align with server schema)
|
|
93
89
|
client.send(JSON.stringify({
|
|
94
90
|
type: 'joined',
|
|
95
|
-
|
|
91
|
+
sessionId: sessionId,
|
|
96
92
|
clientId: clientId,
|
|
97
93
|
clients: peers
|
|
98
94
|
}));
|
|
@@ -101,6 +97,7 @@ export class Room {
|
|
|
101
97
|
// Notify other peers
|
|
102
98
|
this.broadcast({
|
|
103
99
|
type: 'peer-joined',
|
|
100
|
+
sessionId: sessionId,
|
|
104
101
|
peerId: clientId
|
|
105
102
|
}, clientId);
|
|
106
103
|
}
|