@devdataphone/sdk 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.
- package/index.d.ts +249 -0
- package/index.js +175 -0
- package/index.mjs +31 -0
- package/package.json +57 -0
- package/src/countries.js +240 -0
- package/src/formatter.js +299 -0
- package/src/generator.js +334 -0
- package/src/validator.js +364 -0
package/src/countries.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Country/Region data for phone number generation
|
|
3
|
+
* @module countries
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported countries with their phone number metadata
|
|
10
|
+
* @type {Array<Country>}
|
|
11
|
+
*/
|
|
12
|
+
const COUNTRIES = [
|
|
13
|
+
{
|
|
14
|
+
code: 'US',
|
|
15
|
+
name: 'United States',
|
|
16
|
+
dialCode: '+1',
|
|
17
|
+
flag: '🇺🇸',
|
|
18
|
+
example: '(212) 555-0123',
|
|
19
|
+
// E.164 compliant regex
|
|
20
|
+
regex: /^(\+1)?[-. ]?\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/,
|
|
21
|
+
// Reserved fictional ranges (FCC/NANP)
|
|
22
|
+
reservedRanges: [
|
|
23
|
+
{ areaCode: '555', exchange: '0100', to: '0199' } // 555-0100 to 555-0199
|
|
24
|
+
],
|
|
25
|
+
numberLength: 10, // National significant number length
|
|
26
|
+
mobilePrefix: null // US doesn't distinguish mobile by prefix
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'UK',
|
|
30
|
+
name: 'United Kingdom',
|
|
31
|
+
dialCode: '+44',
|
|
32
|
+
flag: '🇬🇧',
|
|
33
|
+
example: '07700 900123',
|
|
34
|
+
regex: /^(\+44|0)7\d{9}$/,
|
|
35
|
+
// Ofcom reserved drama/test ranges
|
|
36
|
+
reservedRanges: [
|
|
37
|
+
{ prefix: '07700', start: '900000', end: '900999' }
|
|
38
|
+
],
|
|
39
|
+
numberLength: 10,
|
|
40
|
+
mobilePrefix: '7'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: 'CA',
|
|
44
|
+
name: 'Canada',
|
|
45
|
+
dialCode: '+1',
|
|
46
|
+
flag: '🇨🇦',
|
|
47
|
+
example: '(416) 555-0199',
|
|
48
|
+
regex: /^(\+1)?[-. ]?\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/,
|
|
49
|
+
// Same NANP reserved ranges as US
|
|
50
|
+
reservedRanges: [
|
|
51
|
+
{ areaCode: '555', exchange: '0100', to: '0199' }
|
|
52
|
+
],
|
|
53
|
+
numberLength: 10,
|
|
54
|
+
mobilePrefix: null
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
code: 'AU',
|
|
58
|
+
name: 'Australia',
|
|
59
|
+
dialCode: '+61',
|
|
60
|
+
flag: '🇦🇺',
|
|
61
|
+
example: '0491 570 156',
|
|
62
|
+
regex: /^(\+61|0)4\d{8}$/,
|
|
63
|
+
// ACMA reserved ranges
|
|
64
|
+
reservedRanges: [
|
|
65
|
+
{ prefix: '0491', start: '570156', end: '570159' }
|
|
66
|
+
],
|
|
67
|
+
numberLength: 9, // Without leading 0
|
|
68
|
+
mobilePrefix: '4'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
code: 'CN',
|
|
72
|
+
name: 'China',
|
|
73
|
+
dialCode: '+86',
|
|
74
|
+
flag: '🇨🇳',
|
|
75
|
+
example: '139 1234 5678',
|
|
76
|
+
regex: /^(\+86)?1[3-9]\d{9}$/,
|
|
77
|
+
// No official reserved ranges, generate from common prefixes
|
|
78
|
+
reservedRanges: [],
|
|
79
|
+
numberLength: 11,
|
|
80
|
+
mobilePrefix: '1',
|
|
81
|
+
// Common mobile carrier prefixes
|
|
82
|
+
mobilePrefixes: [
|
|
83
|
+
'130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
|
|
84
|
+
'150', '151', '152', '153', '155', '156', '157', '158', '159',
|
|
85
|
+
'166', '170', '171', '172', '173', '175', '176', '177', '178',
|
|
86
|
+
'180', '181', '182', '183', '184', '185', '186', '187', '188', '189',
|
|
87
|
+
'191', '198', '199'
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
code: 'IN',
|
|
92
|
+
name: 'India',
|
|
93
|
+
dialCode: '+91',
|
|
94
|
+
flag: '🇮🇳',
|
|
95
|
+
example: '98123 45678',
|
|
96
|
+
regex: /^(\+91|0)?[6-9]\d{9}$/,
|
|
97
|
+
reservedRanges: [],
|
|
98
|
+
numberLength: 10,
|
|
99
|
+
mobilePrefix: null,
|
|
100
|
+
// Valid starting digits for mobile
|
|
101
|
+
mobileStartDigits: ['6', '7', '8', '9']
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
code: 'DE',
|
|
105
|
+
name: 'Germany',
|
|
106
|
+
dialCode: '+49',
|
|
107
|
+
flag: '🇩🇪',
|
|
108
|
+
example: '0170 1234567',
|
|
109
|
+
regex: /^(\+49|0)[1-9]\d{6,13}$/,
|
|
110
|
+
reservedRanges: [],
|
|
111
|
+
numberLength: { min: 7, max: 14 },
|
|
112
|
+
mobilePrefixes: ['151', '152', '157', '159', '160', '162', '163', '170', '171', '172', '173', '174', '175', '176', '177', '178', '179']
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
code: 'FR',
|
|
116
|
+
name: 'France',
|
|
117
|
+
dialCode: '+33',
|
|
118
|
+
flag: '🇫🇷',
|
|
119
|
+
example: '06 12 34 56 78',
|
|
120
|
+
regex: /^(\+33|0)[1-9]\d{8}$/,
|
|
121
|
+
reservedRanges: [],
|
|
122
|
+
numberLength: 9,
|
|
123
|
+
mobilePrefix: '6'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
code: 'JP',
|
|
127
|
+
name: 'Japan',
|
|
128
|
+
dialCode: '+81',
|
|
129
|
+
flag: '🇯🇵',
|
|
130
|
+
example: '090-1234-5678',
|
|
131
|
+
regex: /^(\+81|0)[7-9]0\d{8}$/,
|
|
132
|
+
reservedRanges: [],
|
|
133
|
+
numberLength: 10,
|
|
134
|
+
mobilePrefixes: ['70', '80', '90']
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
code: 'BR',
|
|
138
|
+
name: 'Brazil',
|
|
139
|
+
dialCode: '+55',
|
|
140
|
+
flag: '🇧🇷',
|
|
141
|
+
example: '(11) 91234-5678',
|
|
142
|
+
regex: /^(\+55)?[1-9]{2}9[0-9]{8}$/,
|
|
143
|
+
reservedRanges: [],
|
|
144
|
+
numberLength: 11,
|
|
145
|
+
mobilePrefix: '9'
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Area codes for US/Canada number generation
|
|
151
|
+
* @type {Array<{code: number, state: string}>}
|
|
152
|
+
*/
|
|
153
|
+
const US_AREA_CODES = [
|
|
154
|
+
// New York
|
|
155
|
+
{ code: 212, state: 'NY', city: 'New York City' },
|
|
156
|
+
{ code: 718, state: 'NY', city: 'New York City' },
|
|
157
|
+
{ code: 917, state: 'NY', city: 'New York City' },
|
|
158
|
+
{ code: 646, state: 'NY', city: 'New York City' },
|
|
159
|
+
{ code: 347, state: 'NY', city: 'New York City' },
|
|
160
|
+
// California
|
|
161
|
+
{ code: 310, state: 'CA', city: 'Los Angeles' },
|
|
162
|
+
{ code: 213, state: 'CA', city: 'Los Angeles' },
|
|
163
|
+
{ code: 323, state: 'CA', city: 'Los Angeles' },
|
|
164
|
+
{ code: 415, state: 'CA', city: 'San Francisco' },
|
|
165
|
+
{ code: 650, state: 'CA', city: 'San Francisco Bay' },
|
|
166
|
+
{ code: 408, state: 'CA', city: 'San Jose' },
|
|
167
|
+
{ code: 619, state: 'CA', city: 'San Diego' },
|
|
168
|
+
// Texas
|
|
169
|
+
{ code: 512, state: 'TX', city: 'Austin' },
|
|
170
|
+
{ code: 713, state: 'TX', city: 'Houston' },
|
|
171
|
+
{ code: 214, state: 'TX', city: 'Dallas' },
|
|
172
|
+
{ code: 469, state: 'TX', city: 'Dallas' },
|
|
173
|
+
// Others
|
|
174
|
+
{ code: 202, state: 'DC', city: 'Washington' },
|
|
175
|
+
{ code: 312, state: 'IL', city: 'Chicago' },
|
|
176
|
+
{ code: 773, state: 'IL', city: 'Chicago' },
|
|
177
|
+
{ code: 206, state: 'WA', city: 'Seattle' },
|
|
178
|
+
{ code: 617, state: 'MA', city: 'Boston' },
|
|
179
|
+
{ code: 305, state: 'FL', city: 'Miami' },
|
|
180
|
+
{ code: 407, state: 'FL', city: 'Orlando' },
|
|
181
|
+
{ code: 404, state: 'GA', city: 'Atlanta' },
|
|
182
|
+
{ code: 303, state: 'CO', city: 'Denver' },
|
|
183
|
+
{ code: 602, state: 'AZ', city: 'Phoenix' },
|
|
184
|
+
{ code: 702, state: 'NV', city: 'Las Vegas' },
|
|
185
|
+
{ code: 215, state: 'PA', city: 'Philadelphia' }
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Canada-specific area codes
|
|
190
|
+
* @type {Array<{code: number, province: string}>}
|
|
191
|
+
*/
|
|
192
|
+
const CA_AREA_CODES = [
|
|
193
|
+
{ code: 416, province: 'ON', city: 'Toronto' },
|
|
194
|
+
{ code: 647, province: 'ON', city: 'Toronto' },
|
|
195
|
+
{ code: 905, province: 'ON', city: 'Greater Toronto' },
|
|
196
|
+
{ code: 604, province: 'BC', city: 'Vancouver' },
|
|
197
|
+
{ code: 778, province: 'BC', city: 'British Columbia' },
|
|
198
|
+
{ code: 514, province: 'QC', city: 'Montreal' },
|
|
199
|
+
{ code: 438, province: 'QC', city: 'Montreal' },
|
|
200
|
+
{ code: 403, province: 'AB', city: 'Calgary' },
|
|
201
|
+
{ code: 780, province: 'AB', city: 'Edmonton' },
|
|
202
|
+
{ code: 613, province: 'ON', city: 'Ottawa' }
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get country by code
|
|
207
|
+
* @param {string} code - ISO 3166-1 alpha-2 country code
|
|
208
|
+
* @returns {Country|undefined}
|
|
209
|
+
*/
|
|
210
|
+
function getCountry(code) {
|
|
211
|
+
if (!code || typeof code !== 'string') return undefined;
|
|
212
|
+
return COUNTRIES.find(c => c.code === code.toUpperCase());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all supported country codes
|
|
217
|
+
* @returns {string[]}
|
|
218
|
+
*/
|
|
219
|
+
function getSupportedCountries() {
|
|
220
|
+
return COUNTRIES.map(c => c.code);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if a country code is supported
|
|
225
|
+
* @param {string} code
|
|
226
|
+
* @returns {boolean}
|
|
227
|
+
*/
|
|
228
|
+
function isSupported(code) {
|
|
229
|
+
if (!code || typeof code !== 'string') return false;
|
|
230
|
+
return COUNTRIES.some(c => c.code === code.toUpperCase());
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
COUNTRIES,
|
|
235
|
+
US_AREA_CODES,
|
|
236
|
+
CA_AREA_CODES,
|
|
237
|
+
getCountry,
|
|
238
|
+
getSupportedCountries,
|
|
239
|
+
isSupported
|
|
240
|
+
};
|
package/src/formatter.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone number formatting module
|
|
3
|
+
* Formats phone numbers into various standard formats
|
|
4
|
+
* @module formatter
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { getCountry } = require('./countries');
|
|
10
|
+
const { extractCountryCode, sanitize } = require('./validator');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Format styles
|
|
14
|
+
* @enum {string}
|
|
15
|
+
*/
|
|
16
|
+
const FormatStyle = {
|
|
17
|
+
E164: 'e164', // +14155550100
|
|
18
|
+
INTERNATIONAL: 'international', // +1 415 555 0100
|
|
19
|
+
NATIONAL: 'national', // (415) 555-0100
|
|
20
|
+
RFC3966: 'rfc3966', // tel:+1-415-555-0100
|
|
21
|
+
DIGITS: 'digits' // 14155550100
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format US/Canada number in national format
|
|
26
|
+
* @param {string} nationalNumber
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function formatUSNational(nationalNumber) {
|
|
30
|
+
if (nationalNumber.length !== 10) return nationalNumber;
|
|
31
|
+
const areaCode = nationalNumber.slice(0, 3);
|
|
32
|
+
const exchange = nationalNumber.slice(3, 6);
|
|
33
|
+
const subscriber = nationalNumber.slice(6);
|
|
34
|
+
return `(${areaCode}) ${exchange}-${subscriber}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format UK number in national format
|
|
39
|
+
* @param {string} nationalNumber
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function formatUKNational(nationalNumber) {
|
|
43
|
+
if (nationalNumber.length < 10) return nationalNumber;
|
|
44
|
+
// Mobile: 07XXX XXXXXX
|
|
45
|
+
if (nationalNumber.startsWith('7')) {
|
|
46
|
+
return `0${nationalNumber.slice(0, 4)} ${nationalNumber.slice(4)}`;
|
|
47
|
+
}
|
|
48
|
+
return `0${nationalNumber}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format China number in national format
|
|
53
|
+
* @param {string} nationalNumber
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function formatCNNational(nationalNumber) {
|
|
57
|
+
if (nationalNumber.length !== 11) return nationalNumber;
|
|
58
|
+
const prefix = nationalNumber.slice(0, 3);
|
|
59
|
+
const middle = nationalNumber.slice(3, 7);
|
|
60
|
+
const end = nationalNumber.slice(7);
|
|
61
|
+
return `${prefix} ${middle} ${end}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format Australia number in national format
|
|
66
|
+
* @param {string} nationalNumber
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
function formatAUNational(nationalNumber) {
|
|
70
|
+
if (nationalNumber.length !== 9) return nationalNumber;
|
|
71
|
+
// Mobile: 04XX XXX XXX
|
|
72
|
+
if (nationalNumber.startsWith('4')) {
|
|
73
|
+
return `0${nationalNumber.slice(0, 3)} ${nationalNumber.slice(3, 6)} ${nationalNumber.slice(6)}`;
|
|
74
|
+
}
|
|
75
|
+
return `0${nationalNumber}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format India number in national format
|
|
80
|
+
* @param {string} nationalNumber
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function formatINNational(nationalNumber) {
|
|
84
|
+
if (nationalNumber.length !== 10) return nationalNumber;
|
|
85
|
+
const first5 = nationalNumber.slice(0, 5);
|
|
86
|
+
const last5 = nationalNumber.slice(5);
|
|
87
|
+
return `${first5} ${last5}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Format Germany number in national format
|
|
92
|
+
* @param {string} nationalNumber
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function formatDENational(nationalNumber) {
|
|
96
|
+
if (nationalNumber.length < 7) return nationalNumber;
|
|
97
|
+
// Mobile prefix (3 digits) + rest
|
|
98
|
+
const prefix = nationalNumber.slice(0, 3);
|
|
99
|
+
const rest = nationalNumber.slice(3);
|
|
100
|
+
return `0${prefix} ${rest}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Format France number in national format
|
|
105
|
+
* @param {string} nationalNumber
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
function formatFRNational(nationalNumber) {
|
|
109
|
+
if (nationalNumber.length !== 9) return nationalNumber;
|
|
110
|
+
// Format: 0X XX XX XX XX
|
|
111
|
+
const parts = [];
|
|
112
|
+
parts.push('0' + nationalNumber[0]);
|
|
113
|
+
for (let i = 1; i < 9; i += 2) {
|
|
114
|
+
parts.push(nationalNumber.slice(i, i + 2));
|
|
115
|
+
}
|
|
116
|
+
return parts.join(' ');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format Japan number in national format
|
|
121
|
+
* @param {string} nationalNumber
|
|
122
|
+
* @returns {string}
|
|
123
|
+
*/
|
|
124
|
+
function formatJPNational(nationalNumber) {
|
|
125
|
+
if (nationalNumber.length !== 10) return nationalNumber;
|
|
126
|
+
// Mobile: 0X0-XXXX-XXXX
|
|
127
|
+
const prefix = nationalNumber.slice(0, 2);
|
|
128
|
+
const middle = nationalNumber.slice(2, 6);
|
|
129
|
+
const end = nationalNumber.slice(6);
|
|
130
|
+
return `0${prefix}-${middle}-${end}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format Brazil number in national format
|
|
135
|
+
* @param {string} nationalNumber
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function formatBRNational(nationalNumber) {
|
|
139
|
+
if (nationalNumber.length !== 11) return nationalNumber;
|
|
140
|
+
// Format: (XX) XXXXX-XXXX
|
|
141
|
+
const areaCode = nationalNumber.slice(0, 2);
|
|
142
|
+
const first = nationalNumber.slice(2, 7);
|
|
143
|
+
const second = nationalNumber.slice(7);
|
|
144
|
+
return `(${areaCode}) ${first}-${second}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* National format functions by country code
|
|
149
|
+
*/
|
|
150
|
+
const nationalFormatters = {
|
|
151
|
+
US: formatUSNational,
|
|
152
|
+
CA: formatUSNational,
|
|
153
|
+
UK: formatUKNational,
|
|
154
|
+
CN: formatCNNational,
|
|
155
|
+
AU: formatAUNational,
|
|
156
|
+
IN: formatINNational,
|
|
157
|
+
DE: formatDENational,
|
|
158
|
+
FR: formatFRNational,
|
|
159
|
+
JP: formatJPNational,
|
|
160
|
+
BR: formatBRNational
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format international style with spaces
|
|
165
|
+
* @param {string} dialCode
|
|
166
|
+
* @param {string} nationalNumber
|
|
167
|
+
* @param {string} countryCode
|
|
168
|
+
* @returns {string}
|
|
169
|
+
*/
|
|
170
|
+
function formatInternational(dialCode, nationalNumber, countryCode) {
|
|
171
|
+
// Get national format and replace leading 0 removal
|
|
172
|
+
const formatter = nationalFormatters[countryCode];
|
|
173
|
+
if (formatter) {
|
|
174
|
+
let national = formatter(nationalNumber);
|
|
175
|
+
// Remove leading 0 if present
|
|
176
|
+
if (national.startsWith('0')) {
|
|
177
|
+
national = national.slice(1);
|
|
178
|
+
}
|
|
179
|
+
return `${dialCode} ${national}`;
|
|
180
|
+
}
|
|
181
|
+
return `${dialCode} ${nationalNumber}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Format phone number
|
|
186
|
+
* @param {string} input - Phone number to format
|
|
187
|
+
* @param {string} [style='e164'] - Output format style
|
|
188
|
+
* @param {Object} [options] - Additional options
|
|
189
|
+
* @param {string} [options.defaultCountry] - Default country for local numbers
|
|
190
|
+
* @returns {string|null} Formatted number or null if invalid
|
|
191
|
+
*/
|
|
192
|
+
function format(input, style = 'e164', options = {}) {
|
|
193
|
+
if (!input || typeof input !== 'string') return null;
|
|
194
|
+
|
|
195
|
+
const { defaultCountry } = options;
|
|
196
|
+
const trimmed = input.trim();
|
|
197
|
+
const normalizedStyle = String(style).toLowerCase();
|
|
198
|
+
|
|
199
|
+
// Sanitize input
|
|
200
|
+
const sanitized = sanitize(trimmed);
|
|
201
|
+
|
|
202
|
+
let countryCode;
|
|
203
|
+
let dialCode;
|
|
204
|
+
let nationalNumber;
|
|
205
|
+
|
|
206
|
+
// Handle E.164 format
|
|
207
|
+
if (sanitized.startsWith('+')) {
|
|
208
|
+
const extracted = extractCountryCode(sanitized);
|
|
209
|
+
if (!extracted) return null;
|
|
210
|
+
|
|
211
|
+
countryCode = extracted.countryCode;
|
|
212
|
+
dialCode = extracted.dialCode;
|
|
213
|
+
nationalNumber = extracted.nationalNumber;
|
|
214
|
+
}
|
|
215
|
+
// Handle local format with defaultCountry
|
|
216
|
+
else if (defaultCountry) {
|
|
217
|
+
const country = getCountry(defaultCountry);
|
|
218
|
+
if (!country) return null;
|
|
219
|
+
|
|
220
|
+
countryCode = country.code;
|
|
221
|
+
dialCode = country.dialCode;
|
|
222
|
+
nationalNumber = sanitized.startsWith('0') ? sanitized.slice(1) : sanitized;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const country = getCountry(countryCode);
|
|
229
|
+
if (!country) return null;
|
|
230
|
+
|
|
231
|
+
// Format based on style
|
|
232
|
+
switch (normalizedStyle) {
|
|
233
|
+
case FormatStyle.E164:
|
|
234
|
+
return `${dialCode}${nationalNumber}`;
|
|
235
|
+
|
|
236
|
+
case FormatStyle.INTERNATIONAL:
|
|
237
|
+
return formatInternational(dialCode, nationalNumber, countryCode);
|
|
238
|
+
|
|
239
|
+
case FormatStyle.NATIONAL: {
|
|
240
|
+
const formatter = nationalFormatters[countryCode];
|
|
241
|
+
if (formatter) {
|
|
242
|
+
return formatter(nationalNumber);
|
|
243
|
+
}
|
|
244
|
+
return nationalNumber;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case FormatStyle.RFC3966: {
|
|
248
|
+
const e164 = `${dialCode}${nationalNumber}`.replace('+', '');
|
|
249
|
+
// Format with hyphens
|
|
250
|
+
const international = formatInternational(dialCode, nationalNumber, countryCode);
|
|
251
|
+
const formatted = international.replace(/\s+/g, '-').replace(/[()]/g, '');
|
|
252
|
+
return `tel:${formatted}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case FormatStyle.DIGITS:
|
|
256
|
+
return `${dialCode.replace('+', '')}${nationalNumber}`;
|
|
257
|
+
|
|
258
|
+
default:
|
|
259
|
+
return `${dialCode}${nationalNumber}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Convert any phone number format to E.164
|
|
265
|
+
* @param {string} input - Phone number
|
|
266
|
+
* @param {string} [defaultCountry] - Default country code for local numbers
|
|
267
|
+
* @returns {string|null}
|
|
268
|
+
*/
|
|
269
|
+
function toE164(input, defaultCountry) {
|
|
270
|
+
return format(input, 'e164', { defaultCountry });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Convert to national format
|
|
275
|
+
* @param {string} input - Phone number
|
|
276
|
+
* @param {string} [defaultCountry] - Default country code
|
|
277
|
+
* @returns {string|null}
|
|
278
|
+
*/
|
|
279
|
+
function toNational(input, defaultCountry) {
|
|
280
|
+
return format(input, 'national', { defaultCountry });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Convert to international format
|
|
285
|
+
* @param {string} input - Phone number
|
|
286
|
+
* @param {string} [defaultCountry] - Default country code
|
|
287
|
+
* @returns {string|null}
|
|
288
|
+
*/
|
|
289
|
+
function toInternational(input, defaultCountry) {
|
|
290
|
+
return format(input, 'international', { defaultCountry });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = {
|
|
294
|
+
format,
|
|
295
|
+
toE164,
|
|
296
|
+
toNational,
|
|
297
|
+
toInternational,
|
|
298
|
+
FormatStyle
|
|
299
|
+
};
|