uniwrtc 1.0.0

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.
@@ -0,0 +1,113 @@
1
+ @echo off
2
+ REM UniWRTC Cloudflare Automated Setup Script (Windows)
3
+
4
+ setlocal enabledelayedexpansion
5
+
6
+ echo ๐Ÿš€ UniWRTC Cloudflare Setup
7
+ echo ============================
8
+ echo.
9
+
10
+ REM Check Node.js
11
+ where node >nul 2>nul
12
+ if errorlevel 1 (
13
+ echo โŒ Node.js not found. Please install Node.js v16+
14
+ exit /b 1
15
+ )
16
+
17
+ REM Check/Install Wrangler
18
+ where wrangler >nul 2>nul
19
+ if errorlevel 1 (
20
+ echo ๐Ÿ“ฆ Installing Wrangler CLI...
21
+ call npm install -g wrangler
22
+ )
23
+
24
+ echo โœ… Prerequisites OK
25
+ echo.
26
+
27
+ REM Install dependencies
28
+ echo ๐Ÿ“ฆ Installing dependencies...
29
+ call npm install
30
+ echo โœ… Dependencies installed
31
+ echo.
32
+
33
+ REM Check authentication
34
+ echo ๐Ÿ” Checking Cloudflare authentication...
35
+ call wrangler whoami >nul 2>nul
36
+ if errorlevel 1 (
37
+ echo โš ๏ธ Not logged in to Cloudflare. Running login...
38
+ call wrangler login
39
+ )
40
+ echo โœ… Authenticated with Cloudflare
41
+ echo.
42
+
43
+ REM Ask for domain
44
+ echo ๐ŸŒ Domain Configuration
45
+ echo =====================
46
+ set /p DOMAIN="Enter your Cloudflare domain (e.g., peer.ooo): "
47
+ set /p SUBDOMAIN="Enter subdomain for signaling (e.g., signal): "
48
+
49
+ if "!DOMAIN!"=="" (
50
+ echo โŒ Domain required
51
+ exit /b 1
52
+ )
53
+
54
+ if "!SUBDOMAIN!"=="" (
55
+ echo โŒ Subdomain required
56
+ exit /b 1
57
+ )
58
+
59
+ set FULL_DOMAIN=!SUBDOMAIN!.!DOMAIN!
60
+
61
+ REM Update wrangler.toml
62
+ echo ๐Ÿ“ Updating wrangler.toml...
63
+ (
64
+ echo name = "uniwrtc"
65
+ echo main = "src/index.js"
66
+ echo compatibility_date = "2024-12-20"
67
+ echo.
68
+ echo [env.production]
69
+ echo routes = [
70
+ echo { pattern = "!FULL_DOMAIN!/*", zone_name = "!DOMAIN!" }
71
+ echo ]
72
+ echo.
73
+ echo [[durable_objects.bindings]]
74
+ echo name = "ROOMS"
75
+ echo class_name = "Room"
76
+ echo.
77
+ echo [durable_objects]
78
+ echo migrations = [
79
+ echo { tag = "v1", new_classes = ["Room"] }
80
+ echo ]
81
+ echo.
82
+ echo [build]
83
+ echo command = "npm install"
84
+ ) > wrangler.toml
85
+
86
+ echo โœ… wrangler.toml updated
87
+ echo.
88
+
89
+ REM Deploy
90
+ echo ๐Ÿš€ Deploying to Cloudflare...
91
+ echo.
92
+ call wrangler deploy --env production
93
+
94
+ echo.
95
+ echo โœ… Deployment Complete!
96
+ echo.
97
+ echo ๐ŸŽ‰ Your UniWRTC signaling server is live at:
98
+ echo https://!FULL_DOMAIN!/
99
+ echo.
100
+ echo ๐Ÿ“Š Test it:
101
+ echo curl https://!FULL_DOMAIN!/health
102
+ echo.
103
+ echo ๐Ÿงช Local testing:
104
+ echo wrangler dev
105
+ echo.
106
+ echo ๐Ÿ“Š View logs:
107
+ echo wrangler tail --env production
108
+ echo.
109
+ echo ๐Ÿ› ๏ธ Next: Update demo.html to use:
110
+ echo const serverUrl = 'https://!FULL_DOMAIN!/';
111
+ echo.
112
+
113
+ endlocal
@@ -0,0 +1,100 @@
1
+ #!/bin/bash
2
+
3
+ # UniWRTC Cloudflare Automated Setup Script
4
+ # Run this to setup and deploy to Cloudflare
5
+
6
+ set -e
7
+
8
+ echo "๐Ÿš€ UniWRTC Cloudflare Setup"
9
+ echo "============================"
10
+ echo ""
11
+
12
+ # Check prerequisites
13
+ echo "๐Ÿ“‹ Checking prerequisites..."
14
+
15
+ if ! command -v node &> /dev/null; then
16
+ echo "โŒ Node.js not found. Please install Node.js v16+"
17
+ exit 1
18
+ fi
19
+
20
+ if ! command -v wrangler &> /dev/null; then
21
+ echo "๐Ÿ“ฆ Installing Wrangler CLI..."
22
+ npm install -g wrangler
23
+ fi
24
+
25
+ echo "โœ… Prerequisites OK"
26
+ echo ""
27
+
28
+ # Install dependencies
29
+ echo "๐Ÿ“ฆ Installing dependencies..."
30
+ cd "$(dirname "$0")"
31
+ npm install || true
32
+ echo "โœ… Dependencies installed"
33
+ echo ""
34
+
35
+ # Check Cloudflare authentication
36
+ echo "๐Ÿ” Checking Cloudflare authentication..."
37
+ if ! wrangler whoami &> /dev/null; then
38
+ echo "โš ๏ธ Not logged in to Cloudflare. Running login..."
39
+ wrangler login
40
+ fi
41
+ echo "โœ… Authenticated with Cloudflare"
42
+ echo ""
43
+
44
+ # Domain configuration
45
+ DOMAIN="peer.ooo"
46
+ SUBDOMAIN="signal"
47
+
48
+ FULL_DOMAIN="${SUBDOMAIN}.${DOMAIN}"
49
+
50
+ echo "๐Ÿ“ Updating wrangler.toml..."
51
+ cat > wrangler.toml << EOF
52
+ name = "uniwrtc"
53
+ main = "src/index.js"
54
+ compatibility_date = "2024-12-20"
55
+
56
+ [env.production]
57
+ routes = [
58
+ { pattern = "${FULL_DOMAIN}/*", zone_name = "${DOMAIN}" }
59
+ ]
60
+
61
+ [[durable_objects.bindings]]
62
+ name = "ROOMS"
63
+ class_name = "Room"
64
+
65
+ [durable_objects]
66
+ migrations = [
67
+ { tag = "v1", new_classes = ["Room"] }
68
+ ]
69
+
70
+ [build]
71
+ command = "npm install"
72
+ EOF
73
+
74
+ echo "โœ… wrangler.toml updated"
75
+ echo ""
76
+
77
+ # Deploy
78
+ echo "๐Ÿš€ Deploying to Cloudflare..."
79
+ echo ""
80
+ echo "Deploying to production..."
81
+ wrangler deploy --env production
82
+
83
+ echo ""
84
+ echo "โœ… Deployment Complete!"
85
+ echo ""
86
+ echo "๐ŸŽ‰ Your UniWRTC signaling server is live at:"
87
+ echo " https://${FULL_DOMAIN}/"
88
+ echo ""
89
+ echo "๐Ÿ“Š Test it:"
90
+ echo " curl https://${FULL_DOMAIN}/health"
91
+ echo ""
92
+ echo "๐Ÿงช Local testing:"
93
+ echo " wrangler dev"
94
+ echo ""
95
+ echo "๐Ÿ“Š View logs:"
96
+ echo " wrangler tail --env production"
97
+ echo ""
98
+ echo "๐Ÿ› ๏ธ Next: Update demo.html to use:"
99
+ echo " const serverUrl = 'https://${FULL_DOMAIN}/';"
100
+ echo ""
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "uniwrtc-cloudflare",
3
+ "version": "1.0.0",
4
+ "description": "UniWRTC Cloudflare Workers deployment",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler publish",
9
+ "deploy-prod": "wrangler publish --env production",
10
+ "tail": "wrangler tail",
11
+ "tail-prod": "wrangler tail --env production"
12
+ },
13
+ "devDependencies": {
14
+ "wrangler": "^3.26.0"
15
+ }
16
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "uniwrtc",
3
+ "version": "1.0.0",
4
+ "description": "A universal WebRTC signaling service",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "node server.js",
9
+ "test": "node test.js"
10
+ },
11
+ "keywords": [
12
+ "webrtc",
13
+ "signaling",
14
+ "websocket",
15
+ "peer-to-peer",
16
+ "real-time"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "ws": "^8.17.1"
22
+ },
23
+ "engines": {
24
+ "node": ">=14.0.0"
25
+ }
26
+ }
package/server.js ADDED
@@ -0,0 +1,350 @@
1
+ const WebSocket = require('ws');
2
+ const http = require('http');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const PORT = process.env.PORT || 8080;
7
+
8
+ // Create HTTP server
9
+ const server = http.createServer((req, res) => {
10
+ if (req.url === '/health') {
11
+ res.writeHead(200, { 'Content-Type': 'application/json' });
12
+ res.end(JSON.stringify({ status: 'ok', connections: wss.clients.size }));
13
+ } else if (req.url === '/' || req.url === '/demo.html') {
14
+ const filePath = path.join(__dirname, 'demo.html');
15
+ fs.readFile(filePath, 'utf-8', (err, data) => {
16
+ if (err) {
17
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
18
+ res.end('Demo not found');
19
+ } else {
20
+ res.writeHead(200, { 'Content-Type': 'text/html' });
21
+ res.end(data);
22
+ }
23
+ });
24
+ } else if (req.url === '/client-browser.js') {
25
+ const filePath = path.join(__dirname, 'client-browser.js');
26
+ fs.readFile(filePath, 'utf-8', (err, data) => {
27
+ if (err) {
28
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
29
+ res.end('Client not found');
30
+ } else {
31
+ res.writeHead(200, {
32
+ 'Content-Type': 'application/javascript',
33
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
34
+ 'Pragma': 'no-cache',
35
+ 'Expires': '0'
36
+ });
37
+ res.end(data);
38
+ }
39
+ });
40
+ } else {
41
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
42
+ res.end('UniWRTC Signaling Server');
43
+ }
44
+ });
45
+
46
+ // Create WebSocket server
47
+ const wss = new WebSocket.Server({ server });
48
+
49
+ // Store rooms and their participants
50
+ const rooms = new Map();
51
+
52
+ function log(message, data = '') {
53
+ const timestamp = new Date().toISOString();
54
+ console.log(`[${timestamp}] ${message}`, data);
55
+ }
56
+
57
+ function broadcastToRoom(roomId, message, excludeClient = null) {
58
+ const room = rooms.get(roomId);
59
+ if (!room) return;
60
+
61
+ room.clients.forEach(client => {
62
+ if (client !== excludeClient && client.readyState === WebSocket.OPEN) {
63
+ client.send(JSON.stringify(message));
64
+ }
65
+ });
66
+ }
67
+
68
+ function sendToClient(client, message) {
69
+ if (client.readyState === WebSocket.OPEN) {
70
+ client.send(JSON.stringify(message));
71
+ }
72
+ }
73
+
74
+ wss.on('connection', (ws) => {
75
+ log('New connection established');
76
+
77
+ ws.clientId = generateClientId();
78
+ ws.room = null;
79
+
80
+ ws.on('message', (data) => {
81
+ try {
82
+ const message = JSON.parse(data);
83
+ log('Message received:', message.type);
84
+
85
+ switch (message.type) {
86
+ case 'set-id':
87
+ handleSetId(ws, message);
88
+ break;
89
+ case 'join':
90
+ handleJoin(ws, message);
91
+ break;
92
+ case 'leave':
93
+ handleLeave(ws, message);
94
+ break;
95
+ case 'offer':
96
+ case 'answer':
97
+ case 'ice-candidate':
98
+ handleSignaling(ws, message);
99
+ break;
100
+ case 'chat':
101
+ handleChat(ws, message);
102
+ break;
103
+ case 'list-rooms':
104
+ handleListRooms(ws);
105
+ break;
106
+ default:
107
+ sendToClient(ws, { type: 'error', message: 'Unknown message type' });
108
+ }
109
+ } catch (error) {
110
+ log('Error processing message:', error.message);
111
+ sendToClient(ws, { type: 'error', message: 'Invalid message format' });
112
+ }
113
+ });
114
+
115
+ ws.on('close', () => {
116
+ log('Connection closed', ws.clientId);
117
+ if (ws.room) {
118
+ handleLeave(ws, { roomId: ws.room });
119
+ }
120
+ });
121
+
122
+ ws.on('error', (error) => {
123
+ log('WebSocket error:', error.message);
124
+ });
125
+
126
+ // Send welcome message
127
+ sendToClient(ws, {
128
+ type: 'welcome',
129
+ clientId: ws.clientId,
130
+ message: 'Connected to UniWRTC signaling server'
131
+ });
132
+ });
133
+
134
+ function handleSetId(ws, message) {
135
+ const { customId } = message;
136
+
137
+ if (!customId || customId.length < 3 || customId.length > 20) {
138
+ sendToClient(ws, { type: 'error', message: 'Custom ID must be between 3-20 characters' });
139
+ return;
140
+ }
141
+
142
+ // Check if ID is already taken
143
+ let idTaken = false;
144
+ wss.clients.forEach(client => {
145
+ if (client.clientId === customId && client !== ws) {
146
+ idTaken = true;
147
+ }
148
+ });
149
+
150
+ if (idTaken) {
151
+ sendToClient(ws, { type: 'error', message: 'Peer ID already taken' });
152
+ return;
153
+ }
154
+
155
+ log(`Client ${ws.clientId} changed ID to ${customId}`);
156
+ ws.clientId = customId;
157
+
158
+ // Send updated welcome message
159
+ sendToClient(ws, {
160
+ type: 'welcome',
161
+ clientId: ws.clientId,
162
+ message: 'Custom peer ID set'
163
+ });
164
+ }
165
+
166
+ function handleJoin(ws, message) {
167
+ const { roomId } = message;
168
+
169
+ if (!roomId) {
170
+ sendToClient(ws, { type: 'error', message: 'Room ID is required' });
171
+ return;
172
+ }
173
+
174
+ // Leave current room if in one
175
+ if (ws.room) {
176
+ handleLeave(ws, { roomId: ws.room });
177
+ }
178
+
179
+ // Create room if it doesn't exist
180
+ if (!rooms.has(roomId)) {
181
+ rooms.set(roomId, {
182
+ id: roomId,
183
+ clients: new Set(),
184
+ createdAt: Date.now()
185
+ });
186
+ log('Room created:', roomId);
187
+ }
188
+
189
+ const room = rooms.get(roomId);
190
+ room.clients.add(ws);
191
+ ws.room = roomId;
192
+
193
+ log(`Client ${ws.clientId} joined room ${roomId}`);
194
+
195
+ // Get list of existing clients in the room
196
+ const existingClients = Array.from(room.clients)
197
+ .filter(client => client !== ws)
198
+ .map(client => client.clientId);
199
+
200
+ // Notify the joining client
201
+ sendToClient(ws, {
202
+ type: 'joined',
203
+ roomId: roomId,
204
+ clientId: ws.clientId,
205
+ clients: existingClients
206
+ });
207
+
208
+ // Notify other clients in the room
209
+ broadcastToRoom(roomId, {
210
+ type: 'peer-joined',
211
+ peerId: ws.clientId,
212
+ clientId: ws.clientId
213
+ }, ws);
214
+ }
215
+
216
+ function handleLeave(ws, message) {
217
+ const { roomId } = message;
218
+
219
+ if (!roomId || !rooms.has(roomId)) {
220
+ return;
221
+ }
222
+
223
+ const room = rooms.get(roomId);
224
+ room.clients.delete(ws);
225
+
226
+ log(`Client ${ws.clientId} left room ${roomId}`);
227
+
228
+ // Notify other clients
229
+ broadcastToRoom(roomId, {
230
+ type: 'peer-left',
231
+ clientId: ws.clientId
232
+ });
233
+
234
+ // Clean up empty rooms
235
+ if (room.clients.size === 0) {
236
+ rooms.delete(roomId);
237
+ log('Room deleted:', roomId);
238
+ }
239
+
240
+ ws.room = null;
241
+ }
242
+
243
+ function handleSignaling(ws, message) {
244
+ const { targetId, roomId } = message;
245
+
246
+ if (!ws.room) {
247
+ sendToClient(ws, { type: 'error', message: 'Not in a room' });
248
+ return;
249
+ }
250
+
251
+ if (!targetId) {
252
+ // Broadcast to all in room if no specific target
253
+ broadcastToRoom(ws.room, {
254
+ ...message,
255
+ peerId: ws.clientId
256
+ }, ws);
257
+ return;
258
+ }
259
+
260
+ // Send to specific client
261
+ const room = rooms.get(ws.room);
262
+ if (!room) return;
263
+
264
+ for (const client of room.clients) {
265
+ if (client.clientId === targetId) {
266
+ sendToClient(client, {
267
+ ...message,
268
+ peerId: ws.clientId
269
+ });
270
+ break;
271
+ }
272
+ }
273
+ }
274
+
275
+ function handleChat(ws, message) {
276
+ const { roomId, text } = message;
277
+
278
+ if (!roomId || !text) {
279
+ sendToClient(ws, { type: 'error', message: 'Room ID and text are required' });
280
+ return;
281
+ }
282
+
283
+ log(`Chat message in room ${roomId}: ${text.substring(0, 50)}`);
284
+
285
+ const room = rooms.get(roomId);
286
+ if (!room) {
287
+ sendToClient(ws, { type: 'error', message: 'Room not found' });
288
+ return;
289
+ }
290
+
291
+ // Broadcast chat to ALL clients in the room (including sender)
292
+ room.clients.forEach(client => {
293
+ if (client.readyState === WebSocket.OPEN) {
294
+ client.send(JSON.stringify({
295
+ type: 'chat',
296
+ text: text,
297
+ senderId: ws.clientId,
298
+ roomId: roomId
299
+ }));
300
+ }
301
+ });
302
+ }
303
+
304
+ function handleListRooms(ws) {
305
+ const roomList = Array.from(rooms.entries()).map(([id, room]) => ({
306
+ id,
307
+ clients: room.clients.size,
308
+ createdAt: room.createdAt
309
+ }));
310
+
311
+ sendToClient(ws, {
312
+ type: 'room-list',
313
+ rooms: roomList
314
+ });
315
+ }
316
+
317
+ function generateClientId() {
318
+ return Math.random().toString(36).substring(2, 11);
319
+ }
320
+
321
+ server.listen(PORT, () => {
322
+ log(`UniWRTC Signaling Server listening on port ${PORT}`);
323
+ console.log(`\n>>> Demo available at: http://localhost:${PORT}\n`);
324
+ });
325
+
326
+ // Graceful shutdown
327
+ process.on('SIGTERM', () => {
328
+ log('SIGTERM received, closing server...');
329
+ server.close(() => {
330
+ log('Server closed');
331
+ process.exit(0);
332
+ });
333
+ });
334
+
335
+ process.on('SIGINT', () => {
336
+ log('SIGINT received, closing server...');
337
+ // Close all WebSocket connections
338
+ wss.clients.forEach(client => {
339
+ client.close();
340
+ });
341
+ server.close(() => {
342
+ log('Server closed');
343
+ process.exit(0);
344
+ });
345
+ // Force exit after 5 seconds if still hanging
346
+ setTimeout(() => {
347
+ log('Forced shutdown');
348
+ process.exit(1);
349
+ }, 5000);
350
+ });