@sawport/peers-caller 0.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.
Files changed (62) hide show
  1. package/README.md +782 -0
  2. package/dist/core/BaseStore.d.ts +13 -0
  3. package/dist/core/BaseStore.d.ts.map +1 -0
  4. package/dist/core/CallMediaStream.d.ts +101 -0
  5. package/dist/core/CallMediaStream.d.ts.map +1 -0
  6. package/dist/core/CallParticipant.d.ts +122 -0
  7. package/dist/core/CallParticipant.d.ts.map +1 -0
  8. package/dist/core/CallPeerConnection.d.ts +92 -0
  9. package/dist/core/CallPeerConnection.d.ts.map +1 -0
  10. package/dist/core/CallRecorder.d.ts +72 -0
  11. package/dist/core/CallRecorder.d.ts.map +1 -0
  12. package/dist/core/CallSocket.d.ts +131 -0
  13. package/dist/core/CallSocket.d.ts.map +1 -0
  14. package/dist/core/PeersCaller.d.ts +155 -0
  15. package/dist/core/PeersCaller.d.ts.map +1 -0
  16. package/dist/events/CallEventEmitter.d.ts +2 -0
  17. package/dist/events/CallEventEmitter.d.ts.map +1 -0
  18. package/dist/events/index.d.ts +2 -0
  19. package/dist/events/index.d.ts.map +1 -0
  20. package/dist/events/types.d.ts +2 -0
  21. package/dist/events/types.d.ts.map +1 -0
  22. package/dist/hooks/index.d.ts +90 -0
  23. package/dist/hooks/index.d.ts.map +1 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/peers-caller.es.js +7983 -0
  27. package/dist/peers-caller.es.js.map +1 -0
  28. package/dist/peers-caller.umd.js +22 -0
  29. package/dist/peers-caller.umd.js.map +1 -0
  30. package/dist/polyfills.d.ts +5 -0
  31. package/dist/polyfills.d.ts.map +1 -0
  32. package/dist/store/index.d.ts +57 -0
  33. package/dist/store/index.d.ts.map +1 -0
  34. package/dist/test-polyfills.d.ts +2 -0
  35. package/dist/test-polyfills.d.ts.map +1 -0
  36. package/dist/test-setup.d.ts +2 -0
  37. package/dist/test-setup.d.ts.map +1 -0
  38. package/dist/test-utils.d.ts +2 -0
  39. package/dist/test-utils.d.ts.map +1 -0
  40. package/dist/tester/App.d.ts +3 -0
  41. package/dist/tester/App.d.ts.map +1 -0
  42. package/dist/tester/components/ConfigPanel.d.ts +17 -0
  43. package/dist/tester/components/ConfigPanel.d.ts.map +1 -0
  44. package/dist/tester/components/ConnectionStatus.d.ts +21 -0
  45. package/dist/tester/components/ConnectionStatus.d.ts.map +1 -0
  46. package/dist/tester/components/ControlPanel.d.ts +23 -0
  47. package/dist/tester/components/ControlPanel.d.ts.map +1 -0
  48. package/dist/tester/components/DebugConsole.d.ts +27 -0
  49. package/dist/tester/components/DebugConsole.d.ts.map +1 -0
  50. package/dist/tester/components/ParticipantList.d.ts +10 -0
  51. package/dist/tester/components/ParticipantList.d.ts.map +1 -0
  52. package/dist/tester/components/VideoGrid.d.ts +10 -0
  53. package/dist/tester/components/VideoGrid.d.ts.map +1 -0
  54. package/dist/tester/hooks/useTester.d.ts +54 -0
  55. package/dist/tester/hooks/useTester.d.ts.map +1 -0
  56. package/dist/tester/main.d.ts +3 -0
  57. package/dist/tester/main.d.ts.map +1 -0
  58. package/dist/types/index.d.ts +262 -0
  59. package/dist/types/index.d.ts.map +1 -0
  60. package/dist/utils/index.d.ts +50 -0
  61. package/dist/utils/index.d.ts.map +1 -0
  62. package/package.json +78 -0
