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,249 @@
1
+ /**
2
+ * UniWRTC Client - WebRTC Signaling Client Library
3
+ * Browser-only version
4
+ */
5
+
6
+ class UniWRTCClient {
7
+ constructor(serverUrl, options = {}) {
8
+ this.serverUrl = serverUrl;
9
+ this.ws = null;
10
+ this.clientId = null;
11
+ this.roomId = 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
+ joinRoom(roomId) {
97
+ // Prevent duplicate join calls for the same room
98
+ if (this.roomId === roomId) return;
99
+ this.roomId = roomId;
100
+ this.send({
101
+ type: 'join',
102
+ roomId: roomId,
103
+ peerId: this.clientId
104
+ });
105
+ }
106
+
107
+ leaveRoom() {
108
+ if (this.roomId) {
109
+ this.send({
110
+ type: 'leave',
111
+ roomId: this.roomId
112
+ });
113
+ this.roomId = null;
114
+ }
115
+ }
116
+
117
+ send(message) {
118
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
119
+ this.ws.send(JSON.stringify(message));
120
+ } else {
121
+ console.warn('WebSocket is not connected');
122
+ }
123
+ }
124
+
125
+ sendOffer(targetId, offer) {
126
+ this.send({
127
+ type: 'offer',
128
+ offer: offer,
129
+ targetId: targetId,
130
+ roomId: this.roomId
131
+ });
132
+ }
133
+
134
+ sendAnswer(targetId, answer) {
135
+ this.send({
136
+ type: 'answer',
137
+ answer: answer,
138
+ targetId: targetId,
139
+ roomId: this.roomId
140
+ });
141
+ }
142
+
143
+ sendIceCandidate(targetId, candidate) {
144
+ this.send({
145
+ type: 'ice-candidate',
146
+ candidate: candidate,
147
+ targetId: targetId,
148
+ roomId: this.roomId
149
+ });
150
+ }
151
+
152
+ listRooms() {
153
+ this.send({
154
+ type: 'list-rooms'
155
+ });
156
+ }
157
+
158
+ on(event, handler) {
159
+ if (this.eventHandlers[event]) {
160
+ this.eventHandlers[event].push(handler);
161
+ }
162
+ }
163
+
164
+ off(event, handler) {
165
+ if (this.eventHandlers[event]) {
166
+ this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
167
+ }
168
+ }
169
+
170
+ emit(event, data) {
171
+ if (this.eventHandlers[event]) {
172
+ this.eventHandlers[event].forEach(handler => {
173
+ try {
174
+ handler(data);
175
+ } catch (error) {
176
+ console.error(`Error in ${event} handler:`, error);
177
+ }
178
+ });
179
+ }
180
+ }
181
+
182
+ handleMessage(message) {
183
+ switch (message.type) {
184
+ case 'welcome':
185
+ // Only set clientId here; 'connected' is emitted in connect() with a guard
186
+ this.clientId = message.clientId;
187
+ console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
188
+ break;
189
+ case 'joined':
190
+ this.roomId = message.roomId;
191
+ this.emit('joined', {
192
+ roomId: message.roomId,
193
+ peerId: message.peerId,
194
+ clientId: message.clientId,
195
+ clients: message.clients
196
+ });
197
+ break;
198
+ case 'peer-joined':
199
+ this.emit('peer-joined', {
200
+ peerId: message.peerId || message.clientId,
201
+ clientId: message.clientId
202
+ });
203
+ break;
204
+ case 'peer-left':
205
+ this.emit('peer-left', {
206
+ peerId: message.peerId || message.clientId,
207
+ clientId: message.clientId
208
+ });
209
+ break;
210
+ case 'offer':
211
+ this.emit('offer', {
212
+ peerId: message.peerId,
213
+ offer: message.offer
214
+ });
215
+ break;
216
+ case 'answer':
217
+ this.emit('answer', {
218
+ peerId: message.peerId,
219
+ answer: message.answer
220
+ });
221
+ break;
222
+ case 'ice-candidate':
223
+ this.emit('ice-candidate', {
224
+ peerId: message.peerId,
225
+ candidate: message.candidate
226
+ });
227
+ break;
228
+ case 'room-list':
229
+ this.emit('room-list', {
230
+ rooms: message.rooms
231
+ });
232
+ break;
233
+ case 'error':
234
+ this.emit('error', {
235
+ message: message.message
236
+ });
237
+ break;
238
+ case 'chat':
239
+ this.emit('chat', {
240
+ text: message.text,
241
+ senderId: message.senderId,
242
+ roomId: message.roomId
243
+ });
244
+ break;
245
+ default:
246
+ console.log('Unknown message type:', message.type);
247
+ }
248
+ }
249
+ }
package/client.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * UniWRTC Client - WebRTC Signaling Client Library
3
+ * Simplifies connection to the UniWRTC signaling server
4
+ */
5
+
6
+ class UniWRTCClient {
7
+ constructor(serverUrl, options = {}) {
8
+ this.serverUrl = serverUrl;
9
+ this.ws = null;
10
+ this.clientId = null;
11
+ this.roomId = null;
12
+ this.peers = new Map();
13
+ this.options = {
14
+ autoReconnect: true,
15
+ reconnectDelay: 3000,
16
+ ...options
17
+ };
18
+ this.eventHandlers = {
19
+ 'connected': [],
20
+ 'disconnected': [],
21
+ 'joined': [],
22
+ 'peer-joined': [],
23
+ 'peer-left': [],
24
+ 'offer': [],
25
+ 'answer': [],
26
+ 'ice-candidate': [],
27
+ 'room-list': [],
28
+ 'error': []
29
+ };
30
+ }
31
+
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);
38
+
39
+ this.ws.onopen = () => {
40
+ console.log('Connected to signaling server');
41
+ };
42
+
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);
76
+ }
77
+ });
78
+ }
79
+
80
+ disconnect() {
81
+ if (this.ws) {
82
+ this.options.autoReconnect = false;
83
+ this.ws.close();
84
+ this.ws = null;
85
+ }
86
+ }
87
+
88
+ joinRoom(roomId) {
89
+ this.roomId = roomId;
90
+ this.send({
91
+ type: 'join',
92
+ roomId: roomId
93
+ });
94
+ }
95
+
96
+ leaveRoom() {
97
+ if (this.roomId) {
98
+ this.send({
99
+ type: 'leave',
100
+ roomId: this.roomId
101
+ });
102
+ this.roomId = null;
103
+ }
104
+ }
105
+
106
+ sendOffer(offer, targetId = null) {
107
+ this.send({
108
+ type: 'offer',
109
+ offer: offer,
110
+ targetId: targetId,
111
+ roomId: this.roomId
112
+ });
113
+ }
114
+
115
+ sendAnswer(answer, targetId) {
116
+ this.send({
117
+ type: 'answer',
118
+ answer: answer,
119
+ targetId: targetId,
120
+ roomId: this.roomId
121
+ });
122
+ }
123
+
124
+ sendIceCandidate(candidate, targetId = null) {
125
+ this.send({
126
+ type: 'ice-candidate',
127
+ candidate: candidate,
128
+ targetId: targetId,
129
+ roomId: this.roomId
130
+ });
131
+ }
132
+
133
+ listRooms() {
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
+ }
145
+ }
146
+
147
+ handleMessage(message) {
148
+ switch (message.type) {
149
+ case 'welcome':
150
+ // Handled in connect(), but also surface donation message
151
+ console.log('[UniWRTC] If this helps, consider donating ❤️ → https://coff.ee/draederg');
152
+ break;
153
+ case 'joined':
154
+ this.emit('joined', {
155
+ roomId: message.roomId,
156
+ clientId: message.clientId,
157
+ clients: message.clients
158
+ });
159
+ break;
160
+ case 'peer-joined':
161
+ this.emit('peer-joined', {
162
+ clientId: message.clientId
163
+ });
164
+ break;
165
+ case 'peer-left':
166
+ this.emit('peer-left', {
167
+ clientId: message.clientId
168
+ });
169
+ break;
170
+ case 'offer':
171
+ this.emit('offer', {
172
+ senderId: message.senderId,
173
+ offer: message.offer
174
+ });
175
+ break;
176
+ case 'answer':
177
+ this.emit('answer', {
178
+ senderId: message.senderId,
179
+ answer: message.answer
180
+ });
181
+ break;
182
+ case 'ice-candidate':
183
+ this.emit('ice-candidate', {
184
+ senderId: message.senderId,
185
+ candidate: message.candidate
186
+ });
187
+ break;
188
+ case 'room-list':
189
+ this.emit('room-list', {
190
+ rooms: message.rooms
191
+ });
192
+ break;
193
+ case 'error':
194
+ this.emit('error', {
195
+ message: message.message
196
+ });
197
+ break;
198
+ default:
199
+ console.warn('Unknown message type:', message.type);
200
+ }
201
+ }
202
+
203
+ on(event, handler) {
204
+ if (this.eventHandlers[event]) {
205
+ this.eventHandlers[event].push(handler);
206
+ }
207
+ }
208
+
209
+ off(event, handler) {
210
+ if (this.eventHandlers[event]) {
211
+ this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
212
+ }
213
+ }
214
+
215
+ emit(event, data) {
216
+ if (this.eventHandlers[event]) {
217
+ this.eventHandlers[event].forEach(handler => {
218
+ try {
219
+ handler(data);
220
+ } catch (error) {
221
+ console.error(`Error in ${event} handler:`, error);
222
+ }
223
+ });
224
+ }
225
+ }
226
+ }
227
+
228
+ // Export for Node.js and browser
229
+ if (typeof module !== 'undefined' && module.exports) {
230
+ module.exports = { UniWRTCClient };
231
+ }