UNPKG

11.1 kBJavaScriptView Raw
1// This is a port of Google Android `libphonenumber`'s
2// `phonenumberutil.js` of December 31th, 2018.
3//
4// https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
5import { VALID_DIGITS, PLUS_CHARS, MIN_LENGTH_FOR_NSN, MAX_LENGTH_FOR_NSN } from './constants';
6import ParseError from './ParseError';
7import Metadata from './metadata';
8import isViablePhoneNumber from './helpers/isViablePhoneNumber';
9import extractExtension from './helpers/extension/extractExtension';
10import parseIncompletePhoneNumber from './parseIncompletePhoneNumber';
11import getCountryCallingCode from './getCountryCallingCode';
12import { isPossibleNumber } from './isPossibleNumber_';
13import { parseRFC3966 } from './helpers/RFC3966';
14import PhoneNumber from './PhoneNumber';
15import matchesEntirely from './helpers/matchesEntirely';
16import extractCountryCallingCode from './helpers/extractCountryCallingCode';
17import extractCountryCallingCodeFromInternationalNumberWithoutPlusSign from './helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign';
18import extractNationalNumber from './helpers/extractNationalNumber';
19import stripIddPrefix from './helpers/stripIddPrefix';
20import getCountryByCallingCode from './helpers/getCountryByCallingCode'; // We don't allow input strings for parsing to be longer than 250 chars.
21// This prevents malicious input from consuming CPU.
22
23var MAX_INPUT_STRING_LENGTH = 250; // This consists of the plus symbol, digits, and arabic-indic digits.
24
25var PHONE_NUMBER_START_PATTERN = new RegExp('[' + PLUS_CHARS + VALID_DIGITS + ']'); // Regular expression of trailing characters that we want to remove.
26// A trailing `#` is sometimes used when writing phone numbers with extensions in US.
27// Example: "+1 (645) 123 1234-910#" number has extension "910".
28
29var AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + VALID_DIGITS + '#' + ']+$');
30var USE_NON_GEOGRAPHIC_COUNTRY_CODE = false; // `options`:
31// {
32// country:
33// {
34// restrict - (a two-letter country code)
35// the phone number must be in this country
36//
37// default - (a two-letter country code)
38// default country to use for phone number parsing and validation
39// (if no country code could be derived from the phone number)
40// }
41// }
42//
43// Returns `{ country, number }`
44//
45// Example use cases:
46//
47// ```js
48// parse('8 (800) 555-35-35', 'RU')
49// parse('8 (800) 555-35-35', 'RU', metadata)
50// parse('8 (800) 555-35-35', { country: { default: 'RU' } })
51// parse('8 (800) 555-35-35', { country: { default: 'RU' } }, metadata)
52// parse('+7 800 555 35 35')
53// parse('+7 800 555 35 35', metadata)
54// ```
55//
56
57export default function parse(text, options, metadata) {
58 // If assigning the `{}` default value is moved to the arguments above,
59 // code coverage would decrease for some weird reason.
60 options = options || {};
61 metadata = new Metadata(metadata); // Validate `defaultCountry`.
62
63 if (options.defaultCountry && !metadata.hasCountry(options.defaultCountry)) {
64 if (options.v2) {
65 throw new ParseError('INVALID_COUNTRY');
66 }
67
68 throw new Error("Unknown country: ".concat(options.defaultCountry));
69 } // Parse the phone number.
70
71
72 var _parseInput = parseInput(text, options.v2),
73 formattedPhoneNumber = _parseInput.number,
74 ext = _parseInput.ext; // If the phone number is not viable then return nothing.
75
76
77 if (!formattedPhoneNumber) {
78 if (options.v2) {
79 throw new ParseError('NOT_A_NUMBER');
80 }
81
82 return {};
83 }
84
85 var _parsePhoneNumber = parsePhoneNumber(formattedPhoneNumber, options.defaultCountry, options.defaultCallingCode, metadata),
86 country = _parsePhoneNumber.country,
87 nationalNumber = _parsePhoneNumber.nationalNumber,
88 countryCallingCode = _parsePhoneNumber.countryCallingCode,
89 carrierCode = _parsePhoneNumber.carrierCode;
90
91 if (!metadata.hasSelectedNumberingPlan()) {
92 if (options.v2) {
93 throw new ParseError('INVALID_COUNTRY');
94 }
95
96 return {};
97 } // Validate national (significant) number length.
98
99
100 if (!nationalNumber || nationalNumber.length < MIN_LENGTH_FOR_NSN) {
101 // Won't throw here because the regexp already demands length > 1.
102
103 /* istanbul ignore if */
104 if (options.v2) {
105 throw new ParseError('TOO_SHORT');
106 } // Google's demo just throws an error in this case.
107
108
109 return {};
110 } // Validate national (significant) number length.
111 //
112 // A sidenote:
113 //
114 // They say that sometimes national (significant) numbers
115 // can be longer than `MAX_LENGTH_FOR_NSN` (e.g. in Germany).
116 // https://github.com/googlei18n/libphonenumber/blob/7e1748645552da39c4e1ba731e47969d97bdb539/resources/phonenumber.proto#L36
117 // Such numbers will just be discarded.
118 //
119
120
121 if (nationalNumber.length > MAX_LENGTH_FOR_NSN) {
122 if (options.v2) {
123 throw new ParseError('TOO_LONG');
124 } // Google's demo just throws an error in this case.
125
126
127 return {};
128 }
129
130 if (options.v2) {
131 var phoneNumber = new PhoneNumber(countryCallingCode, nationalNumber, metadata.metadata);
132
133 if (country) {
134 phoneNumber.country = country;
135 }
136
137 if (carrierCode) {
138 phoneNumber.carrierCode = carrierCode;
139 }
140
141 if (ext) {
142 phoneNumber.ext = ext;
143 }
144
145 return phoneNumber;
146 } // Check if national phone number pattern matches the number.
147 // National number pattern is different for each country,
148 // even for those ones which are part of the "NANPA" group.
149
150
151 var valid = (options.extended ? metadata.hasSelectedNumberingPlan() : country) ? matchesEntirely(nationalNumber, metadata.nationalNumberPattern()) : false;
152
153 if (!options.extended) {
154 return valid ? result(country, nationalNumber, ext) : {};
155 } // isInternational: countryCallingCode !== undefined
156
157
158 return {
159 country: country,
160 countryCallingCode: countryCallingCode,
161 carrierCode: carrierCode,
162 valid: valid,
163 possible: valid ? true : options.extended === true && metadata.possibleLengths() && isPossibleNumber(nationalNumber, metadata) ? true : false,
164 phone: nationalNumber,
165 ext: ext
166 };
167}
168/**
169 * Extracts a formatted phone number from text.
170 * Doesn't guarantee that the extracted phone number
171 * is a valid phone number (for example, doesn't validate its length).
172 * @param {string} text
173 * @param {boolean} throwOnError — By default, it won't throw if the text is too long.
174 * @return {string}
175 * @example
176 * // Returns "(213) 373-4253".
177 * extractFormattedPhoneNumber("Call (213) 373-4253 for assistance.")
178 */
179
180export function extractFormattedPhoneNumber(text, throwOnError) {
181 if (!text) {
182 return;
183 }
184
185 if (text.length > MAX_INPUT_STRING_LENGTH) {
186 if (throwOnError) {
187 throw new ParseError('TOO_LONG');
188 }
189
190 return;
191 } // Attempt to extract a possible number from the string passed in
192
193
194 var startsAt = text.search(PHONE_NUMBER_START_PATTERN);
195
196 if (startsAt < 0) {
197 return;
198 }
199
200 return text // Trim everything to the left of the phone number
201 .slice(startsAt) // Remove trailing non-numerical characters
202 .replace(AFTER_PHONE_NUMBER_END_PATTERN, '');
203}
204/**
205 * @param {string} text - Input.
206 * @return {object} `{ ?number, ?ext }`.
207 */
208
209function parseInput(text, v2) {
210 // Parse RFC 3966 phone number URI.
211 if (text && text.indexOf('tel:') === 0) {
212 return parseRFC3966(text);
213 }
214
215 var number = extractFormattedPhoneNumber(text, v2); // If the phone number is not viable, then abort.
216
217 if (!number || !isViablePhoneNumber(number)) {
218 return {};
219 } // Attempt to parse extension first, since it doesn't require region-specific
220 // data and we want to have the non-normalised number here.
221
222
223 var withExtensionStripped = extractExtension(number);
224
225 if (withExtensionStripped.ext) {
226 return withExtensionStripped;
227 }
228
229 return {
230 number: number
231 };
232}
233/**
234 * Creates `parse()` result object.
235 */
236
237
238function result(country, nationalNumber, ext) {
239 var result = {
240 country: country,
241 phone: nationalNumber
242 };
243
244 if (ext) {
245 result.ext = ext;
246 }
247
248 return result;
249}
250/**
251 * Parses a viable phone number.
252 * @param {string} formattedPhoneNumber — Example: "(213) 373-4253".
253 * @param {string} [defaultCountry]
254 * @param {string} [defaultCallingCode]
255 * @param {Metadata} metadata
256 * @return {object} Returns `{ country: string?, countryCallingCode: string?, nationalNumber: string? }`.
257 */
258
259
260function parsePhoneNumber(formattedPhoneNumber, defaultCountry, defaultCallingCode, metadata) {
261 // Extract calling code from phone number.
262 var _extractCountryCallin = extractCountryCallingCode(parseIncompletePhoneNumber(formattedPhoneNumber), defaultCountry, defaultCallingCode, metadata.metadata),
263 countryCallingCode = _extractCountryCallin.countryCallingCode,
264 number = _extractCountryCallin.number; // Choose a country by `countryCallingCode`.
265
266
267 var country;
268
269 if (countryCallingCode) {
270 metadata.selectNumberingPlan(countryCallingCode);
271 } // If `formattedPhoneNumber` is in "national" format
272 // then `number` is defined and `countryCallingCode` isn't.
273 else if (number && (defaultCountry || defaultCallingCode)) {
274 metadata.selectNumberingPlan(defaultCountry, defaultCallingCode);
275
276 if (defaultCountry) {
277 country = defaultCountry;
278 } else {
279 /* istanbul ignore if */
280 if (USE_NON_GEOGRAPHIC_COUNTRY_CODE) {
281 if (metadata.isNonGeographicCallingCode(defaultCallingCode)) {
282 country = '001';
283 }
284 }
285 }
286
287 countryCallingCode = defaultCallingCode || getCountryCallingCode(defaultCountry, metadata.metadata);
288 } else return {};
289
290 if (!number) {
291 return {
292 countryCallingCode: countryCallingCode
293 };
294 }
295
296 var _extractNationalNumbe = extractNationalNumber(parseIncompletePhoneNumber(number), metadata),
297 nationalNumber = _extractNationalNumbe.nationalNumber,
298 carrierCode = _extractNationalNumbe.carrierCode; // Sometimes there are several countries
299 // corresponding to the same country phone code
300 // (e.g. NANPA countries all having `1` country phone code).
301 // Therefore, to reliably determine the exact country,
302 // national (significant) number should have been parsed first.
303 //
304 // When `metadata.json` is generated, all "ambiguous" country phone codes
305 // get their countries populated with the full set of
306 // "phone number type" regular expressions.
307 //
308
309
310 var exactCountry = getCountryByCallingCode(countryCallingCode, nationalNumber, metadata);
311
312 if (exactCountry) {
313 country = exactCountry;
314 /* istanbul ignore if */
315
316 if (exactCountry === '001') {// Can't happen with `USE_NON_GEOGRAPHIC_COUNTRY_CODE` being `false`.
317 // If `USE_NON_GEOGRAPHIC_COUNTRY_CODE` is set to `true` for some reason,
318 // then remove the "istanbul ignore if".
319 } else {
320 metadata.country(country);
321 }
322 }
323
324 return {
325 country: country,
326 countryCallingCode: countryCallingCode,
327 nationalNumber: nationalNumber,
328 carrierCode: carrierCode
329 };
330}
331//# sourceMappingURL=parse_.js.map
\No newline at end of file