You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							184 lines
						
					
					
						
							6.7 KiB
						
					
					
				
			
		
		
	
	
							184 lines
						
					
					
						
							6.7 KiB
						
					
					
				| // This is a port of Google Android `libphonenumber`'s
 | |
| // `phonenumberutil.js` of December 31th, 2018.
 | |
| //
 | |
| // https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
 | |
| 
 | |
| import matchesEntirely from './helpers/matchesEntirely.js'
 | |
| import formatNationalNumberUsingFormat from './helpers/formatNationalNumberUsingFormat.js'
 | |
| import Metadata, { getCountryCallingCode } from './metadata.js'
 | |
| import getIddPrefix from './helpers/getIddPrefix.js'
 | |
| import { formatRFC3966 } from './helpers/RFC3966.js'
 | |
| 
 | |
| const DEFAULT_OPTIONS = {
 | |
| 	formatExtension: (formattedNumber, extension, metadata) => `${formattedNumber}${metadata.ext()}${extension}`
 | |
| }
 | |
| 
 | |
| // Formats a phone number
 | |
| //
 | |
| // Example use cases:
 | |
| //
 | |
| // ```js
 | |
| // formatNumber('8005553535', 'RU', 'INTERNATIONAL')
 | |
| // formatNumber('8005553535', 'RU', 'INTERNATIONAL', metadata)
 | |
| // formatNumber({ phone: '8005553535', country: 'RU' }, 'INTERNATIONAL')
 | |
| // formatNumber({ phone: '8005553535', country: 'RU' }, 'INTERNATIONAL', metadata)
 | |
| // formatNumber('+78005553535', 'NATIONAL')
 | |
| // formatNumber('+78005553535', 'NATIONAL', metadata)
 | |
| // ```
 | |
| //
 | |
