uniwrtc 1.0.8 → 1.1.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.
- package/NOSTR_INTEGRATION.md +6 -0
- package/client-browser.js +254 -2
- package/client.js +136 -178
- package/package.json +4 -2
- package/src/client-cloudflare.js +119 -138
- package/src/main.js +102 -64
- package/src/nostr/nostrClient.js +243 -0
- package/src/nostr/useNostr.js +6 -0
- package/src/nostr/useWebRTC.js +6 -0
- package/src/services/nostrService.js +129 -0
- package/simple-peer-adapter.js +0 -124
package/client-browser.js
CHANGED
|
@@ -1,13 +1,265 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UniWRTC Client - WebRTC Signaling Client Library
|
|
3
|
-
* Browser version
|
|
3
|
+
* Browser-only version
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
class UniWRTCClient {
|
|
7
|
+
constructor(serverUrl, options = {}) {
|
|
8
|
+
this.serverUrl = serverUrl;
|
|
9
|
+
this.ws = null;
|
|
10
|
+
this.clientId = null;
|
|
11
|
+
this.sessionId = null;
|
|
12
|
+
this.peers = new Map();
|
|
13
|
+
this._connectedOnce = false;
|
|
14
|
+
this.options = {
|
|
15
|
+
autoReconnect: true,
|
|
16
|
+
reconnectDelay: 3000,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
this.eventHandlers = {
|
|
20
|
+
'connected': [],
|
|
21
|
+
'disconnected': [],
|
|
22
|
+
'joined': [],
|
|
23
|
+
'peer-joined': [],
|
|
24
|
+
'peer-left': [],
|
|
25
|
+
'offer': [],
|
|
26
|
+
'answer': [],
|
|
27
|
+
'ice-candidate': [],
|
|
28
|
+
'room-list': [],
|
|
29
|
+
'error': []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
connect() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
try {
|
|
36
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
37
|
+
|
|
38
|
+
this.ws.onopen = () => {
|
|
39
|
+
console.log('Connected to signaling server');
|
|
40
|
+
|
|
41
|
+
// Send custom peer ID if provided
|
|
42
|
+
if (this.options.customPeerId) {
|
|
43
|
+
this.send({
|
|
44
|
+
type: 'set-id',
|
|
45
|
+
customId: this.options.customPeerId
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.ws.onmessage = (event) => {
|
|
51
|
+
try {
|
|
52
|
+
const message = JSON.parse(event.data);
|
|
53
|
+
this.handleMessage(message);
|
|
54
|
+
|
|
55
|
+
if (message.type === 'welcome' && !this._connectedOnce) {
|
|
56
|
+
this.clientId = message.clientId;
|
|
57
|
+
this._connectedOnce = true;
|
|
58
|
+
this.emit('connected', { clientId: this.clientId });
|
|
59
|
+
resolve(this.clientId);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error parsing message:', error);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
this.ws.onclose = () => {
|
|
67
|
+
console.log('Disconnected from signaling server');
|
|
68
|
+
this.emit('disconnected');
|
|
69
|
+
|
|
70
|
+
if (this.options.autoReconnect) {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
console.log('Attempting to reconnect...');
|
|
73
|
+
this.connect();
|
|
74
|
+
}, this.options.reconnectDelay);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.ws.onerror = (error) => {
|
|
79
|
+
console.error('WebSocket error:', error);
|
|
80
|
+
reject(error);
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
reject(error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
disconnect() {
|
|
89
|
+
if (this.ws) {
|
|
90
|
+
this.options.autoReconnect = false;
|
|
91
|
+
this.ws.close();
|
|
92
|
+
this.ws = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
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
|
|
102
|
+
this.send({
|
|
103
|
+
type: 'join',
|
|
104
|
+
sessionId: sessionId,
|
|
105
|
+
peerId: this.clientId
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
leaveSession() {
|
|
110
|
+
if (this.sessionId) {
|
|
111
|
+
this.send({
|
|
112
|
+
type: 'leave',
|
|
113
|
+
sessionId: this.sessionId
|
|
114
|
+
});
|
|
115
|
+
this.sessionId = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
send(message) {
|
|
120
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
121
|
+
this.ws.send(JSON.stringify(message));
|
|
122
|
+
} else {
|
|
123
|
+
console.warn('WebSocket is not connected');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
sendOffer(offer, targetId) {
|
|
128
|
+
console.log(`[Client] Sending offer to ${targetId}`);
|
|
129
|
+
this.send({
|
|
130
|
+
type: 'offer',
|
|
131
|
+
offer: offer,
|
|
132
|
+
targetId: targetId,
|
|
133
|
+
sessionId: this.sessionId
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
sendAnswer(answer, targetId) {
|
|
138
|
+
console.log(`[Client] Sending answer to ${targetId}`);
|
|
139
|
+
this.send({
|
|
140
|
+
type: 'answer',
|
|
141
|
+
answer: answer,
|
|
142
|
+
targetId: targetId,
|
|
143
|
+
sessionId: this.sessionId
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
sendIceCandidate(candidate, targetId) {
|
|
148
|
+
console.log(`[Client] Sending ICE candidate to ${targetId}`);
|
|
149
|
+
this.send({
|
|
150
|
+
type: 'ice-candidate',
|
|
151
|
+
candidate: candidate,
|
|
152
|
+
targetId: targetId,
|
|
153
|
+
sessionId: this.sessionId
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
listRooms() {
|
|
158
|
+
this.send({
|
|
159
|
+
type: 'list-rooms'
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
on(event, handler) {
|
|
164
|
+
if (this.eventHandlers[event]) {
|
|
165
|
+
this.eventHandlers[event].push(handler);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
off(event, handler) {
|
|
170
|
+
if (this.eventHandlers[event]) {
|
|
171
|
+
this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
emit(event, data) {
|
|
176
|
+
if (this.eventHandlers[event]) {
|
|
177
|
+
this.eventHandlers[event].forEach(handler => {
|
|
178
|
+
try {
|
|
179
|
+
handler(data);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`Error in ${event} handler:`, error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
handleMessage(message) {
|
|
188
|
+
switch (message.type) {
|
|
189
|
+
case 'welcome':
|
|
190
|
+
// Only set clientId here; 'connected' is emitted in connect() with a guard
|
|
191
|
+
this.clientId = message.clientId;
|
|
192
|
+
console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
|
|
193
|
+
break;
|
|
194
|
+
case 'joined':
|
|
195
|
+
this.sessionId = message.sessionId;
|
|
196
|
+
this.emit('joined', {
|
|
197
|
+
sessionId: message.sessionId,
|
|
198
|
+
peerId: message.peerId,
|
|
199
|
+
clientId: message.clientId,
|
|
200
|
+
clients: message.clients
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
case 'peer-joined':
|
|
204
|
+
this.emit('peer-joined', {
|
|
205
|
+
sessionId: message.sessionId,
|
|
206
|
+
peerId: message.peerId
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
case 'peer-left':
|
|
210
|
+
this.emit('peer-left', {
|
|
211
|
+
sessionId: message.sessionId,
|
|
212
|
+
peerId: message.peerId
|
|
213
|
+
});
|
|
214
|
+
break;
|
|
215
|
+
case 'offer':
|
|
216
|
+
console.log(`[Client] Received offer from ${message.peerId}`);
|
|
217
|
+
this.emit('offer', {
|
|
218
|
+
peerId: message.peerId,
|
|
219
|
+
offer: message.offer
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
case 'answer':
|
|
223
|
+
console.log(`[Client] Received answer from ${message.peerId}`);
|
|
224
|
+
this.emit('answer', {
|
|
225
|
+
peerId: message.peerId,
|
|
226
|
+
answer: message.answer
|
|
227
|
+
});
|
|
228
|
+
break;
|
|
229
|
+
case 'ice-candidate':
|
|
230
|
+
console.log(`[Client] Received ICE candidate from ${message.peerId}`);
|
|
231
|
+
this.emit('ice-candidate', {
|
|
232
|
+
peerId: message.peerId,
|
|
233
|
+
candidate: message.candidate
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
case 'room-list':
|
|
237
|
+
this.emit('room-list', {
|
|
238
|
+
rooms: message.rooms
|
|
239
|
+
});
|
|
240
|
+
break;
|
|
241
|
+
case 'error':
|
|
242
|
+
this.emit('error', {
|
|
243
|
+
message: message.message
|
|
244
|
+
});
|
|
245
|
+
break;
|
|
246
|
+
case 'chat':
|
|
247
|
+
this.emit('chat', {
|
|
248
|
+
text: message.text,
|
|
249
|
+
peerId: message.peerId,
|
|
250
|
+
sessionId: message.sessionId
|
|
251
|
+
});
|
|
252
|
+
break;
|
|
253
|
+
default:
|
|
254
|
+
console.log('Unknown message type:', message.type);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
7
258
|
|
|
8
259
|
// Attach to window for non-module script usage
|
|
9
260
|
if (typeof window !== 'undefined') {
|
|
10
261
|
window.UniWRTCClient = UniWRTCClient;
|
|
11
262
|
}
|
|
12
263
|
|
|
264
|
+
// ESM default export for bundlers like Vite
|
|
13
265
|
export default UniWRTCClient;
|
package/client.js
CHANGED
|
@@ -1,69 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* UniWRTC Client -
|
|
2
|
+
* UniWRTC Client - WebRTC Signaling Client Library
|
|
3
|
+
* Simplifies connection to the UniWRTC signaling server
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
import https from 'https';
|
|
7
|
-
|
|
8
|
-
function requestJson(urlString, { method, body } = {}) {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const url = new URL(urlString);
|
|
11
|
-
const isHttps = url.protocol === 'https:';
|
|
12
|
-
const lib = isHttps ? https : http;
|
|
13
|
-
|
|
14
|
-
const payload = body === undefined ? null : JSON.stringify(body);
|
|
15
|
-
const req = lib.request(
|
|
16
|
-
{
|
|
17
|
-
hostname: url.hostname,
|
|
18
|
-
port: url.port || (isHttps ? 443 : 80),
|
|
19
|
-
path: url.pathname + url.search,
|
|
20
|
-
method: method || 'GET',
|
|
21
|
-
headers: {
|
|
22
|
-
...(payload ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } : {}),
|
|
23
|
-
'Accept': 'application/json'
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
(res) => {
|
|
27
|
-
let data = '';
|
|
28
|
-
res.on('data', (chunk) => (data += chunk));
|
|
29
|
-
res.on('end', () => {
|
|
30
|
-
const status = res.statusCode || 0;
|
|
31
|
-
let parsed = null;
|
|
32
|
-
try {
|
|
33
|
-
parsed = data ? JSON.parse(data) : null;
|
|
34
|
-
} catch {
|
|
35
|
-
parsed = null;
|
|
36
|
-
}
|
|
37
|
-
if (status >= 200 && status < 300) return resolve(parsed);
|
|
38
|
-
const message = parsed?.message || `HTTP ${status}`;
|
|
39
|
-
const err = new Error(message);
|
|
40
|
-
err.status = status;
|
|
41
|
-
err.body = parsed;
|
|
42
|
-
return reject(err);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
req.on('error', reject);
|
|
47
|
-
if (payload) req.write(payload);
|
|
48
|
-
req.end();
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export class UniWRTCClient {
|
|
6
|
+
class UniWRTCClient {
|
|
53
7
|
constructor(serverUrl, options = {}) {
|
|
54
8
|
this.serverUrl = serverUrl;
|
|
9
|
+
this.ws = null;
|
|
55
10
|
this.clientId = null;
|
|
56
11
|
this.sessionId = null;
|
|
57
|
-
this.
|
|
58
|
-
|
|
12
|
+
this.peers = new Map();
|
|
59
13
|
this.options = {
|
|
60
14
|
autoReconnect: true,
|
|
61
15
|
reconnectDelay: 3000,
|
|
62
|
-
pollIntervalMs: 500,
|
|
63
|
-
roomId: options.roomId || 'default',
|
|
64
16
|
...options
|
|
65
17
|
};
|
|
66
|
-
|
|
67
18
|
this.eventHandlers = {
|
|
68
19
|
'connected': [],
|
|
69
20
|
'disconnected': [],
|
|
@@ -78,147 +29,128 @@ export class UniWRTCClient {
|
|
|
78
29
|
};
|
|
79
30
|
}
|
|
80
31
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
apiUrl(pathname, extraSearch = {}) {
|
|
90
|
-
const origin = this.baseOrigin();
|
|
91
|
-
const url = new URL(origin + pathname);
|
|
92
|
-
url.searchParams.set('room', this.options.roomId);
|
|
93
|
-
for (const [k, v] of Object.entries(extraSearch)) {
|
|
94
|
-
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
95
|
-
}
|
|
96
|
-
return url.toString();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async postJson(pathname, body) {
|
|
100
|
-
return requestJson(this.apiUrl(pathname), { method: 'POST', body });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
startPolling() {
|
|
104
|
-
if (this._pollTimer) return;
|
|
105
|
-
this._pollTimer = setInterval(() => {
|
|
106
|
-
this.pollOnce().catch((err) => {
|
|
107
|
-
this.emit('error', { message: err?.message || String(err) });
|
|
108
|
-
});
|
|
109
|
-
}, this.options.pollIntervalMs);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
stopPolling() {
|
|
113
|
-
if (this._pollTimer) {
|
|
114
|
-
clearInterval(this._pollTimer);
|
|
115
|
-
this._pollTimer = null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async connect() {
|
|
120
|
-
try {
|
|
121
|
-
const welcome = await this.postJson('/api/connect', {});
|
|
122
|
-
this.clientId = welcome?.clientId;
|
|
123
|
-
if (!this.clientId) throw new Error('Missing clientId from server');
|
|
32
|
+
connect() {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
try {
|
|
35
|
+
// Get WebSocket class (browser or Node.js)
|
|
36
|
+
const WSClass = typeof WebSocket !== 'undefined' ? WebSocket : require('ws');
|
|
37
|
+
this.ws = new WSClass(this.serverUrl);
|
|
124
38
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
customId: this.options.customPeerId
|
|
129
|
-
});
|
|
130
|
-
this.clientId = res?.clientId || this.clientId;
|
|
131
|
-
}
|
|
39
|
+
this.ws.onopen = () => {
|
|
40
|
+
console.log('Connected to signaling server');
|
|
41
|
+
};
|
|
132
42
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
43
|
+
this.ws.onmessage = (event) => {
|
|
44
|
+
try {
|
|
45
|
+
const message = JSON.parse(event.data);
|
|
46
|
+
this.handleMessage(message);
|
|
47
|
+
|
|
48
|
+
if (message.type === 'welcome') {
|
|
49
|
+
this.clientId = message.clientId;
|
|
50
|
+
this.emit('connected', { clientId: this.clientId });
|
|
51
|
+
resolve(this.clientId);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error parsing message:', error);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.ws.onclose = () => {
|
|
59
|
+
console.log('Disconnected from signaling server');
|
|
60
|
+
this.emit('disconnected');
|
|
61
|
+
|
|
62
|
+
if (this.options.autoReconnect) {
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
console.log('Attempting to reconnect...');
|
|
65
|
+
this.connect();
|
|
66
|
+
}, this.options.reconnectDelay);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.ws.onerror = (error) => {
|
|
71
|
+
console.error('WebSocket error:', error);
|
|
72
|
+
reject(error);
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
reject(error);
|
|
140
76
|
}
|
|
141
|
-
|
|
142
|
-
}
|
|
77
|
+
});
|
|
143
78
|
}
|
|
144
79
|
|
|
145
|
-
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
await this.postJson('/api/leave', { clientId: this.clientId });
|
|
151
|
-
} catch {
|
|
152
|
-
// ignore
|
|
153
|
-
}
|
|
80
|
+
disconnect() {
|
|
81
|
+
if (this.ws) {
|
|
82
|
+
this.options.autoReconnect = false;
|
|
83
|
+
this.ws.close();
|
|
84
|
+
this.ws = null;
|
|
154
85
|
}
|
|
155
|
-
this.emit('disconnected');
|
|
156
86
|
}
|
|
157
87
|
|
|
158
|
-
|
|
159
|
-
if (!this.clientId) throw new Error('Not connected');
|
|
160
|
-
if (this.sessionId === sessionId) return;
|
|
88
|
+
joinSession(sessionId) {
|
|
161
89
|
this.sessionId = sessionId;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
sessionId: joined?.sessionId,
|
|
166
|
-
clientId: joined?.clientId,
|
|
167
|
-
clients: joined?.clients
|
|
90
|
+
this.send({
|
|
91
|
+
type: 'join',
|
|
92
|
+
sessionId: sessionId
|
|
168
93
|
});
|
|
169
94
|
}
|
|
170
95
|
|
|
171
|
-
|
|
172
|
-
this.sessionId
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
for (const msg of messages) this.handleMessage(msg);
|
|
96
|
+
leaveSession() {
|
|
97
|
+
if (this.sessionId) {
|
|
98
|
+
this.send({
|
|
99
|
+
type: 'leave',
|
|
100
|
+
sessionId: this.sessionId
|
|
101
|
+
});
|
|
102
|
+
this.sessionId = null;
|
|
103
|
+
}
|
|
180
104
|
}
|
|
181
105
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
106
|
+
sendOffer(offer, targetId = null) {
|
|
107
|
+
this.send({
|
|
108
|
+
type: 'offer',
|
|
109
|
+
offer: offer,
|
|
110
|
+
targetId: targetId,
|
|
111
|
+
sessionId: this.sessionId
|
|
188
112
|
});
|
|
189
113
|
}
|
|
190
114
|
|
|
191
|
-
sendOffer(offer, targetId) {
|
|
192
|
-
const offerSdp = typeof offer === 'string' ? offer : offer?.sdp;
|
|
193
|
-
return this.sendSignal({ type: 'offer', offer: offerSdp, targetId });
|
|
194
|
-
}
|
|
195
|
-
|
|
196
115
|
sendAnswer(answer, targetId) {
|
|
197
|
-
|
|
198
|
-
|
|
116
|
+
this.send({
|
|
117
|
+
type: 'answer',
|
|
118
|
+
answer: answer,
|
|
119
|
+
targetId: targetId,
|
|
120
|
+
sessionId: this.sessionId
|
|
121
|
+
});
|
|
199
122
|
}
|
|
200
123
|
|
|
201
|
-
sendIceCandidate(candidate, targetId) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return this.sendSignal({ type: 'ice-candidate', candidate: candidateText, targetId });
|
|
124
|
+
sendIceCandidate(candidate, targetId = null) {
|
|
125
|
+
this.send({
|
|
126
|
+
type: 'ice-candidate',
|
|
127
|
+
candidate: candidate,
|
|
128
|
+
targetId: targetId,
|
|
129
|
+
sessionId: this.sessionId
|
|
130
|
+
});
|
|
209
131
|
}
|
|
210
132
|
|
|
211
133
|
listRooms() {
|
|
212
|
-
|
|
134
|
+
this.send({
|
|
135
|
+
type: 'list-rooms'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
send(message) {
|
|
140
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
141
|
+
this.ws.send(JSON.stringify(message));
|
|
142
|
+
} else {
|
|
143
|
+
console.error('WebSocket is not connected');
|
|
144
|
+
}
|
|
213
145
|
}
|
|
214
146
|
|
|
215
147
|
handleMessage(message) {
|
|
216
148
|
switch (message.type) {
|
|
217
149
|
case 'welcome':
|
|
218
|
-
|
|
150
|
+
// Handled in connect(), but also surface donation message
|
|
151
|
+
console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
|
|
219
152
|
break;
|
|
220
153
|
case 'joined':
|
|
221
|
-
this.sessionId = message.sessionId;
|
|
222
154
|
this.emit('joined', {
|
|
223
155
|
sessionId: message.sessionId,
|
|
224
156
|
clientId: message.clientId,
|
|
@@ -226,50 +158,76 @@ export class UniWRTCClient {
|
|
|
226
158
|
});
|
|
227
159
|
break;
|
|
228
160
|
case 'peer-joined':
|
|
229
|
-
this.emit('peer-joined', {
|
|
161
|
+
this.emit('peer-joined', {
|
|
162
|
+
sessionId: message.sessionId,
|
|
163
|
+
peerId: message.peerId
|
|
164
|
+
});
|
|
230
165
|
break;
|
|
231
166
|
case 'peer-left':
|
|
232
|
-
this.emit('peer-left', {
|
|
167
|
+
this.emit('peer-left', {
|
|
168
|
+
sessionId: message.sessionId,
|
|
169
|
+
peerId: message.peerId
|
|
170
|
+
});
|
|
233
171
|
break;
|
|
234
172
|
case 'offer':
|
|
235
|
-
this.emit('offer', {
|
|
173
|
+
this.emit('offer', {
|
|
174
|
+
peerId: message.peerId,
|
|
175
|
+
offer: message.offer
|
|
176
|
+
});
|
|
236
177
|
break;
|
|
237
178
|
case 'answer':
|
|
238
|
-
this.emit('answer', {
|
|
179
|
+
this.emit('answer', {
|
|
180
|
+
peerId: message.peerId,
|
|
181
|
+
answer: message.answer
|
|
182
|
+
});
|
|
239
183
|
break;
|
|
240
184
|
case 'ice-candidate':
|
|
241
|
-
this.emit('ice-candidate', {
|
|
185
|
+
this.emit('ice-candidate', {
|
|
186
|
+
peerId: message.peerId,
|
|
187
|
+
candidate: message.candidate
|
|
188
|
+
});
|
|
189
|
+
break;
|
|
190
|
+
case 'room-list':
|
|
191
|
+
this.emit('room-list', {
|
|
192
|
+
rooms: message.rooms
|
|
193
|
+
});
|
|
242
194
|
break;
|
|
243
195
|
case 'error':
|
|
244
|
-
this.emit('error', {
|
|
196
|
+
this.emit('error', {
|
|
197
|
+
message: message.message
|
|
198
|
+
});
|
|
245
199
|
break;
|
|
246
200
|
default:
|
|
247
|
-
|
|
248
|
-
break;
|
|
201
|
+
console.warn('Unknown message type:', message.type);
|
|
249
202
|
}
|
|
250
203
|
}
|
|
251
204
|
|
|
252
205
|
on(event, handler) {
|
|
253
|
-
if (this.eventHandlers[event])
|
|
206
|
+
if (this.eventHandlers[event]) {
|
|
207
|
+
this.eventHandlers[event].push(handler);
|
|
208
|
+
}
|
|
254
209
|
}
|
|
255
210
|
|
|
256
211
|
off(event, handler) {
|
|
257
212
|
if (this.eventHandlers[event]) {
|
|
258
|
-
this.eventHandlers[event] = this.eventHandlers[event].filter(
|
|
213
|
+
this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
|
|
259
214
|
}
|
|
260
215
|
}
|
|
261
216
|
|
|
262
217
|
emit(event, data) {
|
|
263
218
|
if (this.eventHandlers[event]) {
|
|
264
|
-
|
|
219
|
+
this.eventHandlers[event].forEach(handler => {
|
|
265
220
|
try {
|
|
266
221
|
handler(data);
|
|
267
222
|
} catch (error) {
|
|
268
223
|
console.error(`Error in ${event} handler:`, error);
|
|
269
224
|
}
|
|
270
|
-
}
|
|
225
|
+
});
|
|
271
226
|
}
|
|
272
227
|
}
|
|
273
228
|
}
|
|
274
229
|
|
|
275
|
-
|
|
230
|
+
// Export for Node.js and browser
|
|
231
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
232
|
+
module.exports = { UniWRTCClient };
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniwrtc",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A universal WebRTC signaling service",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
],
|
|
21
21
|
"author": "",
|
|
22
22
|
"license": "MIT",
|
|
23
|
-
"dependencies": {
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"nostr-tools": "^2.9.0"
|
|
25
|
+
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@playwright/test": "^1.57.0",
|
|
26
28
|
"vite": "^6.0.6"
|