@dora-cell/sdk 0.1.1-beta.6

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 ADDED
@@ -0,0 +1,276 @@
1
+ # @dora-cell/sdk
2
+
3
+ VoIP calling SDK for Dora Cell - Make calls from any JavaScript application without logging into the Dora Cell web app.
4
+
5
+ ## Features
6
+
7
+ ✅ **Framework-agnostic** - Works with vanilla JS, React, Vue, Angular, etc.
8
+ ✅ **TypeScript support** - Full type definitions included
9
+ ✅ **WebRTC-based** - High-quality voice calls using JsSIP
10
+ ✅ **Event-driven API** - Listen to call state changes
11
+ ✅ **API token authentication** - Secure, no user login required
12
+ ✅ **Auto-extension selection** - Automatically selects first available extension
13
+ ✅ **React bindings** - Optional React hooks for easy integration
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @dora-cell/sdk jssip
19
+ # or
20
+ yarn add @dora-cell/sdk jssip
21
+ # or
22
+ pnpm add @dora-cell/sdk jssip
23
+ ```
24
+
25
+ For React projects:
26
+ ```bash
27
+ npm install @dora-cell/sdk @dora-cell/sdk-react jssip
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Vanilla JavaScript
33
+
34
+ ```javascript
35
+ import { DoraCell } from '@dora-cell/sdk';
36
+
37
+ // Initialize SDK with API token
38
+ const sdk = new DoraCell({
39
+ auth: {
40
+ type: 'api-token',
41
+ apiToken: 'your-api-token-here',
42
+ apiBaseUrl: 'https://api.usedora.com'
43
+ },
44
+ debug: true // Enable console logging
45
+ });
46
+
47
+ // Initialize connection
48
+ await sdk.initialize();
49
+
50
+ // Listen for connection status
51
+ sdk.on('connection:status', (state) => {
52
+ console.log('Connection status:', state.status);
53
+ });
54
+
55
+ // Listen for call events
56
+ sdk.on('call:connected', (call) => {
57
+ console.log('Call connected:', call.remoteNumber);
58
+ });
59
+
60
+ sdk.on('call:ended', (call, reason) => {
61
+ console.log('Call ended:', reason);
62
+ });
63
+
64
+ // Make a call
65
+ const call = await sdk.call('+2348012345678');
66
+
67
+ // Mute/unmute
68
+ call.mute();
69
+ call.unmute();
70
+
71
+ // Hang up
72
+ call.hangup();
73
+ ```
74
+
75
+ ### React
76
+
77
+ ```tsx
78
+ import { DoraCellProvider, useCall, useConnectionStatus } from '@dora-cell/sdk-react';
79
+
80
+ function App() {
81
+ return (
82
+ <DoraCellProvider
83
+ config={{
84
+ auth: {
85
+ type: 'api-token',
86
+ apiToken: 'your-api-token-here',
87
+ apiBaseUrl: 'https://api.usedora.com'
88
+ }
89
+ }}
90
+ >
91
+ <CallInterface />
92
+ </DoraCellProvider>
93
+ );
94
+ }
95
+
96
+ function CallInterface() {
97
+ const { call, hangup, toggleMute, callStatus, callDuration, isMuted } = useCall();
98
+ const { isConnected } = useConnectionStatus();
99
+ const [number, setNumber] = useState('');
100
+
101
+ const handleCall = async () => {
102
+ try {
103
+ await call(number);
104
+ } catch (error) {
105
+ console.error('Call failed:', error);
106
+ }
107
+ };
108
+
109
+ return (
110
+ <div>
111
+ <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
112
+ <input
113
+ value={number}
114
+ onChange={(e) => setNumber(e.target.value)}
115
+ placeholder="Enter phone number"
116
+ />
117
+ <button onClick={handleCall} disabled={!isConnected}>
118
+ Call
119
+ </button>
120
+ {callStatus === 'ongoing' && (
121
+ <>
122
+ <p>Duration: {callDuration}</p>
123
+ <button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
124
+ <button onClick={hangup}>Hang Up</button>
125
+ </>
126
+ )}
127
+ </div>
128
+ );
129
+ }
130
+ ```
131
+
132
+ ## API Reference
133
+
134
+ ### DoraCell Class
135
+
136
+ #### Constructor
137
+
138
+ ```typescript
139
+ new DoraCell(config: DoraCellConfig)
140
+ ```
141
+
142
+ **Config options:**
143
+ - `auth` - Authentication configuration (required)
144
+ - `type: 'api-token'` - Use API token authentication
145
+ - `apiToken: string` - Your Dora Cell API token
146
+ - `apiBaseUrl?: string` - API base URL (optional)
147
+ - `turnServers?: RTCIceServer[]` - Custom TURN/STUN servers
148
+ - `debug?: boolean` - Enable debug logging
149
+ - `autoSelectExtension?: boolean` - Auto-select first extension (default: true)
150
+
151
+ #### Methods
152
+
153
+ **`initialize(): Promise<void>`**
154
+ Initialize the SDK and connect to SIP server.
155
+
156
+ **`call(phoneNumber: string, options?: CallOptions): Promise<Call>`**
157
+ Make an outbound call.
158
+
159
+ Options:
160
+ - `extension?: string` - Specific extension to use for this call
161
+
162
+ **`hangup(): void`**
163
+ Hang up the current call.
164
+
165
+ **`answerCall(): void`**
166
+ Answer an incoming call.
167
+
168
+ **`getCurrentCall(): Call | null`**
169
+ Get the current active call.
170
+
171
+ **`getStatus(): ConnectionStatus`**
172
+ Get current connection status.
173
+
174
+ **`getExtensions(): string[]`**
175
+ Get list of available extensions.
176
+
177
+ **`on(event, handler): void`**
178
+ Register event listener.
179
+
180
+ **`off(event, handler): void`**
181
+ Remove event listener.
182
+
183
+ **`destroy(): void`**
184
+ Cleanup and destroy SDK instance.
185
+
186
+ ### Events
187
+
188
+ ```typescript
189
+ sdk.on('connection:status', (state: ConnectionState) => {});
190
+ sdk.on('call:incoming', (call: Call) => {});
191
+ sdk.on('call:outgoing', (call: Call) => {});
192
+ sdk.on('call:ringing', (call: Call) => {});
193
+ sdk.on('call:connected', (call: Call) => {});
194
+ sdk.on('call:ended', (call: Call, reason?: string) => {});
195
+ sdk.on('call:failed', (call: Call, error: string) => {});
196
+ sdk.on('error', (error: Error) => {});
197
+ ```
198
+
199
+ ### Call Object
200
+
201
+ ```typescript
202
+ interface Call {
203
+ id: string;
204
+ status: CallStatus;
205
+ direction: 'inbound' | 'outbound';
206
+ remoteNumber: string;
207
+ localExtension: string;
208
+ duration: number;
209
+
210
+ mute(): void;
211
+ unmute(): void;
212
+ hangup(): void;
213
+ isMuted(): boolean;
214
+ }
215
+ ```
216
+
217
+ ## Authentication
218
+
219
+ ### Getting an API Token
220
+
221
+ 1. Log in to your Dora Cell dashboard
222
+ 2. Navigate to Settings → API Tokens
223
+ 3. Generate a new API token
224
+ 4. Copy the token and use it in your SDK configuration
225
+
226
+ ### Backend API Endpoint
227
+
228
+ The SDK expects your backend to provide a `/api/sip-credentials` endpoint that:
229
+ - Accepts `Authorization: Bearer <token>` header
230
+ - Returns SIP credentials in this format:
231
+
232
+ ```json
233
+ {
234
+ "ws_url": "wss://cell.usedora.com:8089/ws",
235
+ "sip_uri": "sip:101@64.227.10.164",
236
+ "password": "your-password",
237
+ "extensions": [
238
+ { "extension": "101", "displayName": "Main Line" }
239
+ ]
240
+ }
241
+ ```
242
+
243
+ ## Phone Number Formatting
244
+
245
+ The SDK automatically handles Nigerian phone number formatting:
246
+
247
+ - `08012345678` → `sip:2348012345678@domain`
248
+ - `2348012345678` → `sip:2348012345678@domain`
249
+ - `101` (extension) → `sip:101@domain`
250
+
251
+ ## Error Handling
252
+
253
+ ```javascript
254
+ import { AuthenticationError, CallError, ConnectionError } from '@dora-cell/sdk';
255
+
256
+ try {
257
+ await sdk.initialize();
258
+ } catch (error) {
259
+ if (error instanceof AuthenticationError) {
260
+ console.error('Authentication failed:', error.message);
261
+ } else if (error instanceof ConnectionError) {
262
+ console.error('Connection failed:', error.message);
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Examples
268
+
269
+ See the `/examples` directory for complete working examples:
270
+ - `vanilla-js/` - Pure JavaScript implementation
271
+ - `react-app/` - React application
272
+ - `nextjs-app/` - Next.js application
273
+
274
+ ## License
275
+
276
+ MIT
@@ -0,0 +1,210 @@
1
+ import EventEmitter from 'eventemitter3';
2
+
3
+ /**
4
+ * Core TypeScript type definitions for Dora Cell SDK
5
+ */
6
+ interface ApiTokenAuth {
7
+ type: 'api-token';
8
+ apiToken: string;
9
+ apiBaseUrl?: string;
10
+ }
11
+ interface DirectCredentialsAuth {
12
+ type: 'direct';
13
+ sipUri: string;
14
+ password: string;
15
+ wsUrl: string;
16
+ }
17
+ type AuthConfig = ApiTokenAuth | DirectCredentialsAuth;
18
+ interface DoraCellConfig {
19
+ auth: AuthConfig;
20
+ sipServer?: string;
21
+ turnServers?: RTCIceServer[];
22
+ debug?: boolean;
23
+ autoSelectExtension?: boolean;
24
+ }
25
+ interface SipCredentials {
26
+ wsUrl: string;
27
+ sipUri: string;
28
+ password: string;
29
+ sipDomain?: string;
30
+ extensions?: Extension[];
31
+ expiresIn?: number;
32
+ }
33
+ interface Extension {
34
+ extension: string;
35
+ displayName?: string;
36
+ isPrimary?: boolean;
37
+ }
38
+ type CallStatus = 'idle' | 'connecting' | 'ringing' | 'ongoing' | 'ended';
39
+ type CallDirection = 'inbound' | 'outbound';
40
+ interface CallOptions {
41
+ extension?: string;
42
+ mediaConstraints?: MediaStreamConstraints;
43
+ }
44
+ interface Call {
45
+ id: string;
46
+ status: CallStatus;
47
+ direction: CallDirection;
48
+ remoteNumber: string;
49
+ localExtension: string;
50
+ duration: number;
51
+ startTime?: number;
52
+ endTime?: number;
53
+ mute(): void;
54
+ unmute(): void;
55
+ hangup(): void;
56
+ isMuted(): boolean;
57
+ }
58
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'registered' | 'registrationFailed';
59
+ interface ConnectionState {
60
+ status: ConnectionStatus;
61
+ extension?: string;
62
+ error?: string;
63
+ }
64
+ type DoraCellEventMap = {
65
+ 'connection:status': (state: ConnectionState) => void;
66
+ 'call:incoming': (call: Call) => void;
67
+ 'call:outgoing': (call: Call) => void;
68
+ 'call:ringing': (call: Call) => void;
69
+ 'call:connected': (call: Call) => void;
70
+ 'call:ended': (call: Call, reason?: string) => void;
71
+ 'call:failed': (call: Call, error: string) => void;
72
+ 'error': (error: Error) => void;
73
+ };
74
+ type DoraCellEvent = keyof DoraCellEventMap;
75
+ declare class DoraCellError extends Error {
76
+ code?: string | undefined;
77
+ details?: any | undefined;
78
+ constructor(message: string, code?: string | undefined, details?: any | undefined);
79
+ }
80
+ declare class AuthenticationError extends DoraCellError {
81
+ constructor(message: string, details?: any);
82
+ }
83
+ declare class CallError extends DoraCellError {
84
+ constructor(message: string, details?: any);
85
+ }
86
+ declare class ConnectionError extends DoraCellError {
87
+ constructor(message: string, details?: any);
88
+ }
89
+
90
+ /**
91
+ * Dora Cell SDK - Main entry point
92
+ * Framework-agnostic VoIP calling SDK using JsSIP
93
+ */
94
+
95
+ declare class DoraCell {
96
+ private config;
97
+ private events;
98
+ private authProvider;
99
+ private credentials;
100
+ private ua;
101
+ private callManager;
102
+ private connectionStatus;
103
+ private retryCount;
104
+ private maxRetries;
105
+ constructor(config: DoraCellConfig);
106
+ /**
107
+ * Initialize the SDK - authenticate and connect to SIP server
108
+ */
109
+ initialize(): Promise<void>;
110
+ private initializeUserAgent;
111
+ private setupUserAgentHandlers;
112
+ private initializeCallManager;
113
+ /**
114
+ * Make an outbound call
115
+ */
116
+ call(phoneNumber: string, options?: CallOptions): Promise<Call>;
117
+ /**
118
+ * Answer an incoming call
119
+ */
120
+ answerCall(): void;
121
+ /**
122
+ * Hangup the current call
123
+ */
124
+ hangup(): void;
125
+ /**
126
+ * Get current call
127
+ */
128
+ getCurrentCall(): Call | null;
129
+ /**
130
+ * Get connection status
131
+ */
132
+ getStatus(): ConnectionStatus;
133
+ /**
134
+ * Get available extensions
135
+ */
136
+ getExtensions(): string[];
137
+ /**
138
+ * Event listener management
139
+ */
140
+ on<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
141
+ off<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
142
+ once<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
143
+ /**
144
+ * Cleanup and destroy the SDK instance
145
+ */
146
+ destroy(): void;
147
+ private emitConnectionStatus;
148
+ private emitError;
149
+ private getDefaultTurnServers;
150
+ private getDisplayName;
151
+ private generateInstanceId;
152
+ }
153
+
154
+ /**
155
+ * Call Manager - Handles individual call sessions
156
+ * Extracted and refactored from JsSIPProvider.tsx
157
+ */
158
+
159
+ declare class CallSession implements Call {
160
+ id: string;
161
+ status: CallStatus;
162
+ direction: CallDirection;
163
+ remoteNumber: string;
164
+ localExtension: string;
165
+ duration: number;
166
+ startTime?: number;
167
+ endTime?: number;
168
+ private session;
169
+ private _isMuted;
170
+ private remoteStreamValue;
171
+ private durationInterval?;
172
+ private events;
173
+ constructor(session: any, direction: CallDirection, remoteNumber: string, localExtension: string, events: EventEmitter);
174
+ private generateCallId;
175
+ private setupSessionHandlers;
176
+ private handleCallEnd;
177
+ private startDurationTimer;
178
+ private stopDurationTimer;
179
+ mute(): void;
180
+ unmute(): void;
181
+ hangup(): void;
182
+ isMuted(): boolean;
183
+ getRemoteStream(): MediaStream | null;
184
+ getFormattedDuration(): string;
185
+ destroy(): void;
186
+ }
187
+
188
+ /**
189
+ * Phone number formatting utilities
190
+ * Extracted from JsSIPProvider.tsx
191
+ */
192
+ /**
193
+ * Format a phone number to SIP URI format
194
+ * Handles Nigerian numbers, international format, and local extensions
195
+ */
196
+ declare function formatPhoneToSIP(phone: string, sipDomain: string): string;
197
+ /**
198
+ * Normalize a phone number to E.164 format
199
+ */
200
+ declare function normalizePhoneNumber(phone: string, countryCode?: string): string;
201
+ /**
202
+ * Extract extension/number from SIP URI
203
+ */
204
+ declare function extractNumberFromSipUri(sipUri: string): string;
205
+ /**
206
+ * Validate phone number format
207
+ */
208
+ declare function isValidPhoneNumber(phone: string, minLength?: number): boolean;
209
+
210
+ export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };
@@ -0,0 +1,210 @@
1
+ import EventEmitter from 'eventemitter3';
2
+
3
+ /**
4
+ * Core TypeScript type definitions for Dora Cell SDK
5
+ */
6
+ interface ApiTokenAuth {
7
+ type: 'api-token';
8
+ apiToken: string;
9
+ apiBaseUrl?: string;
10
+ }
11
+ interface DirectCredentialsAuth {
12
+ type: 'direct';
13
+ sipUri: string;
14
+ password: string;
15
+ wsUrl: string;
16
+ }
17
+ type AuthConfig = ApiTokenAuth | DirectCredentialsAuth;
18
+ interface DoraCellConfig {
19
+ auth: AuthConfig;
20
+ sipServer?: string;
21
+ turnServers?: RTCIceServer[];
22
+ debug?: boolean;
23
+ autoSelectExtension?: boolean;
24
+ }
25
+ interface SipCredentials {
26
+ wsUrl: string;
27
+ sipUri: string;
28
+ password: string;
29
+ sipDomain?: string;
30
+ extensions?: Extension[];
31
+ expiresIn?: number;
32
+ }
33
+ interface Extension {
34
+ extension: string;
35
+ displayName?: string;
36
+ isPrimary?: boolean;
37
+ }
38
+ type CallStatus = 'idle' | 'connecting' | 'ringing' | 'ongoing' | 'ended';
39
+ type CallDirection = 'inbound' | 'outbound';
40
+ interface CallOptions {
41
+ extension?: string;
42
+ mediaConstraints?: MediaStreamConstraints;
43
+ }
44
+ interface Call {
45
+ id: string;
46
+ status: CallStatus;
47
+ direction: CallDirection;
48
+ remoteNumber: string;
49
+ localExtension: string;
50
+ duration: number;
51
+ startTime?: number;
52
+ endTime?: number;
53
+ mute(): void;
54
+ unmute(): void;
55
+ hangup(): void;
56
+ isMuted(): boolean;
57
+ }
58
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'registered' | 'registrationFailed';
59
+ interface ConnectionState {
60
+ status: ConnectionStatus;
61
+ extension?: string;
62
+ error?: string;
63
+ }
64
+ type DoraCellEventMap = {
65
+ 'connection:status': (state: ConnectionState) => void;
66
+ 'call:incoming': (call: Call) => void;
67
+ 'call:outgoing': (call: Call) => void;
68
+ 'call:ringing': (call: Call) => void;
69
+ 'call:connected': (call: Call) => void;
70
+ 'call:ended': (call: Call, reason?: string) => void;
71
+ 'call:failed': (call: Call, error: string) => void;
72
+ 'error': (error: Error) => void;
73
+ };
74
+ type DoraCellEvent = keyof DoraCellEventMap;
75
+ declare class DoraCellError extends Error {
76
+ code?: string | undefined;
77
+ details?: any | undefined;
78
+ constructor(message: string, code?: string | undefined, details?: any | undefined);
79
+ }
80
+ declare class AuthenticationError extends DoraCellError {
81
+ constructor(message: string, details?: any);
82
+ }
83
+ declare class CallError extends DoraCellError {
84
+ constructor(message: string, details?: any);
85
+ }
86
+ declare class ConnectionError extends DoraCellError {
87
+ constructor(message: string, details?: any);
88
+ }
89
+
90
+ /**
91
+ * Dora Cell SDK - Main entry point
92
+ * Framework-agnostic VoIP calling SDK using JsSIP
93
+ */
94
+
95
+ declare class DoraCell {
96
+ private config;
97
+ private events;
98
+ private authProvider;
99
+ private credentials;
100
+ private ua;
101
+ private callManager;
102
+ private connectionStatus;
103
+ private retryCount;
104
+ private maxRetries;
105
+ constructor(config: DoraCellConfig);
106
+ /**
107
+ * Initialize the SDK - authenticate and connect to SIP server
108
+ */
109
+ initialize(): Promise<void>;
110
+ private initializeUserAgent;
111
+ private setupUserAgentHandlers;
112
+ private initializeCallManager;
113
+ /**
114
+ * Make an outbound call
115
+ */
116
+ call(phoneNumber: string, options?: CallOptions): Promise<Call>;
117
+ /**
118
+ * Answer an incoming call
119
+ */
120
+ answerCall(): void;
121
+ /**
122
+ * Hangup the current call
123
+ */
124
+ hangup(): void;
125
+ /**
126
+ * Get current call
127
+ */
128
+ getCurrentCall(): Call | null;
129
+ /**
130
+ * Get connection status
131
+ */
132
+ getStatus(): ConnectionStatus;
133
+ /**
134
+ * Get available extensions
135
+ */
136
+ getExtensions(): string[];
137
+ /**
138
+ * Event listener management
139
+ */
140
+ on<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
141
+ off<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
142
+ once<K extends DoraCellEvent>(event: K, handler: DoraCellEventMap[K]): void;
143
+ /**
144
+ * Cleanup and destroy the SDK instance
145
+ */
146
+ destroy(): void;
147
+ private emitConnectionStatus;
148
+ private emitError;
149
+ private getDefaultTurnServers;
150
+ private getDisplayName;
151
+ private generateInstanceId;
152
+ }
153
+
154
+ /**
155
+ * Call Manager - Handles individual call sessions
156
+ * Extracted and refactored from JsSIPProvider.tsx
157
+ */
158
+
159
+ declare class CallSession implements Call {
160
+ id: string;
161
+ status: CallStatus;
162
+ direction: CallDirection;
163
+ remoteNumber: string;
164
+ localExtension: string;
165
+ duration: number;
166
+ startTime?: number;
167
+ endTime?: number;
168
+ private session;
169
+ private _isMuted;
170
+ private remoteStreamValue;
171
+ private durationInterval?;
172
+ private events;
173
+ constructor(session: any, direction: CallDirection, remoteNumber: string, localExtension: string, events: EventEmitter);
174
+ private generateCallId;
175
+ private setupSessionHandlers;
176
+ private handleCallEnd;
177
+ private startDurationTimer;
178
+ private stopDurationTimer;
179
+ mute(): void;
180
+ unmute(): void;
181
+ hangup(): void;
182
+ isMuted(): boolean;
183
+ getRemoteStream(): MediaStream | null;
184
+ getFormattedDuration(): string;
185
+ destroy(): void;
186
+ }
187
+
188
+ /**
189
+ * Phone number formatting utilities
190
+ * Extracted from JsSIPProvider.tsx
191
+ */
192
+ /**
193
+ * Format a phone number to SIP URI format
194
+ * Handles Nigerian numbers, international format, and local extensions
195
+ */
196
+ declare function formatPhoneToSIP(phone: string, sipDomain: string): string;
197
+ /**
198
+ * Normalize a phone number to E.164 format
199
+ */
200
+ declare function normalizePhoneNumber(phone: string, countryCode?: string): string;
201
+ /**
202
+ * Extract extension/number from SIP URI
203
+ */
204
+ declare function extractNumberFromSipUri(sipUri: string): string;
205
+ /**
206
+ * Validate phone number format
207
+ */
208
+ declare function isValidPhoneNumber(phone: string, minLength?: number): boolean;
209
+
210
+ export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };