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)}`
 | 
						|
	}
 | 
						|
} |