@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.
@@ -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
+ };
@@ -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
+ };