@tbisoftware/phone 1.0.11 β 1.0.12
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/README.md +309 -7
- package/dist/index.cjs +29 -29
- package/dist/index.js +2305 -2112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,25 @@ A reusable SIP phone component for React applications built with Tailwind CSS an
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
```bash
|
|
7
8
|
npm install @tbisoftware/phone
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- π Full SIP/WebRTC phone functionality
|
|
14
|
+
- π¨ Beautiful UI built with Tailwind CSS
|
|
15
|
+
- πͺ Headless mode with `usePhoneManager` hook for custom UIs
|
|
16
|
+
- π± Call history with localStorage persistence
|
|
17
|
+
- π Internationalization support with custom labels
|
|
18
|
+
- β‘ Singleton pattern for reliable WebSocket connections
|
|
8
19
|
|
|
9
20
|
## Usage
|
|
10
21
|
|
|
22
|
+
### Option 1: Ready-to-use Component
|
|
23
|
+
|
|
24
|
+
The simplest way to add a phone to your app:
|
|
25
|
+
|
|
11
26
|
```tsx
|
|
12
27
|
import { Phone } from "@tbisoftware/phone";
|
|
13
28
|
|
|
@@ -21,17 +36,304 @@ const config = {
|
|
|
21
36
|
};
|
|
22
37
|
|
|
23
38
|
function App() {
|
|
24
|
-
return
|
|
39
|
+
return (
|
|
40
|
+
<Phone
|
|
41
|
+
config={config}
|
|
42
|
+
onCallStart={(number) => console.log('Calling:', number)}
|
|
43
|
+
onCallEnd={(number, duration, status) => {
|
|
44
|
+
console.log(`Call to ${number} ${status}. Duration: ${duration}s`);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option 2: Headless Hook for Custom UI
|
|
52
|
+
|
|
53
|
+
Use `usePhoneManager` to build your own phone interface:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { usePhoneManager, formatDuration } from "@tbisoftware/phone";
|
|
57
|
+
|
|
58
|
+
const config = {
|
|
59
|
+
websocketUrl: "wss://your-sip-server.com:8989",
|
|
60
|
+
sipUri: "sip:user@domain.com",
|
|
61
|
+
password: "your-password",
|
|
62
|
+
registrarServer: "sip:domain.com",
|
|
63
|
+
displayName: "User Name",
|
|
64
|
+
authorizationUser: "auth-user",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function CustomPhone() {
|
|
68
|
+
const {
|
|
69
|
+
status,
|
|
70
|
+
callNumber,
|
|
71
|
+
setCallNumber,
|
|
72
|
+
callHistory,
|
|
73
|
+
currentCallDuration,
|
|
74
|
+
startCall,
|
|
75
|
+
endCall,
|
|
76
|
+
isReady,
|
|
77
|
+
connectionStatus,
|
|
78
|
+
} = usePhoneManager(config, {
|
|
79
|
+
onCallStart: (number) => console.log('Starting call to:', number),
|
|
80
|
+
onCallEnd: (number, duration, status) => {
|
|
81
|
+
console.log(`Call ended: ${number}, ${duration}s, ${status}`);
|
|
82
|
+
},
|
|
83
|
+
onStatusChange: (status) => console.log('Status:', status),
|
|
84
|
+
onConnectionChange: (status) => console.log('Connection:', status),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="p-4 border rounded-lg">
|
|
89
|
+
{/* Connection indicator */}
|
|
90
|
+
<div className="flex items-center gap-2 mb-4">
|
|
91
|
+
<div className={`w-2 h-2 rounded-full ${
|
|
92
|
+
connectionStatus === 'connected' ? 'bg-green-500' :
|
|
93
|
+
connectionStatus === 'connecting' ? 'bg-yellow-500' :
|
|
94
|
+
'bg-red-500'
|
|
95
|
+
}`} />
|
|
96
|
+
<span className="text-sm text-gray-500">
|
|
97
|
+
{connectionStatus === 'connected' ? 'Ready' :
|
|
98
|
+
connectionStatus === 'connecting' ? 'Connecting...' :
|
|
99
|
+
'Disconnected'}
|
|
100
|
+
</span>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Idle state - show input */}
|
|
104
|
+
{status === 'disconnected' && (
|
|
105
|
+
<div className="flex gap-2">
|
|
106
|
+
<input
|
|
107
|
+
type="tel"
|
|
108
|
+
value={callNumber}
|
|
109
|
+
onChange={(e) => setCallNumber(e.target.value)}
|
|
110
|
+
onKeyDown={(e) => e.key === 'Enter' && startCall(callNumber)}
|
|
111
|
+
placeholder="Enter phone number"
|
|
112
|
+
className="flex-1 px-3 py-2 border rounded"
|
|
113
|
+
/>
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => startCall(callNumber)}
|
|
116
|
+
disabled={!isReady || callNumber.length < 9}
|
|
117
|
+
className="px-4 py-2 bg-green-500 text-white rounded disabled:opacity-50"
|
|
118
|
+
>
|
|
119
|
+
Call
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{/* Calling state */}
|
|
125
|
+
{status === 'progress' && (
|
|
126
|
+
<div className="text-center">
|
|
127
|
+
<p className="text-lg">Calling {callNumber}...</p>
|
|
128
|
+
<button
|
|
129
|
+
onClick={endCall}
|
|
130
|
+
className="mt-4 px-4 py-2 bg-red-500 text-white rounded"
|
|
131
|
+
>
|
|
132
|
+
Cancel
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* In call */}
|
|
138
|
+
{status === 'confirmed' && (
|
|
139
|
+
<div className="text-center">
|
|
140
|
+
<p className="text-lg font-bold">{callNumber}</p>
|
|
141
|
+
<p className="text-2xl font-mono text-green-600">
|
|
142
|
+
{formatDuration(currentCallDuration)}
|
|
143
|
+
</p>
|
|
144
|
+
<button
|
|
145
|
+
onClick={endCall}
|
|
146
|
+
className="mt-4 px-4 py-2 bg-red-500 text-white rounded"
|
|
147
|
+
>
|
|
148
|
+
Hang Up
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Call history */}
|
|
154
|
+
{status === 'disconnected' && callHistory.length > 0 && (
|
|
155
|
+
<div className="mt-4">
|
|
156
|
+
<h3 className="text-sm font-semibold mb-2">Recent Calls</h3>
|
|
157
|
+
<ul className="space-y-1">
|
|
158
|
+
{callHistory.slice(0, 5).map((entry) => (
|
|
159
|
+
<li
|
|
160
|
+
key={entry.id}
|
|
161
|
+
className="flex justify-between text-sm cursor-pointer hover:bg-gray-50 p-1 rounded"
|
|
162
|
+
onClick={() => {
|
|
163
|
+
setCallNumber(entry.number);
|
|
164
|
+
startCall(entry.number);
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<span>{entry.number}</span>
|
|
168
|
+
<span className={
|
|
169
|
+
entry.status === 'completed' ? 'text-green-500' :
|
|
170
|
+
entry.status === 'failed' ? 'text-red-500' :
|
|
171
|
+
'text-yellow-500'
|
|
172
|
+
}>
|
|
173
|
+
{entry.status}
|
|
174
|
+
</span>
|
|
175
|
+
</li>
|
|
176
|
+
))}
|
|
177
|
+
</ul>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Option 3: Using Provider and usePhone Hook
|
|
186
|
+
|
|
187
|
+
For more complex scenarios where you need to access phone state from multiple components:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { PhoneProvider, usePhone } from "@tbisoftware/phone";
|
|
191
|
+
|
|
192
|
+
const config = {
|
|
193
|
+
websocketUrl: "wss://your-sip-server.com:8989",
|
|
194
|
+
sipUri: "sip:user@domain.com",
|
|
195
|
+
password: "your-password",
|
|
196
|
+
registrarServer: "sip:domain.com",
|
|
197
|
+
displayName: "User Name",
|
|
198
|
+
authorizationUser: "auth-user",
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Wrap your app with the provider
|
|
202
|
+
function App() {
|
|
203
|
+
return (
|
|
204
|
+
<PhoneProvider config={config}>
|
|
205
|
+
<PhoneDialer />
|
|
206
|
+
<CallStatus />
|
|
207
|
+
</PhoneProvider>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Access phone state from any child component
|
|
212
|
+
function PhoneDialer() {
|
|
213
|
+
const { callNumber, setCallNumber, startCall, isReady } = usePhone();
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div>
|
|
217
|
+
<input
|
|
218
|
+
value={callNumber}
|
|
219
|
+
onChange={(e) => setCallNumber(e.target.value)}
|
|
220
|
+
/>
|
|
221
|
+
<button onClick={() => startCall(callNumber)} disabled={!isReady}>
|
|
222
|
+
Call
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function CallStatus() {
|
|
229
|
+
const { status, currentCallDuration, endCall } = usePhone();
|
|
230
|
+
|
|
231
|
+
if (status === 'disconnected') return null;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div>
|
|
235
|
+
<p>Status: {status}</p>
|
|
236
|
+
{status === 'confirmed' && <p>Duration: {currentCallDuration}s</p>}
|
|
237
|
+
<button onClick={endCall}>End Call</button>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
25
240
|
}
|
|
26
241
|
```
|
|
27
242
|
|
|
28
|
-
##
|
|
243
|
+
## API Reference
|
|
244
|
+
|
|
245
|
+
### `<Phone />` Component Props
|
|
246
|
+
|
|
247
|
+
| Prop | Type | Description |
|
|
248
|
+
|------|------|-------------|
|
|
249
|
+
| `config` | `PhoneConfig` | Required. SIP configuration object |
|
|
250
|
+
| `className` | `string` | Optional. Additional CSS classes |
|
|
251
|
+
| `onCallStart` | `(number: string) => void` | Callback when a call starts |
|
|
252
|
+
| `onCallEnd` | `(number: string, duration: number, status: 'completed' \| 'failed') => void` | Callback when a call ends |
|
|
253
|
+
| `onStatusChange` | `(status: PhoneStatus) => void` | Callback when status changes |
|
|
254
|
+
| `labels` | `Partial<PhoneLabels>` | Custom labels for internationalization |
|
|
255
|
+
|
|
256
|
+
### `usePhoneManager(config, options)` Hook
|
|
257
|
+
|
|
258
|
+
Returns an object with:
|
|
259
|
+
|
|
260
|
+
| Property | Type | Description |
|
|
261
|
+
|----------|------|-------------|
|
|
262
|
+
| `status` | `PhoneStatus` | Current call status: `'disconnected' \| 'progress' \| 'confirmed' \| 'failed' \| 'ended'` |
|
|
263
|
+
| `callNumber` | `string` | Current phone number |
|
|
264
|
+
| `setCallNumber` | `(number: string) => void` | Set the phone number |
|
|
265
|
+
| `callHistory` | `CallHistoryEntry[]` | Array of past calls |
|
|
266
|
+
| `clearCallHistory` | `() => void` | Clear the call history |
|
|
267
|
+
| `currentCallDuration` | `number` | Duration of current call in seconds |
|
|
268
|
+
| `startCall` | `(number: string) => void` | Start a call |
|
|
269
|
+
| `endCall` | `() => void` | End the current call |
|
|
270
|
+
| `isReady` | `boolean` | Whether the phone is registered and ready |
|
|
271
|
+
| `connectionStatus` | `ConnectionStatus` | `'connecting' \| 'connected' \| 'disconnected' \| 'failed'` |
|
|
272
|
+
| `ua` | `JsSIP.UA \| null` | Raw JsSIP User Agent for advanced usage |
|
|
273
|
+
|
|
274
|
+
#### Options
|
|
29
275
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
276
|
+
| Option | Type | Default | Description |
|
|
277
|
+
|--------|------|---------|-------------|
|
|
278
|
+
| `onCallStart` | `(number: string) => void` | - | Callback when call starts |
|
|
279
|
+
| `onCallEnd` | `(number, duration, status) => void` | - | Callback when call ends |
|
|
280
|
+
| `onStatusChange` | `(status: PhoneStatus) => void` | - | Callback on status change |
|
|
281
|
+
| `onConnectionChange` | `(status: ConnectionStatus) => void` | - | Callback on connection change |
|
|
282
|
+
| `persistHistory` | `boolean` | `true` | Save history to localStorage |
|
|
283
|
+
| `historyKey` | `string` | `'tbi-phone-call-history'` | localStorage key for history |
|
|
284
|
+
|
|
285
|
+
### `PhoneConfig` Type
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
interface PhoneConfig {
|
|
289
|
+
websocketUrl: string; // WebSocket URL of SIP server
|
|
290
|
+
sipUri: string; // SIP URI (sip:user@domain.com)
|
|
291
|
+
password: string; // SIP password
|
|
292
|
+
registrarServer: string; // SIP registrar server
|
|
293
|
+
displayName: string; // Display name for caller ID
|
|
294
|
+
authorizationUser: string; // Authorization username
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `PhoneLabels` Type
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
interface PhoneLabels {
|
|
302
|
+
placeholder: string; // Default: "Ingresa un nΓΊmero"
|
|
303
|
+
calling: string; // Default: "Llamando"
|
|
304
|
+
inCall: string; // Default: "En llamada"
|
|
305
|
+
callEnded: string; // Default: "Llamada finalizada"
|
|
306
|
+
inactive: string; // Default: "Inactivo"
|
|
307
|
+
waitingResponse: string; // Default: "Esperando respuesta..."
|
|
308
|
+
cancel: string; // Default: "Cancelar"
|
|
309
|
+
hangUp: string; // Default: "Colgar"
|
|
310
|
+
callHistory: string; // Default: "Historial de llamadas"
|
|
311
|
+
noCallsRegistered: string;// Default: "No hay llamadas registradas"
|
|
312
|
+
callsRegistered: string; // Default: "llamadas registradas"
|
|
313
|
+
noCalls: string; // Default: "No hay llamadas en el historial"
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Triggering Calls from Outside
|
|
318
|
+
|
|
319
|
+
You can trigger a call from anywhere in your app using a custom event:
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// Dispatch this event to start a call
|
|
323
|
+
window.dispatchEvent(new CustomEvent('StartCallEvent', {
|
|
324
|
+
detail: { number: '+1234567890' }
|
|
325
|
+
}));
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Styling
|
|
329
|
+
|
|
330
|
+
The component uses Tailwind CSS classes. Make sure you have Tailwind CSS configured in your project. All components have the `tbi-phone` class for custom styling:
|
|
331
|
+
|
|
332
|
+
```css
|
|
333
|
+
.tbi-phone {
|
|
334
|
+
/* Your custom styles */
|
|
335
|
+
}
|
|
336
|
+
```
|
|
35
337
|
|
|
36
338
|
## License
|
|
37
339
|
|