| export default function formatNumber(input, format, options, metadata) {
 | |
| 	// Apply default options.
 | |
| 	if (options) {
 | |
| 		options = { ...DEFAULT_OPTIONS, ...options }
 | |
| 	} else {
 | |
| 		options = DEFAULT_OPTIONS
 | |
| 	}
 | |
| 
 | |
| 	metadata = new Metadata(metadata)
 | |
| 
 | |
| 	if (input.country && input.country !== '001') {
 | |
| 		// Validate `input.country`.
 | |
| 		if (!metadata.hasCountry(input.country)) {
 | |
| 			throw new Error(`Unknown country: ${input.country}`)
 | |
| 		}
 | |
| 		metadata.country(input.country)
 | |
| 	}
 | |
| 	else if (input.countryCallingCode) {
 | |
| 		metadata.selectNumberingPlan(input.countryCallingCode)
 | |
| 	}
 | |
| 	else return input.phone || ''
 | |
| 
 | |
| 	const countryCallingCode = metadata.countryCallingCode()
 | |
| 
 | |
| 	const nationalNumber = options.v2 ? input.nationalNumber : input.phone
 | |
| 
 | |
| 	// This variable should have been declared inside `case`s
 | |
| 	// but Babel has a bug and it says "duplicate variable declaration".
 | |
| 	let number
 | |
| 
 | |
| 	switch (format) {
 | |
| 		case 'NATIONAL':
 | |
| 			// Legacy argument support.
 | |
| 			// (`{ country: ..., phone: '' }`)
 | |
| 			if (!nationalNumber) {
 | |
| 				return ''
 | |
| 			}
 | |
| 			number = formatNationalNumber(nationalNumber, input.carrierCode, 'NATIONAL', metadata, options)
 | |
| 			return addExtension(number, input.ext, metadata, options.formatExtension)
 | |
| 
 | |
| 		case 'INTERNATIONAL':
 | |
| 			// Legacy argument support.
 | |
| 			// (`{ country: ..., phone: '' }`)
 | |
| 			if (!nationalNumber) {
 | |
| 				return `+${countryCallingCode}`
 | |
| 			}
 | |
| 			number = formatNationalNumber(nationalNumber, null, 'INTERNATIONAL', metadata, options)
 | |
| 			number = `+${countryCallingCode} ${number}`
 | |
| 			return addExtension(number, input.ext, metadata, options.formatExtension)
 | |
| 
 | |
| 		case 'E.164':
 | |
| 			// `E.164` doesn't define "phone number extensions".
 | |
| 			return `+${countryCallingCode}${nationalNumber}`
 | |
| 
 | |
| 		case 'RFC3966':
 | |
| 			return formatRFC3966({
 | |
| 				number: `+${countryCallingCode}${nationalNumber}`,
 | |
| 				ext: input.ext
 | |
| 			})
 | |
| 
 | |
| 		// For reference, here's Google's IDD formatter:
 | |
| 		// https://github.com/google/libphonenumber/blob/32719cf74e68796788d1ca45abc85dcdc63ba5b9/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java#L1546
 | |
| 		// Not saying that this IDD formatter replicates it 1:1, but it seems to work.
 | |
| 		// Who would even need to format phone numbers in IDD format anyway?
 | |
| 		case 'IDD':
 | |
| 			if (!options.fromCountry) {
 | |
| 				return
 | |
| 				// throw new Error('`fromCountry` option not passed for IDD-prefixed formatting.')
 | |
| 			}
 | |
| 			const formattedNumber = formatIDD(
 | |
| 				nationalNumber,
 | |
| 				input.carrierCode,
 | |
| 				countryCallingCode,
 | |
| 				options.fromCountry,
 | |
| 				metadata
 | |
| 			)
 | |
| 			return addExtension(formattedNumber, input.ext, metadata, options.formatExtension)
 | |
| 
 | |
| 		default:
 | |
| 			throw new Error(`Unknown "format" argument passed to "formatNumber()": "${format}"`)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function formatNationalNumber(number, carrierCode, formatAs, metadata, options) {
 | |
| 	const format = chooseFormatForNumber(metadata.formats(), number)
 | |
| 	if (!format) {
 | |
| 		return number
 | |
| 	}
 | |
| 	return formatNationalNumberUsingFormat(
 | |
| 		number,
 | |
| 		format,
 | |
| 		{
 | |
| 			useInternationalFormat: formatAs === 'INTERNATIONAL',
 | |
| 			withNationalPrefix: format.nationalPrefixIsOptionalWhenFormattingInNationalFormat() && (options && options.nationalPrefix === false) ? false : true,
 | |
| 			carrierCode,
 | |
| 			metadata
 | |
| 		}
 | |
| 	)
 | |
| }
 | |
| 
 | |
| function chooseFormatForNumber(availableFormats, nationalNnumber) {
 | |
| 	for (const format of availableFormats) {
 | |
| 		// Validate leading digits.
 | |
| 		// The test case for "else path" could be found by searching for
 | |
| 		// "format.leadingDigitsPatterns().length === 0".
 | |
| 		if (format.leadingDigitsPatterns().length > 0) {
 | |
| 			// The last leading_digits_pattern is used here, as it is the most detailed
 | |
| 			const lastLeadingDigitsPattern = format.leadingDigitsPatterns()[format.leadingDigitsPatterns().length - 1]
 | |
| 			// If leading digits don't match then move on to the next phone number format
 | |
| 			if (nationalNnumber.search(lastLeadingDigitsPattern) !== 0) {
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		// Check that the national number matches the phone number format regular expression
 | |
| 		if (matchesEntirely(nationalNnumber, format.pattern())) {
 | |
| 			return format
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function addExtension(formattedNumber, ext, metadata, formatExtension) {
 | |
| 	return ext ? formatExtension(formattedNumber, ext, metadata) : formattedNumber
 | |
| }
 | |
| 
 | |
| function formatIDD(
 | |
| 	nationalNumber,
 | |
| 	carrierCode,
 | |
| 	countryCallingCode,
 | |
| 	fromCountry,
 | |
| 	metadata
 | |
| ) {
 | |
| 	const fromCountryCallingCode = getCountryCallingCode(fromCountry, metadata.metadata)
 | |
| 	// When calling within the same country calling code.
 | |
| 	if (fromCountryCallingCode === countryCallingCode) {
 | |
| 		const formattedNumber = formatNationalNumber(nationalNumber, carrierCode, 'NATIONAL', metadata)
 | |
| 		// For NANPA regions, return the national format for these regions
 | |
| 		// but prefix it with the country calling code.
 | |
| 		if (countryCallingCode === '1') {
 | |
| 			return countryCallingCode + ' ' + formattedNumber
 | |
| 		}
 | |
| 		// If regions share a country calling code, the country calling code need
 | |
| 		// not be dialled. This also applies when dialling within a region, so this
 | |
| 		// if clause covers both these cases. Technically this is the case for
 | |
| 		// dialling from La Reunion to other overseas departments of France (French
 | |
| 		// Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover
 | |
| 		// this edge case for now and for those cases return the version including
 | |
| 		// country calling code. Details here:
 | |
| 		// http://www.petitfute.com/voyage/225-info-pratiques-reunion
 | |
| 		//
 | |
| 		return formattedNumber
 | |
| 	}
 | |
| 	const iddPrefix = getIddPrefix(fromCountry, undefined, metadata.metadata)
 | |
| 	if (iddPrefix) {
 | |
| 		return `${iddPrefix} ${countryCallingCode} ${formatNationalNumber(nationalNumber, null, 'INTERNATIONAL', metadata)}`
 | |
| 	}
 | |
| } |