package/README.md ADDED
@@ -0,0 +1,782 @@
1
+ # ๐ŸŽฅ PeersCaller
2
+
3
+ <div align="center">
4
+
5
+ [![npm version](https://badge.fury.io/js/@sawport%2Fpeers-caller.svg)](https://badge.fury.io/js/@sawport%2Fpeers-caller)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ A modern, TypeScript-first WebRTC library for multi-peer mesh video calls supporting up to 4 participants. Built with developer experience in mind.
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## โœจ Features
16
+
17
+ - ๐ŸŽฅ **WebRTC-based P2P video calls** - Direct peer-to-peer communication
18
+ - ๏ฟฝ๏ธ **Mesh architecture** - Efficient network topology for up to 4 participants
19
+ - ๏ฟฝ **TypeScript-first** - Full type safety and excellent IntelliSense
20
+ - โšก **Vite-powered** - Lightning-fast development and builds
21
+ - ๐ŸŽฏ **Zustand state management** - Predictable and reactive state
22
+ - ๐ŸŽ›๏ธ **Media controls** - Audio/video toggle, screen sharing
23
+ - ๐Ÿ“น **Call recording** - Built-in recording capabilities
24
+ - ๐Ÿงช **Well-tested** - Comprehensive test suite with Vitest
25
+ - ๐ŸŽจ **React hooks** - Ready-to-use React integration
26
+ - ๐Ÿ“ก **Real-time signaling** - WebSocket-based call coordination
27
+
28
+ ## ๐Ÿ“ฆ Installation
29
+
30
+ ```bash
31
+ npm install @sawport/peers-caller
32
+ # or
33
+ yarn add @sawport/peers-caller
34
+ # or
35
+ pnpm add @sawport/peers-caller
36
+ ```
37
+
38
+ ## ๐Ÿš€ Quick Start
39
+
40
+ ### Basic Usage
41
+
42
+ ```typescript
43
+ import { PeersCaller } from '@sawport/peers-caller';
44
+
45
+ // Initialize the caller
46
+ const peersCaller = new PeersCaller({
47
+ conversationId: 'unique-conversation-id',
48
+ userId: 'current-user-id',
49
+ token: 'jwt-auth-token',
50
+ socketUrl: 'https://your-signaling-server.com',
51
+ maxParticipants: 4,
52
+ mediaConfig: {
53
+ video: true,
54
+ audio: true
55
+ }
56
+ }, {
57
+ onParticipantJoined: (participant) => console.log('User joined:', participant.userId),
58
+ onParticipantLeft: (userId) => console.log('User left:', userId),
59
+ onStreamReceived: (userId, stream) => {
60
+ // Attach stream to video element
61
+ const videoElement = document.getElementById(`video-${userId}`);
62
+ if (videoElement) videoElement.srcObject = stream;
63
+ },
64
+ onError: (error, message) => console.error('Call error:', error, message)
65
+ });
66
+
67
+ // Start or join a call
68
+ async function startCall() {
69
+ try {
70
+ await peersCaller.initialize();
71
+ await peersCaller.startCall();
72
+ console.log('Call started successfully!');
73
+ } catch (error) {
74
+ console.error('Failed to start call:', error);
75
+ }
76
+ }
77
+
78
+ async function joinCall() {
79
+ try {
80
+ await peersCaller.initialize();
81
+ await peersCaller.joinCall();
82
+ console.log('Joined call successfully!');
83
+ } catch (error) {
84
+ console.error('Failed to join call:', error);
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### React Integration
90
+
91
+ ```tsx
92
+ import { useVideoCall } from '@sawport/peers-caller';
93
+
94
+ function VideoCallComponent() {
95
+ const {
96
+ startCall,
97
+ joinCall,
98
+ endCall,
99
+ toggleAudio,
100
+ toggleVideo,
101
+ startScreenShare,
102
+ stopScreenShare,
103
+ participants,
104
+ localParticipant,
105
+ isConnected,
106
+ error
107
+ } = useVideoCall({
108
+ conversationId: 'conversation-123',
109
+ userId: 'user-456',
110
+ token: 'your-jwt-token',
111
+ socketUrl: 'https://your-server.com',
112
+ callbacks: {
113
+ onStreamReceived: (userId, stream) => {
114
+ // Handle received video streams
115
+ console.log(`Received stream from ${userId}`);
116
+ }
117
+ }
118
+ });
119
+
120
+ return (
121
+ <div className="video-call">
122
+ <div className="controls">
123
+ <button onClick={() => startCall()}>Start Call</button>
124
+ <button onClick={() => joinCall()}>Join Call</button>
125
+ <button onClick={() => endCall()}>End Call</button>
126
+ <button onClick={() => toggleAudio(!localParticipant?.audioOn)}>
127
+ {localParticipant?.audioOn ? 'Mute' : 'Unmute'}
128
+ </button>
129
+ <button onClick={() => toggleVideo(!localParticipant?.videoOn)}>
130
+ {localParticipant?.videoOn ? 'Stop Video' : 'Start Video'}
131
+ </button>
132
+ <button onClick={() => startScreenShare()}>Share Screen</button>
133
+ </div>
134
+
135
+ <div className="participants">
136
+ {Object.values(participants).map(participant => (
137
+ <div key={participant.userId} className="participant">
138
+ <video
139
+ autoPlay
140
+ playsInline
141
+ ref={ref => {
142
+ if (ref && participant.stream) {
143
+ ref.srcObject = participant.stream;
144
+ }
145
+ }}
146
+ />
147
+ <span>{participant.userId}</span>
148
+ </div>
149
+ ))}
150
+ </div>
151
+
152
+ {error && <div className="error">Error: {error}</div>}
153
+ </div>
154
+ );
155
+ }
156
+ ```
157
+
158
+ ## ๐Ÿ—๏ธ Backend Signaling Requirements
159
+
160
+ PeersCaller requires a WebSocket signaling server to coordinate calls between peers. The server must implement the following Socket.IO events:
161
+
162
+ ### ๐Ÿ“ก Client-to-Server Events (Outgoing)
163
+
164
+ ```typescript
165
+ // Call Management
166
+ socket.emit('call.start', { conversationId: string });
167
+ socket.emit('call.join', { conversationId: string });
168
+ socket.emit('call.leave', { conversationId: string });
169
+ socket.emit('call.end', { conversationId: string, targetUserId?: string });
170
+ socket.emit('call.status', { conversationId: string });
171
+
172
+ // WebRTC Signaling
173
+ socket.emit('call.offer', {
174
+ to: string,
175
+ offer: RTCSessionDescriptionInit,
176
+ conversationId: string
177
+ });
178
+ socket.emit('call.answer', {
179
+ to: string,
180
+ answer: RTCSessionDescriptionInit,
181
+ conversationId: string
182
+ });
183
+ socket.emit('call.candidate', {
184
+ to: string,
185
+ candidate: RTCIceCandidateInit,
186
+ conversationId: string
187
+ });
188
+
189
+ // State Updates
190
+ socket.emit('call.state', {
191
+ to?: string,
192
+ state: Partial<CallParticipant>,
193
+ conversationId: string
194
+ });
195
+
196
+ // Recording & Transcription
197
+ socket.emit('call.recording.start', { conversationId: string, recordingId: string });
198
+ socket.emit('call.recording.chunk', { conversationId: string, recordingId: string, chunk: Blob });
199
+ socket.emit('call.recording.end', { conversationId: string, recordingId: string });
200
+ socket.emit('call.transcript', { conversationId: string, transcript: string, timestamp: number });
201
+ ```
202
+
203
+ ### ๐Ÿ“จ Server-to-Client Events (Incoming)
204
+
205
+ ```typescript
206
+ // Call Management Responses
207
+ socket.on('call.started', (data: {
208
+ conversationId: string,
209
+ userId: string,
210
+ success: boolean,
211
+ participants: string[]
212
+ }) => {});
213
+
214
+ socket.on('call.participant.joined', (data: {
215
+ userId: string,
216
+ participants: string[],
217
+ conversationId: string
218
+ }) => {});
219
+
220
+ socket.on('call.participant.left', (data: {
221
+ userId: string,
222
+ participants: string[],
223
+ conversationId: string
224
+ }) => {});
225
+
226
+ socket.on('call.participants', (data: {
227
+ participants: string[],
228
+ conversationId: string
229
+ }) => {});
230
+
231
+ socket.on('call.left', (data: {
232
+ conversationId: string,
233
+ success: boolean
234
+ }) => {});
235
+
236
+ socket.on('call.ended', (data: {
237
+ conversationId: string,
238
+ endedBy: string,
239
+ reason: string
240
+ }) => {});
241
+
242
+ socket.on('call.error', (data: {
243
+ error: string,
244
+ message: string
245
+ }) => {});
246
+
247
+ // WebRTC Signaling Forwarding
248
+ socket.on('call.offer', (data: {
249
+ from: string,
250
+ offer: RTCSessionDescriptionInit,
251
+ conversationId: string
252
+ }) => {});
253
+
254
+ socket.on('call.answer', (data: {
255
+ from: string,
256
+ answer: RTCSessionDescriptionInit,
257
+ conversationId: string
258
+ }) => {});
259
+
260
+ socket.on('call.candidate', (data: {
261
+ from: string,
262
+ candidate: RTCIceCandidateInit,
263
+ conversationId: string
264
+ }) => {});
265
+
266
+ socket.on('call.state', (data: {
267
+ from: string,
268
+ state: Partial<CallParticipant>,
269
+ conversationId: string
270
+ }) => {});
271
+
272
+ // Call Status Updates
273
+ socket.on('call.status.changed', (data: {
274
+ conversationId: string,
275
+ hasActiveCall: boolean,
276
+ participantCount: number,
277
+ maxParticipants: number,
278
+ participants: string[],
279
+ startedAt: Date | null,
280
+ canJoin: boolean,
281
+ status: "no_call" | "active" | "full" | "ending"
282
+ }) => {});
283
+
284
+ // Recording Events
285
+ socket.on('call.recording.start', (data: { recordingId: string, conversationId: string }) => {});
286
+ socket.on('call.recording.chunk.received', (data: { recordingId: string, conversationId: string, chunkSize: number, timestamp: number }) => {});
287
+ socket.on('call.recording.end', (data: { recordingId: string, conversationId: string }) => {});
288
+
289
+ // Transcription Events
290
+ socket.on('call.transcript', (data: { userId: string, transcript: string, timestamp: number, conversationId: string }) => {});
291
+ ```
292
+
293
+ ### ๐Ÿ” Authentication
294
+
295
+ The signaling server should authenticate connections using the provided JWT token:
296
+
297
+ ```typescript
298
+ // Client connection with auth
299
+ io(serverUrl, {
300
+ path: '/apis/video-call',
301
+ auth: {
302
+ token: 'your-jwt-token'
303
+ }
304
+ });
305
+ ```
306
+
307
+ ### ๐Ÿ“‹ Server Implementation Requirements
308
+
309
+ 1. **Room Management**: Track participants in conversation rooms
310
+ 2. **Message Forwarding**: Route WebRTC signaling between specific participants
311
+ 3. **Participant Limits**: Enforce maximum participant limits (default: 4)
312
+ 4. **Authentication**: Validate JWT tokens and extract user information
313
+ 5. **Error Handling**: Provide meaningful error messages and codes
314
+ 6. **Graceful Cleanup**: Handle disconnections and cleanup resources
315
+
316
+ ### ๐Ÿ”— Example Server Setup (Node.js + Socket.IO)
317
+
318
+ ```typescript
319
+ import { Server } from 'socket.io';
320
+ import jwt from 'jsonwebtoken';
321
+
322
+ const io = new Server(server, {
323
+ path: '/apis/video-call',
324
+ cors: { origin: "*" }
325
+ });
326
+
327
+ // Authentication middleware
328
+ io.use((socket, next) => {
329
+ const token = socket.handshake.auth.token;
330
+ try {
331
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
332
+ socket.userId = decoded.userId;
333
+ next();
334
+ } catch (err) {
335
+ next(new Error('Authentication failed'));
336
+ }
337
+ });
338
+
339
+ io.on('connection', (socket) => {
340
+ console.log(`User ${socket.userId} connected`);
341
+
342
+ // Handle call start
343
+ socket.on('call.start', async ({ conversationId }) => {
344
+ try {
345
+ // Join room
346
+ await socket.join(conversationId);
347
+
348
+ // Get existing participants
349
+ const room = io.sockets.adapter.rooms.get(conversationId);
350
+ const participants = Array.from(room || []);
351
+
352
+ // Emit success response
353
+ socket.emit('call.started', {
354
+ conversationId,
355
+ userId: socket.userId,
356
+ success: true,
357
+ participants: participants.map(id => io.sockets.sockets.get(id)?.userId).filter(Boolean)
358
+ });
359
+
360
+ // Notify other participants
361
+ socket.to(conversationId).emit('call.participant.joined', {
362
+ userId: socket.userId,
363
+ participants: participants.map(id => io.sockets.sockets.get(id)?.userId).filter(Boolean),
364
+ conversationId
365
+ });
366
+ } catch (error) {
367
+ socket.emit('call.error', { error: 'CALL_START_FAILED', message: error.message });
368
+ }
369
+ });
370
+
371
+ // Handle WebRTC signaling
372
+ socket.on('call.offer', ({ to, offer, conversationId }) => {
373
+ const targetSocket = Array.from(io.sockets.sockets.values())
374
+ .find(s => s.userId === to);
375
+
376
+ if (targetSocket) {
377
+ targetSocket.emit('call.offer', {
378
+ from: socket.userId,
379
+ offer,
380
+ conversationId
381
+ });
382
+ }
383
+ });
384
+
385
+ // Handle disconnection
386
+ socket.on('disconnect', () => {
387
+ // Notify rooms about participant leaving
388
+ socket.rooms.forEach(room => {
389
+ if (room !== socket.id) {
390
+ socket.to(room).emit('call.participant.left', {
391
+ userId: socket.userId,
392
+ conversationId: room
393
+ });
394
+ }
395
+ });
396
+ });
397
+ });
398
+ ```
399
+
400
+ ## ๐Ÿ“š API Reference
401
+
402
+ ### PeersCaller Class
403
+
404
+ The main orchestrator class for managing video calls.
405
+
406
+ #### Constructor
407
+
408
+ ```typescript
409
+ new PeersCaller(config: PeersCallerConfig, callbacks?: PeersCallerCallbacks)
410
+ ```
411
+
412
+ **Parameters:**
413
+ - `config`: Configuration object for the caller
414
+ - `callbacks`: Optional event callbacks
415
+
416
+ #### Methods
417
+
418
+ ##### `initialize(): Promise<void>`
419
+ Initialize the PeersCaller and establish WebSocket connection.
420
+
421
+ ##### `startCall(mediaConfig?: MediaStreamConfig): Promise<void>`
422
+ Start a new video call.
423
+
424
+ ##### `joinCall(mediaConfig?: MediaStreamConfig): Promise<void>`
425
+ Join an existing video call.
426
+
427
+ ##### `endCall(): Promise<void>`
428
+ End the call for all participants.
429
+
430
+ ##### `leaveCall(): Promise<void>`
431
+ Leave the call gracefully.
432
+
433
+ ##### `toggleAudio(enabled: boolean): void`
434
+ Enable or disable local audio.
435
+
436
+ ##### `toggleVideo(enabled: boolean): void`
437
+ Enable or disable local video.
438
+
439
+ ##### `startScreenShare(): Promise<void>`
440
+ Start sharing screen.
441
+
442
+ ##### `stopScreenShare(): Promise<void>`
443
+ Stop sharing screen.
444
+
445
+ ##### `startRecording(recordingData: RecordingData, config?: RecordingConfig): Promise<void>`
446
+ Start recording the call.
447
+
448
+ ##### `stopRecording(): Promise<void>`
449
+ Stop recording the call.
450
+
451
+ ##### `checkCallStatus(): Promise<CallStatusResponse>`
452
+ Check the current status of the call.
453
+
454
+ ##### `cleanup(): void`
455
+ Clean up all resources and disconnect.
456
+
457
+ ### Configuration Types
458
+
459
+ #### `PeersCallerConfig`
460
+
461
+ ```typescript
462
+ interface PeersCallerConfig {
463
+ conversationId: string; // Unique conversation identifier
464
+ userId: string; // Current user's unique identifier
465
+ token: string; // JWT authentication token
466
+ socketUrl: string; // WebSocket server URL
467
+ socketPath?: string; // Socket.IO path (default: '/apis/video-call')
468
+ iceServers?: RTCIceServer[]; // STUN/TURN servers
469
+ mediaConfig?: MediaStreamConfig; // Default media configuration
470
+ maxParticipants?: number; // Maximum participants (default: 4)
471
+ debug?: boolean; // Enable debug logging
472
+ }
473
+ ```
474
+
475
+ #### `MediaStreamConfig`
476
+
477
+ ```typescript
478
+ interface MediaStreamConfig {
479
+ video: boolean | MediaTrackConstraints;
480
+ audio: boolean | MediaTrackConstraints;
481
+ }
482
+ ```
483
+
484
+ #### `PeersCallerCallbacks`
485
+
486
+ ```typescript
487
+ interface PeersCallerCallbacks {
488
+ onCallStarted?: (data: { conversationId: string; success: boolean; participants: string[] }) => void;
489
+ onCallEnded?: (data: { conversationId: string; endedBy: string; reason: string }) => void;
490
+ onParticipantJoined?: (participant: CallParticipant) => void;
491
+ onParticipantLeft?: (userId: string) => void;
492
+ onParticipantStateChanged?: (userId: string, state: Partial<CallParticipant>) => void;
493
+ onStreamReceived?: (userId: string, stream: MediaStream) => void;
494
+ onCallStateChanged?: (state: "idle" | "connecting" | "connected" | "disconnecting" | "failed") => void;
495
+ onCallStatusChanged?: (statusInfo: CallStatusResponse) => void;
496
+ onRecordingStateChanged?: (isRecording: boolean) => void;
497
+ onError?: (error: PeersCallerError, message: string) => void;
498
+ }
499
+ ```
500
+
501
+ ### React Hooks
502
+
503
+ #### `useVideoCall(options: UseVideoCallOptions)`
504
+
505
+ A comprehensive React hook for video call functionality.
506
+
507
+ ```typescript
508
+ const {
509
+ // Core methods
510
+ initialize,
511
+ startCall,
512
+ joinCall,
513
+ endCall,
514
+
515
+ // Media controls
516
+ toggleAudio,
517
+ toggleVideo,
518
+ startScreenShare,
519
+ stopScreenShare,
520
+
521
+ // Recording
522
+ startRecording,
523
+ stopRecording,
524
+
525
+ // State
526
+ callState,
527
+ participants,
528
+ localParticipant,
529
+ isConnected,
530
+ isRecording,
531
+ error,
532
+
533
+ // Utility
534
+ cleanup,
535
+ peersCaller
536
+ } = useVideoCall(options);
537
+ ```
538
+
539
+ ### Error Types
540
+
541
+ ```typescript
542
+ type PeersCallerError =
543
+ | "MEDIA_ACCESS_DENIED"
544
+ | "PEER_CONNECTION_FAILED"
545
+ | "SIGNALING_ERROR"
546
+ | "RECORDING_FAILED"
547
+ | "TRANSCRIPTION_FAILED"
548
+ | "CALL_LIMIT_EXCEEDED"
549
+ | "INVALID_CONVERSATION_ID"
550
+ | "NETWORK_ERROR"
551
+ | "UNKNOWN_ERROR";
552
+ ```
553
+
554
+ ## ๐Ÿ”ง Advanced Usage
555
+
556
+ ### Custom Media Constraints
557
+
558
+ ```typescript
559
+ const peersCaller = new PeersCaller({
560
+ // ... other config
561
+ mediaConfig: {
562
+ video: {
563
+ width: { ideal: 1280 },
564
+ height: { ideal: 720 },
565
+ frameRate: { ideal: 30 }
566
+ },
567
+ audio: {
568
+ echoCancellation: true,
569
+ noiseSuppression: true,
570
+ autoGainControl: true
571
+ }
572
+ }
573
+ });
574
+ ```
575
+
576
+ ### Custom ICE Servers
577
+
578
+ ```typescript
579
+ const peersCaller = new PeersCaller({
580
+ // ... other config
581
+ iceServers: [
582
+ { urls: 'stun:stun.l.google.com:19302' },
583
+ {
584
+ urls: 'turn:your-turn-server.com:3478',
585
+ username: 'username',
586
+ credential: 'password'
587
+ }
588
+ ]
589
+ });
590
+ ```
591
+
592
+ ### Recording with Custom Configuration
593
+
594
+ ```typescript
595
+ await peersCaller.startRecording(
596
+ {
597
+ id: 'recording-123',
598
+ filename: 'meeting-recording.webm',
599
+ conversationId: 'conversation-456',
600
+ startTime: Date.now()
601
+ },
602
+ {
603
+ mimeType: 'video/webm;codecs=vp9',
604
+ videoBitsPerSecond: 2500000,
605
+ audioBitsPerSecond: 128000,
606
+ interval: 1000 // 1 second chunks
607
+ }
608
+ );
609
+ ```
610
+
611
+ ### State Management Integration
612
+
613
+ ```typescript
614
+ import { useCallStore } from '@sawport/peers-caller';
615
+
616
+ function CallStatus() {
617
+ const {
618
+ isCalling,
619
+ callStatus,
620
+ participants,
621
+ error
622
+ } = useCallStore();
623
+
624
+ return (
625
+ <div>
626
+ <p>Status: {callStatus}</p>
627
+ <p>Participants: {Object.keys(participants).length}</p>
628
+ {error && <p>Error: {error}</p>}
629
+ </div>
630
+ );
631
+ }
632
+ ```
633
+
634
+ ## ๐Ÿงช Development
635
+
636
+ ### Prerequisites
637
+
638
+ - Node.js 18+ (recommended: 20+)
639
+ - Yarn (using Yarn Berry/v3+)
640
+
641
+ ### Setup
642
+
643
+ ```bash
644
+ # Clone the repository
645
+ git clone https://github.com/sawport/peers-caller.git
646
+ cd peers-caller
647
+
648
+ # Install dependencies
649
+ yarn install
650
+
651
+ # Start development server with hot reload
652
+ yarn dev
653
+
654
+ # Build the library
655
+ yarn build
656
+
657
+ # Build TypeScript declarations only
658
+ yarn build:types
659
+ ```
660
+
661
+ ### Development Scripts
662
+
663
+ ```bash
664
+ # Development
665
+ yarn dev # Start Vite dev server with hot reload
666
+ yarn build # Build production bundle
667
+ yarn preview # Preview production build
668
+
669
+ # Testing
670
+ yarn test # Run tests once
671
+ yarn test:watch # Run tests in watch mode
672
+ yarn test:ui # Open Vitest UI
673
+ yarn test:coverage # Generate coverage report
674
+
675
+ # Type checking
676
+ yarn type-check # Check TypeScript types without building
677
+ ```
678
+
679
+ ### Testing
680
+
681
+ This project uses **Vitest** for testing with comprehensive coverage reporting and WebRTC API mocking.
682
+
683
+ This project uses Vitest for testing with comprehensive coverage reporting.
684
+
685
+ #### Running Tests
686
+
687
+ ```bash
688
+ # Run tests once
689
+ yarn test
690
+
691
+ # Run tests in watch mode (for development)
692
+ yarn test:watch
693
+
694
+ # Run tests with coverage report
695
+ yarn test:coverage
696
+
697
+ # Open Vitest UI
698
+ yarn test:ui
699
+ ```
700
+
701
+ #### Test Structure
702
+
703
+ - `__tests__/` - Integration and setup tests
704
+ - `src/**/*.test.ts` - Unit tests alongside source code
705
+ - `src/test-utils.ts` - Shared test utilities and mocks
706
+
707
+ #### Writing Tests
708
+
709
+ The test environment includes:
710
+
711
+ - **WebRTC API mocks** - RTCPeerConnection, MediaDevices, etc.
712
+ - **External library mocks** - simple-peer, socket.io-client
713
+ - **jsdom environment** - For DOM testing
714
+ - **TypeScript support** - Full type checking in tests
715
+
716
+ Example test:
717
+
718
+ ```typescript
719
+ import { describe, it, expect } from 'vitest';
720
+ import { generatePeerId } from '../utils';
721
+
722
+ describe('generatePeerId', () => {
723
+ it('should generate unique peer IDs', () => {
724
+ const id1 = generatePeerId();
725
+ const id2 = generatePeerId();
726
+
727
+ expect(id1).not.toBe(id2);
728
+ expect(id1).toMatch(/^peer_\d+_[a-z0-9]{1,9}$/);
729
+ });
730
+ });
731
+ ```
732
+
733
+ #### Coverage Thresholds
734
+
735
+ The project maintains high test coverage standards:
736
+
737
+ - **Branches**: 80%
738
+ - **Functions**: 80%
739
+ - **Lines**: 80%
740
+ - **Statements**: 80%
741
+
742
+ ### CI/CD
743
+
744
+ GitHub Actions automatically:
745
+
746
+ - โœ… Runs tests on Node.js 18.x, 20.x, 22.x
747
+ - โœ… Checks TypeScript compilation
748
+ - โœ… Generates coverage reports
749
+ - โœ… Builds the library
750
+ - โœ… Uploads coverage to Codecov (optional)
751
+
752
+ ## API Reference
753
+
754
+ ### Utilities
755
+
756
+ ### Store
757
+
758
+
759
+ ### Types
760
+
761
+ ## Contributing
762
+
763
+ 1. Fork the repository
764
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
765
+ 3. Make your changes
766
+ 4. Add tests for new functionality
767
+ 5. Ensure tests pass (`yarn test`)
768
+ 6. Commit your changes (`git commit -m 'Add amazing feature'`)
769
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
770
+ 8. Open a Pull Request
771
+
772
+ ### Development Guidelines
773
+
774
+ - Write tests for all new functionality
775
+ - Maintain TypeScript strict mode compliance
776
+ - Follow the existing code style
777
+ - Update documentation as needed
778
+ - Ensure CI passes before submitting PR
779
+
780
+ ## License
781
+
782
+ MIT License - see LICENSE file for details.