@voicemaster/core 1.0.9 → 1.0.11

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/dist/index.d.mts CHANGED
@@ -17,6 +17,8 @@ declare class VoiceClient {
17
17
  private iceServers;
18
18
  private isConnectedFlag;
19
19
  private targetUserId;
20
+ private connectingPromise;
21
+ private peers;
20
22
  constructor(config: VoiceClientConfig);
21
23
  on(event: string, callback: Function): void;
22
24
  private emit;
@@ -31,6 +33,7 @@ declare class VoiceClient {
31
33
  private handleCandidate;
32
34
  private send;
33
35
  disconnect(): void;
36
+ getPeers(): string[];
34
37
  toggleMute(): void;
35
38
  isMuted(): boolean;
36
39
  getUserId(): string;
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ declare class VoiceClient {
17
17
  private iceServers;
18
18
  private isConnectedFlag;
19
19
  private targetUserId;
20
+ private connectingPromise;
21
+ private peers;
20
22
  constructor(config: VoiceClientConfig);
21
23
  on(event: string, callback: Function): void;
22
24
  private emit;
@@ -31,6 +33,7 @@ declare class VoiceClient {
31
33
  private handleCandidate;
32
34
  private send;
33
35
  disconnect(): void;
36
+ getPeers(): string[];
34
37
  toggleMute(): void;
35
38
  isMuted(): boolean;
36
39
  getUserId(): string;
package/dist/index.js CHANGED
@@ -34,6 +34,8 @@ var VoiceClient = class {
34
34
  this.eventHandlers = /* @__PURE__ */ new Map();
35
35
  this.isConnectedFlag = false;
36
36
  this.targetUserId = null;
37
+ this.connectingPromise = null;
38
+ this.peers = /* @__PURE__ */ new Set();
37
39
  this.signalingUrl = config.signalingUrl;
38
40
  this.roomId = config.roomId;
39
41
  this.userId = config.userId;
@@ -41,7 +43,7 @@ var VoiceClient = class {
41
43
  { urls: "stun:stun.l.google.com:19302" }
42
44
  ];
43
45
  if (config.autoConnect !== false) {
44
- this.connect();
46
+ Promise.resolve().then(() => this.connect());
45
47
  }
46
48
  }
47
49
  on(event, callback) {
@@ -57,13 +59,22 @@ var VoiceClient = class {
57
59
  }
58
60
  }
59
61
  async connect() {
60
- try {
61
- await this.initMicrophone();
62
- this.initPeerConnection();
63
- this.initWebSocket();
64
- } catch (error) {
65
- this.emit("error", error);
62
+ if (this.connectingPromise) {
63
+ return this.connectingPromise;
66
64
  }
65
+ this.connectingPromise = (async () => {
66
+ try {
67
+ await this.initMicrophone();
68
+ this.initPeerConnection();
69
+ this.initWebSocket();
70
+ } catch (error) {
71
+ this.emit("error", error);
72
+ throw error;
73
+ } finally {
74
+ this.connectingPromise = null;
75
+ }
76
+ })();
77
+ return this.connectingPromise;
67
78
  }
68
79
  async initMicrophone() {
69
80
  this.localStream = await navigator.mediaDevices.getUserMedia({
@@ -81,7 +92,9 @@ var VoiceClient = class {
81
92
  this.pc.addTrack(track, this.localStream);
82
93
  });
83
94
  this.pc.ontrack = (event) => {
84
- this.remoteStream = event.streams[0];
95
+ const stream = event.streams?.[0] || new MediaStream([event.track]);
96
+ this.remoteStream = stream;
97
+ console.log("[VoiceClient] Remote track received", { track: event.track, stream });
85
98
  this.emit("remoteStream", this.remoteStream);
86
99
  };
87
100
  this.pc.onicecandidate = (event) => {
@@ -105,43 +118,56 @@ var VoiceClient = class {
105
118
  const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
106
119
  this.ws = new WebSocket(url);
107
120
  this.ws.onopen = () => {
121
+ console.log("[VoiceClient] WebSocket connected");
108
122
  this.send({ type: "join", roomId: this.roomId, userId: this.userId });
109
123
  };
110
124
  this.ws.onmessage = async (event) => {
111
125
  const message = JSON.parse(event.data);
112
126
  await this.handleSignalingMessage(message);
113
127
  };
128
+ this.ws.onerror = (event) => {
129
+ console.error("[VoiceClient] WebSocket error", event);
130
+ this.emit("error", new Error("WebSocket error"));
131
+ };
114
132
  this.ws.onclose = () => {
133
+ console.log("[VoiceClient] WebSocket closed");
115
134
  this.emit("disconnected");
116
135
  };
117
136
  }
118
137
  async handleSignalingMessage(message) {
119
- console.log("Received:", message.type, message);
138
+ console.log("[VoiceClient] Received message:", message.type, message);
120
139
  switch (message.type) {
121
140
  case "user-joined":
141
+ console.log("[VoiceClient] User joined, myId:", this.userId, "theirId:", message.userId);
122
142
  if (message.userId !== this.userId) {
123
- console.log("User joined, creating offer...");
143
+ this.peers.add(message.userId);
144
+ console.log("[VoiceClient] Creating offer...");
124
145
  this.targetUserId = message.userId;
125
146
  await this.createOffer();
126
147
  this.emit("userJoined", message.userId);
148
+ } else {
149
+ console.log("[VoiceClient] Ignoring my own join");
127
150
  }
128
151
  break;
129
152
  case "offer":
130
- console.log("Received offer, handling...");
153
+ console.log("[VoiceClient] Received offer");
131
154
  this.targetUserId = message.userId;
132
155
  await this.handleOffer(message);
133
156
  break;
134
157
  case "answer":
135
- console.log("Received answer, handling...");
158
+ console.log("[VoiceClient] Received answer");
136
159
  await this.handleAnswer(message);
137
160
  break;
138
161
  case "candidate":
139
- console.log("Received candidate, handling...");
162
+ console.log("[VoiceClient] Received candidate");
140
163
  await this.handleCandidate(message);
141
164
  break;
142
165
  case "user-left":
166
+ this.peers.delete(message.userId);
143
167
  this.emit("userLeft", message.userId);
144
168
  break;
169
+ default:
170
+ console.log("[VoiceClient] Unknown message type:", message.type);
145
171
  }
146
172
  }
147
173
  async createOffer() {
@@ -188,9 +214,12 @@ var VoiceClient = class {
188
214
  send(data) {
189
215
  if (this.ws?.readyState === WebSocket.OPEN) {
190
216
  this.ws.send(JSON.stringify(data));
217
+ } else {
218
+ console.warn("[VoiceClient] WebSocket is not open, cannot send message", data);
191
219
  }
192
220
  }
193
221
  disconnect() {
222
+ this.peers.clear();
194
223
  if (this.ws) {
195
224
  this.send({ type: "leave", roomId: this.roomId, userId: this.userId });
196
225
  this.ws.close();
@@ -202,6 +231,9 @@ var VoiceClient = class {
202
231
  this.isConnectedFlag = false;
203
232
  this.emit("disconnected");
204
233
  }
234
+ getPeers() {
235
+ return Array.from(this.peers);
236
+ }
205
237
  toggleMute() {
206
238
  if (this.localStream) {
207
239
  const audioTrack = this.localStream.getAudioTracks()[0];
package/dist/index.mjs CHANGED
@@ -8,6 +8,8 @@ var VoiceClient = class {
8
8
  this.eventHandlers = /* @__PURE__ */ new Map();
9
9
  this.isConnectedFlag = false;
10
10
  this.targetUserId = null;
11
+ this.connectingPromise = null;
12
+ this.peers = /* @__PURE__ */ new Set();
11
13
  this.signalingUrl = config.signalingUrl;
12
14
  this.roomId = config.roomId;
13
15
  this.userId = config.userId;
@@ -15,7 +17,7 @@ var VoiceClient = class {
15
17
  { urls: "stun:stun.l.google.com:19302" }
16
18
  ];
17
19
  if (config.autoConnect !== false) {
18
- this.connect();
20
+ Promise.resolve().then(() => this.connect());
19
21
  }
20
22
  }
21
23
  on(event, callback) {
@@ -31,13 +33,22 @@ var VoiceClient = class {
31
33
  }
32
34
  }
33
35
  async connect() {
34
- try {
35
- await this.initMicrophone();
36
- this.initPeerConnection();
37
- this.initWebSocket();
38
- } catch (error) {
39
- this.emit("error", error);
36
+ if (this.connectingPromise) {
37
+ return this.connectingPromise;
40
38
  }
39
+ this.connectingPromise = (async () => {
40
+ try {
41
+ await this.initMicrophone();
42
+ this.initPeerConnection();
43
+ this.initWebSocket();
44
+ } catch (error) {
45
+ this.emit("error", error);
46
+ throw error;
47
+ } finally {
48
+ this.connectingPromise = null;
49
+ }
50
+ })();
51
+ return this.connectingPromise;
41
52
  }
42
53
  async initMicrophone() {
43
54
  this.localStream = await navigator.mediaDevices.getUserMedia({
@@ -55,7 +66,9 @@ var VoiceClient = class {
55
66
  this.pc.addTrack(track, this.localStream);
56
67
  });
57
68
  this.pc.ontrack = (event) => {
58
- this.remoteStream = event.streams[0];
69
+ const stream = event.streams?.[0] || new MediaStream([event.track]);
70
+ this.remoteStream = stream;
71
+ console.log("[VoiceClient] Remote track received", { track: event.track, stream });
59
72
  this.emit("remoteStream", this.remoteStream);
60
73
  };
61
74
  this.pc.onicecandidate = (event) => {
@@ -79,43 +92,56 @@ var VoiceClient = class {
79
92
  const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
80
93
  this.ws = new WebSocket(url);
81
94
  this.ws.onopen = () => {
95
+ console.log("[VoiceClient] WebSocket connected");
82
96
  this.send({ type: "join", roomId: this.roomId, userId: this.userId });
83
97
  };
84
98
  this.ws.onmessage = async (event) => {
85
99
  const message = JSON.parse(event.data);
86
100
  await this.handleSignalingMessage(message);
87
101
  };
102
+ this.ws.onerror = (event) => {
103
+ console.error("[VoiceClient] WebSocket error", event);
104
+ this.emit("error", new Error("WebSocket error"));
105
+ };
88
106
  this.ws.onclose = () => {
107
+ console.log("[VoiceClient] WebSocket closed");
89
108
  this.emit("disconnected");
90
109
  };
91
110
  }
92
111
  async handleSignalingMessage(message) {
93
- console.log("Received:", message.type, message);
112
+ console.log("[VoiceClient] Received message:", message.type, message);
94
113
  switch (message.type) {
95
114
  case "user-joined":
115
+ console.log("[VoiceClient] User joined, myId:", this.userId, "theirId:", message.userId);
96
116
  if (message.userId !== this.userId) {
97
- console.log("User joined, creating offer...");
117
+ this.peers.add(message.userId);
118
+ console.log("[VoiceClient] Creating offer...");
98
119
  this.targetUserId = message.userId;
99
120
  await this.createOffer();
100
121
  this.emit("userJoined", message.userId);
122
+ } else {
123
+ console.log("[VoiceClient] Ignoring my own join");
101
124
  }
102
125
  break;
103
126
  case "offer":
104
- console.log("Received offer, handling...");
127
+ console.log("[VoiceClient] Received offer");
105
128
  this.targetUserId = message.userId;
106
129
  await this.handleOffer(message);
107
130
  break;
108
131
  case "answer":
109
- console.log("Received answer, handling...");
132
+ console.log("[VoiceClient] Received answer");
110
133
  await this.handleAnswer(message);
111
134
  break;
112
135
  case "candidate":
113
- console.log("Received candidate, handling...");
136
+ console.log("[VoiceClient] Received candidate");
114
137
  await this.handleCandidate(message);
115
138
  break;
116
139
  case "user-left":
140
+ this.peers.delete(message.userId);
117
141
  this.emit("userLeft", message.userId);
118
142
  break;
143
+ default:
144
+ console.log("[VoiceClient] Unknown message type:", message.type);
119
145
  }
120
146
  }
121
147
  async createOffer() {
@@ -162,9 +188,12 @@ var VoiceClient = class {
162
188
  send(data) {
163
189
  if (this.ws?.readyState === WebSocket.OPEN) {
164
190
  this.ws.send(JSON.stringify(data));
191
+ } else {
192
+ console.warn("[VoiceClient] WebSocket is not open, cannot send message", data);
165
193
  }
166
194
  }
167
195
  disconnect() {
196
+ this.peers.clear();
168
197
  if (this.ws) {
169
198
  this.send({ type: "leave", roomId: this.roomId, userId: this.userId });
170
199
  this.ws.close();
@@ -176,6 +205,9 @@ var VoiceClient = class {
176
205
  this.isConnectedFlag = false;
177
206
  this.emit("disconnected");
178
207
  }
208
+ getPeers() {
209
+ return Array.from(this.peers);
210
+ }
179
211
  toggleMute() {
180
212
  if (this.localStream) {
181
213
  const audioTrack = this.localStream.getAudioTracks()[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voicemaster/core",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "WebRTC voice communication core library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -18,6 +18,8 @@ export class VoiceClient {
18
18
  private iceServers: RTCIceServer[];
19
19
  private isConnectedFlag = false;
20
20
  private targetUserId: string | null = null;
21
+ private connectingPromise: Promise<void> | null = null;
22
+ private peers: Set<string> = new Set();
21
23
 
22
24
  constructor(config: VoiceClientConfig) {
23
25
  this.signalingUrl = config.signalingUrl;
@@ -28,7 +30,7 @@ export class VoiceClient {
28
30
  ];
29
31
 
30
32
  if (config.autoConnect !== false) {
31
- this.connect();
33
+ Promise.resolve().then(() => this.connect());
32
34
  }
33
35
  }
34
36
 
@@ -49,13 +51,24 @@ export class VoiceClient {
49
51
  }
50
52
 
51
53
  async connect(): Promise<void> {
52
- try {
53
- await this.initMicrophone();
54
- this.initPeerConnection();
55
- this.initWebSocket();
56
- } catch (error) {
57
- this.emit('error', error);
54
+ if (this.connectingPromise) {
55
+ return this.connectingPromise;
58
56
  }
57
+
58
+ this.connectingPromise = (async () => {
59
+ try {
60
+ await this.initMicrophone();
61
+ this.initPeerConnection();
62
+ this.initWebSocket();
63
+ } catch (error) {
64
+ this.emit('error', error);
65
+ throw error;
66
+ } finally {
67
+ this.connectingPromise = null;
68
+ }
69
+ })();
70
+
71
+ return this.connectingPromise;
59
72
  }
60
73
 
61
74
  private async initMicrophone(): Promise<void> {
@@ -77,7 +90,9 @@ export class VoiceClient {
77
90
  });
78
91
 
79
92
  this.pc.ontrack = (event) => {
80
- this.remoteStream = event.streams[0];
93
+ const stream = event.streams?.[0] || new MediaStream([event.track]);
94
+ this.remoteStream = stream;
95
+ console.log('[VoiceClient] Remote track received', { track: event.track, stream });
81
96
  this.emit('remoteStream', this.remoteStream);
82
97
  };
83
98
 
@@ -105,6 +120,7 @@ export class VoiceClient {
105
120
  this.ws = new WebSocket(url);
106
121
 
107
122
  this.ws.onopen = () => {
123
+ console.log('[VoiceClient] WebSocket connected');
108
124
  this.send({ type: 'join', roomId: this.roomId, userId: this.userId });
109
125
  };
110
126
 
@@ -113,39 +129,52 @@ export class VoiceClient {
113
129
  await this.handleSignalingMessage(message);
114
130
  };
115
131
 
132
+ this.ws.onerror = (event) => {
133
+ console.error('[VoiceClient] WebSocket error', event);
134
+ this.emit('error', new Error('WebSocket error'));
135
+ };
136
+
116
137
  this.ws.onclose = () => {
138
+ console.log('[VoiceClient] WebSocket closed');
117
139
  this.emit('disconnected');
118
140
  };
119
141
  }
120
142
 
121
143
  private async handleSignalingMessage(message: any): Promise<void> {
122
- console.log('Received:', message.type, message);
144
+ console.log('[VoiceClient] Received message:', message.type, message);
123
145
 
124
146
  switch (message.type) {
125
147
  case 'user-joined':
148
+ console.log('[VoiceClient] User joined, myId:', this.userId, 'theirId:', message.userId);
126
149
  if (message.userId !== this.userId) {
127
- console.log('User joined, creating offer...');
150
+ this.peers.add(message.userId);
151
+ console.log('[VoiceClient] Creating offer...');
128
152
  this.targetUserId = message.userId;
129
153
  await this.createOffer();
130
154
  this.emit('userJoined', message.userId);
155
+ } else {
156
+ console.log('[VoiceClient] Ignoring my own join');
131
157
  }
132
158
  break;
133
159
  case 'offer':
134
- console.log('Received offer, handling...');
160
+ console.log('[VoiceClient] Received offer');
135
161
  this.targetUserId = message.userId;
136
162
  await this.handleOffer(message);
137
163
  break;
138
164
  case 'answer':
139
- console.log('Received answer, handling...');
165
+ console.log('[VoiceClient] Received answer');
140
166
  await this.handleAnswer(message);
141
167
  break;
142
168
  case 'candidate':
143
- console.log('Received candidate, handling...');
169
+ console.log('[VoiceClient] Received candidate');
144
170
  await this.handleCandidate(message);
145
171
  break;
146
172
  case 'user-left':
173
+ this.peers.delete(message.userId);
147
174
  this.emit('userLeft', message.userId);
148
175
  break;
176
+ default:
177
+ console.log('[VoiceClient] Unknown message type:', message.type);
149
178
  }
150
179
  }
151
180
 
@@ -200,10 +229,13 @@ export class VoiceClient {
200
229
  private send(data: any): void {
201
230
  if (this.ws?.readyState === WebSocket.OPEN) {
202
231
  this.ws.send(JSON.stringify(data));
232
+ } else {
233
+ console.warn('[VoiceClient] WebSocket is not open, cannot send message', data);
203
234
  }
204
235
  }
205
236
 
206
237
  disconnect(): void {
238
+ this.peers.clear();
207
239
  if (this.ws) {
208
240
  this.send({ type: 'leave', roomId: this.roomId, userId: this.userId });
209
241
  this.ws.close();
@@ -216,6 +248,10 @@ export class VoiceClient {
216
248
  this.emit('disconnected');
217
249
  }
218
250
 
251
+ getPeers(): string[] {
252
+ return Array.from(this.peers);
253
+ }
254
+
219
255
  toggleMute(): void {
220
256
  if (this.localStream) {
221
257
  const audioTrack = this.localStream.getAudioTracks()[0];