@lykmapipo/phone
Version:
Helper utilities for parsing and validate phone numbers
309 lines (281 loc) • 8.15 kB
JavaScript
import clone from 'lodash/clone';
import camelCase from 'lodash/camelCase';
import forEach from 'lodash/forEach';
import merge from 'lodash/merge';
import toLower from 'lodash/toLower';
import {
PhoneNumberFormat,
PhoneNumberType,
PhoneNumberUtil,
} from 'google-libphonenumber';
import { mergeObjects, tryCatch } from '@lykmapipo/common';
import { getCountryCode } from '@lykmapipo/env';
import { FORMATS } from './constants';
/**
* @name phoneNumberUtil
* @function phoneNumberUtil
* @description Internal instance of `PhoneNumberUtil`
* @returns {object} Validity of `PhoneNumberUtil`
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.5.0
* @version 0.1.0
* @private
* @example
*
* phoneNumberUtil.parseAndKeepRawInput('0715333777', 'TZ');
*/
export const phoneNumberUtil = PhoneNumberUtil.getInstance();
/**
* @name parseRawPhoneNumber
* @function parseRawPhoneNumber
* @description Attempt to parse given phone number
* @param {string} phoneNumber Number that we are attempting to parse
* @param {string} [countryCode] 2 or 3 letter ISO country code. Default to
* `process.env.DEFAULT_COUNTRY_CODE` or `os country code`.
* @returns {object} Validity of `PhoneNumberUtil`
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.5.0
* @version 0.1.0
* @private
* @example
*
* parseRawPhoneNumber('+255715333777');
* parseRawPhoneNumber('0715333777', 'TZ');
*/
export const parseRawPhoneNumber = (
phoneNumber,
countryCode = getCountryCode()
) => {
try {
// try parse phone number
const rawPhoneNumber = clone(phoneNumber);
const rawCountryCode = clone(countryCode);
const parsedPhoneNumber = phoneNumberUtil.parseAndKeepRawInput(
rawPhoneNumber,
rawCountryCode
);
return parsedPhoneNumber;
} catch (e) {
// fail to parse phone number
return undefined;
}
};
/**
* @name formatPhoneNumber
* @function formatPhoneNumber
* @description Format phone number use given format
* @param {object} phoneNumber Valid instance of parsed phone number
* @param {string} [format] Valid phone number format
* @returns {object} Formatted phone number
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.5.0
* @version 0.1.0
* @private
* @example
*
* const phoneNumber = parseRawPhoneNumber('0715333777', 'TZ');
* formatPhoneNumber(phoneNumber, FORMAT_E164);
*
* //=> result
* {
* e164: '+255715333777'
* }
*/
export const formatPhoneNumber = (phoneNumber, format) => {
try {
// try format parsed phone number
const phoneNumberFormat = toLower(format);
const formattedPhoneNumber = phoneNumberUtil.format(
phoneNumber,
PhoneNumberFormat[format]
);
return { [phoneNumberFormat]: formattedPhoneNumber };
} catch (e) {
// fail to format phone number
return {};
}
};
/**
* @name applyFormats
* @function applyFormats
* @description Format phone number using available phone number formats
* @param {object} phoneNumber Instance of parsed phone number
* @returns {object} Formatted phone number(s)
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.1.0
* @version 0.2.0
* @private
* @example
*
* const phoneNumber = parseRawPhoneNumber('0715333777', 'TZ');
* applyFormats(phoneNumber);
*
* //=> result
* {
* e164: '+255715333777',
* international: '+255 715 333 777',
* national: '0715 333 777',
* rfc3966: 'tel:+255-715-333-777'
* }
*/
export const applyFormats = (phoneNumber) => {
// initialize formats
let formats = {};
// format phone number per each available format
forEach(FORMATS, (phoneNumberFormat) => {
const formattedPhoneNumber = formatPhoneNumber(
phoneNumber,
phoneNumberFormat
);
formats = mergeObjects(formats, formattedPhoneNumber);
});
// return formatted phone number
return formats;
};
/**
* @name checkTypes
* @function checkTypes
* @description Derive phone number type validity
* @param {object} phoneNumber Instance of parsed phone number
* @returns {object} Validities of a phone number
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.5.0
* @version 0.1.0
* @private
* @example
*
* const phoneNumber = parseRawPhoneNumber('0715333777', 'TZ');
* checkTypes(phoneNumber);
*
* //=> result
* {
* isFixedLine: false,
* isMobile: true,
* isFixedLineOrMobile: false,
* isTollFree: false,
* isPremiumRate: false,
* isSharedCost: false,
* isVoip: false,
* isPersonalNumber: false,
* isPager: false,
* isUan: false,
* isVoicemail: false,
* isUnknown: false,
* type: 'MOBILE'
* }
*/
export const checkTypes = (phoneNumber) => {
// initialize types validity
const types = {};
// obtain parsed phone number type
const phoneNumberType = tryCatch(
() => phoneNumberUtil.getNumberType(phoneNumber),
-1
);
// type checker
const checkType = (typeIndex, typeName) => {
// derive type name and check phone number type
const numberTypeName = camelCase(`is${typeName}`);
const numberTypeIs = phoneNumberType === typeIndex;
// set type is flag
types[numberTypeName] = numberTypeIs;
// set type name string
if (numberTypeIs) {
types.type = typeName;
}
};
// check phone number type validity
forEach(PhoneNumberType, checkType);
// return types validity
return types;
};
/**
* @name parsePhoneNumberByCountryCode
* @function parsePhoneNumberByCountryCode
* @description Parse provided phone number to obtain its information
* @param {string} phoneNumber Valid phone number
* @param {...string} [countryCode] Valid country code(s) for validation. If
* not provided `process.env.DEFAULT_COUNTRY_CODE` or `os country code` will
* be used as a default
* @returns {object | undefined} Parsed phone number or undefined
* @author lally elias <lallyelias87@gmail.com>
* @license MIT
* @since 0.5.0
* @version 0.1.0
* @private
* @static
* @example
*
* const phoneNumber = parsePhoneNumberByCountryCode('+255715333777');
* const phoneNumber = parsePhoneNumberByCountryCode('+255715333777', 'TZ');
*
* //=> result
* {
* raw: '+255715333777',
* countryCode: 'TZ',
* callingCode: 255,
* extension: '',
* isValid: true,
* isPossible: true,
* isValidForCountryCode: true,
* isFixedLine: false,
* isMobile: true,
* type: 'MOBILE',
* isFixedLineOrMobile: false,
* isTollFree: false,
* isPremiumRate: false,
* isSharedCost: false,
* isVoip: false,
* isPersonalNumber: false,
* isPager: false,
* isUan: false,
* isVoicemail: false,
* isUnknown: false,
* e164: '+255715333777',
* international: '+255 715 333 777',
* national: '0715 333 777',
* rfc3966: 'tel:+255-715-333-777'
* }
*/
export const parsePhoneNumberByCountryCode = (phoneNumber, countryCode) => {
// parse phone number
try {
const parsed = parseRawPhoneNumber(phoneNumber, countryCode);
// prepare parse phone number result
let phone = {};
// set raw phone number
phone.raw = phoneNumber;
// set phone country code
phone.countryCode =
phoneNumberUtil.getRegionCodeForNumber(parsed) || countryCode;
// set phone country calling code
phone.callingCode = parsed.getCountryCodeOrDefault();
// set phone number extension
phone.extension = parsed.getExtensionOrDefault();
// set phone number valid flag
phone.isValid = phoneNumberUtil.isValidNumber(parsed);
// set possible flag
phone.isPossible = phoneNumberUtil.isPossibleNumber(parsed);
// set is valid for country(or region) code
phone.isValidForCountryCode = phoneNumberUtil.isValidNumberForRegion(
parsed,
phone.countryCode
);
// set phone number type flags
phone = merge({}, phone, checkTypes(parsed));
// format phone number in accepted formats
phone = merge({}, phone, applyFormats(parsed));
// add e164 format with no plus
phone.e164NoPlus = phone.e164.replace(/\+/g, '');
// return parsed phone number
return phone;
} catch (e) {
// fail to parse phone number
return undefined;
}
};