fullstack-phone

    1.93.0 • Public • Published

    fullstack-phone ☎️

    npm Libphonenumber version
    npm version v8.12.35

    fullstack-phone provides formatting, validation, and parsing of phone numbers per-region. The system is optimized for use as two modules:

    1. a metadata server providing dynamic regional metadata
    2. a lightweight, Closure-compiled phone client (27KB, 10KB gzipped)

    This project was extended from Nathan Hammond's project, which itself is an adaptation of Google's libphonenumber library.

    Contents

    Installation

    npm install fullstack-phone

    Usage

    Demo App: fullstack-phone-demo

    The modules are optimized for use in two environments.

    On the server (requires Node 4+):

    // Node.js:
    var phoneServer = require('fullstack-phone/server');
    
    var metadata = phoneServer.loadMeta(['US', 'RU']); // load US and RU phone metadata
    // now serve the metadata via some REST API or write it to a file for bundling with client code

    On the client (assuming a client-side bundler that provides require, like webpack):

    // Browser:
    var phoneClient = require('fullstack-phone/client');
    
    // fetch the metadata somehow, then pass to createPhoneHandler to instantiate a handler:
    var phoneHandler = phoneClient.createPhoneHandler(metadata);

    Once initialized, the phone handler can be used to process phone numbers:

    // phone handler functions:
    
    phoneHandler.getSupportedRegions(); // > ['US', 'RU']
    
    phoneHandler.formatPhoneNumber(
      { countryCode: '1', nationalNumber: '5101234567'},
      { style: 'national'}
    );
    // > '(510) 123-4567'
    
    phoneHandler.parsePhoneNumber('5101234567', 'US');
    // > { countryCode: '1', nationalNumber: '5101234567' }
    
    phoneHandler.validatePhoneNumber(
      { countryCode: '1', nationalNumber: '5101234567'},
      'US'
    );
    // > [Error: PHONE_INVALID_FOR_REGION]

    It's also possible to use both within the same environment. Using the server module in the browser, however, nullifies the advantages of the per-region metadata slicing.

    Why

    Google’s libphonenumber library is the de-facto industry standard for processing international phone numbers, providing support for formatting, validating, and normalizing phone numbers in 250+ regions. However, the default phone metadata is quite heavy. Various custom JS packages have reduced the code & metadata footprint by:

    This package fills a different niche by providing:

    • The official libphonenumber code (not a pure JS re-write)
    • A small, static code base that doesn’t change for different regions
    • Pluggable metadata bundles for individual regions

    Canonical Phone Object

    When using Google libphonenumber directly, processing a phone number requires parsing a string or initializing a protocol buffer phone number object and calling setters for its various properties.

    In contrast, fullstack-phone provides a more idiomatic JavaScript phone object, removing the need to call multiple setters. Most of the phone number functions here operate on a canonical phoneObj, as follows:

    {
      countryCode : '1',
      nationalNumber : '5105551234',
      extension : '999'
    }
    • countryCode

      • Required. A number or string of digits representing a country phone code, e.g., '1'.
    • nationalNumber

      • Required. A number or string of digits representing a phone number, as defined by E.164, e.g., '4085551212'.
      • Note that this excludes the leading national prefix (or "trunk code"), which is 0 or 1 in most territories.
      • Italian leading zeros should be included here.
    • extension

      • Optional. A string representing the phone number extension, e.g., '123'.

    Notes

    For proper formatting and validation results, nationalNumber and countryCode must only contain digits. In addition, countryCode must be the calling code of a country for which the phone handler was initialized. (For example, if a phoneObj is passed with countryCode: 44, the phone handler must have been loaded with GB metadata for proper results.)

    The phoneObj object can be created by calling parsePhoneNumber on a phone number string.

    APIs

    Server

    var phoneServer = require('fullstack-phone/server');

    The phone server exposes only one function:

    loadMeta

    phoneServer.loadMeta(regionCodeArray)

    Given an array of two-letter region codes (ISO 3166-1 alpha-2), return a metadata bundle for those regions, to be passed to phoneClient.createPhoneHandler().

    Notes:
    • If regionCodeArray is undefined, all regional metadata is returned.
    • loadMeta adds support for the following regions to Google libphonenumber:
      • PN: Pitcairn Islands (copied from New Zealand metadata)
      • AN: Netherlands Antilles (copied from Bonaire, Sint Eustatius and Saba)
    • Some regions depend on metadata of other regions. For example, the Bahamas (BS) shares a telephone country code (1) with the United States (US). Since the US is considered the main region for country code 1, the US metadata must be included to support processing of Bahamas phone numbers. loadMeta takes care of this automatically.

    The full list of region codes supported is:

    ['001', 'AC', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TA', 'TC', 'TD', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'XK', 'YE', 'YT', 'ZA', 'ZM', 'ZW']

    Note that '001' is used to load metadata for global numbers, such as 1-800 numbers.

    Examples
    var meta = phoneServer.loadMeta(['DE', 'AU']);
    // > metadata object for DE and AU
    
    var meta = phoneServer.loadMeta();
    // > metadata object for all regions

    Client

    var phoneClient = require('fullstack-phone/client');

    The phone client exposes only one function:

    createPhoneHandler

    phoneClient.createPhoneHandler(metadata)

    Given a metadata bundle from phoneServer.loadMeta(), return a phone handler instantiated for that metadata.

    Phone Handler

    var phoneHandler = phoneClient.createPhoneHandler(metadata);

    The phone handler returned by createPhoneHandler provides the following methods:

    Exceptions

    Any method that takes a phoneObj parameter can throw the following exception if called with an invalid canonical phone object:

    phoneHandler.inferPhoneNumberType(123);
    // Uncaught Error: Phone object conversion failed
    
    phoneHandler.inferPhoneNumberType({ countryCode: 1, nationalNumber: false });
    // Uncaught Error: Phone object conversion failed

    Any method that takes a regionCode string can throw the following exception if called with a region code for which the handler has not been initialized:

    phoneHandler.getCountryCodeForRegion();
    // Uncaught Error: Metadata not loaded for region: undefined
    
    phoneHandler.getCountryCodeForRegion('XX');
    // Uncaught Error: Metadata not loaded for region: XX

    getSupportedRegions

    phoneHandler.getSupportedRegions()

    Return array of supported region codes.

    Note that if a dependent region was loaded (such as the Bahamas), the main region for the shared country code is also reported a supported region (e.g., US).

    Example
    phoneHandler.getSupportedRegions();
    // > ['US', 'RU']

    countryCodeToRegionCodeMap

    phoneHandler.countryCodeToRegionCodeMap()

    Return map from country calling codes to array of supported regions.

    Example
    phoneHandler.countryCodeToRegionCodeMap();
    // > { '1': [ 'US', 'BS' ], '7': [ 'RU' ] }

    getCountryCodeForRegion

    phoneHandler.getCountryCodeForRegion(regionCode)

    Return a country calling code as a number, given a regionCode string (assuming metadata has already been loaded for that region).

    Example
    phoneHandler.getCountryCodeForRegion('RU');
    // > 7

    formatPhoneNumber

    phoneHandler.formatPhoneNumber(phoneObj, options)

    Return a formatted phone number as a string, given a phoneObj object and options object with a valid style property ('national', 'international', 'e164', or 'rfc3966').

    The options object has a single string property to indicate the formatting style desired:

    {
        style: 'national' // or 'international', 'e164', 'rfc3966'
    }
    Examples
    var phone = { countryCode: '1', nationalNumber: '5101234567' };
    
    phoneHandler.formatPhoneNumber(phone, { style: 'international' });
    // > '+1 510-123-4567'
    
    phoneHandler.formatPhoneNumber(phone, { style: 'national' });
    // > '(510) 123-4567'
    
    phoneHandler.formatPhoneNumber(phone, { style: 'e164' });
    // > '+15101234567'
    
    phoneHandler.formatPhoneNumber(phone, { style: 'rfc3966' })
    // > 'tel:+1-510-123-4567'

    validatePhoneNumber

    phoneHandler.validatePhoneNumber(phoneObj, ?regionCode)

    Perform full validation on a phone number: Given a phoneObj object and optional regionCode string, return an Error object indicating any problems with the phone object (or true if it passed validation).

    If regionCode is provided, the phone number is validated for the given region. If it is omitted, the phone number is validated for the region inferred from the number itself. In both cases, the handler needs to have already been instantiated with metadata for the expected region(s).

    The possible error messages are:

    • 'PHONE_INVALID_FOR_REGION'
      • Phone number is not valid for some reason. (See the examples below.)
    • 'PHONE_INVALID_COUNTRY_CODE'
      • The phoneObj.countryCode is not recognized for one of these reasons:
        • It's completely invalid (like '9999'),
        • Metadata has not been loaded for the region corresponding to phoneObj.countryCode, or
        • It does not correspond to to the regionCode passed.
    • 'PHONE_NUMBER_TOO_LONG'
    • 'PHONE_NUMBER_TOO_SHORT'
    • 'PHONE_NUMBER_INVALID_LENGTH'
      • Phone number is not too long, not too short, but not just right, either. For example, Andorra (AD) numbers are 6, 8, or 9 digits, so a 7-digit number yields this error.
    Examples
    // valid US phone number
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '5105261234' }, 'US');
    // > true
    
    // regionCode is optional
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '5105261234' });
    // > true
    
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '5' }, 'US');
    // > [Error: PHONE_NUMBER_TOO_SHORT]
    
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '51052612341' }, 'US');
    // > [Error: PHONE_NUMBER_TOO_LONG]
    
    // 10 digits (like a US phone number), but not an actual valid number
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '1234567890' }, 'US');
    // > [Error: PHONE_INVALID_FOR_REGION]
    phoneHandler.validatePhoneNumber({ countryCode: '1', nationalNumber: '1234567890' });
    // > [Error: PHONE_INVALID_FOR_REGION]
    
    // completely invalid countryCode
    phoneHandler.validatePhoneNumber({ countryCode: '999', nationalNumber: '5105261234' }, 'US');
    // > [Error: PHONE_INVALID_COUNTRY_CODE]
    
    // countryCode 44 is for GB, but US regionCode was passed
    phoneHandler.validatePhoneNumber({ countryCode: '44', nationalNumber: '1212345678' }, 'US');
    // > [Error: PHONE_INVALID_COUNTRY_CODE]
    
    // valid GB number
    phoneHandler.validatePhoneNumber({ countryCode: '44', nationalNumber: '1212345678' }, 'GB');
    // > true
    phoneHandler.validatePhoneNumber({ countryCode: '44', nationalNumber: '1212345678' });
    // > true

    validateLength

    phoneHandler.validateLength(phoneObj, ?regionCode)

    Perform minimal validation (length check only) on a phone number: Given a phoneObj object and optional regionCode string, return an Error object indicating any length problems with the phone object (or true if it passed the length validation).

    If regionCode is provided, the phone number is validated for the given region. If it is omitted, the phone number is validated for the region inferred from the number itself. In both cases, the handler needs to have already been instantiated with metadata for the expected region(s).

    The possible error messages are:

    • 'PHONE_INVALID_COUNTRY_CODE'
      • The phoneObj.countryCode is not recognized for one of these reasons:
        • It's completely invalid (like '9999'),
        • Metadata has not been loaded for the region corresponding to phoneObj.countryCode, or
        • It does not correspond to to the regionCode passed.
    • 'PHONE_NUMBER_TOO_LONG'
    • 'PHONE_NUMBER_TOO_SHORT'
    • 'PHONE_NUMBER_INVALID_LENGTH'
      • The phone number is not too long, not too short, but not just right, either. For example, Andorra (AD) numbers are 6, 8, or 9 digits, so a 7-digit number yields this error.
    • 'PHONE_NUMBER_POSSIBLE_LOCAL_ONLY'
      • The phone number could be dialed within a local area (e.g., US numbers without the area code) but is not long enough to be a full phone number dialable from anywhere.
    • 'PHONE_INVALID_FOR_REGION'
      • Fallback error: This error would only be returned if libphonenumber adds a new enum member to ValidationResult.
    Examples
    // any 10-digit phone number in the US passes validateLength
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '1234567890' }, 'US');
    // > true
    
    // regionCode is optional
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '1234567890' });
    // > true
    
    // 7-digit numbers are only possible locally in the US
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '1234567' }, 'US');
    // > [Error: PHONE_NUMBER_POSSIBLE_LOCAL_ONLY]
    
    // 6-digit numbers are too short in the US
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '123456' }, 'US');
    // > [Error: PHONE_NUMBER_TOO_SHORT]
    
    // 11-digit numbers are too long in the US
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '12345678901' }, 'US');
    // > [Error: PHONE_NUMBER_TOO_LONG]
    
    // wrong regionCode yields PHONE_INVALID_COUNTRY_CODE (regionCode AD does not match countryCode 1)
    phoneHandler.validateLength({ countryCode: '1', nationalNumber: '1234567890' }, 'AD');
    // > [Error: PHONE_INVALID_COUNTRY_CODE]
    
    // a completely invalid countryCode (999) also yields PHONE_INVALID_COUNTRY_CODE
    phoneHandler.validateLength({ countryCode: '999', nationalNumber: '1234567890' });
    // > [Error: PHONE_INVALID_COUNTRY_CODE]
    
    // 7-digit number in Andorra (regionCode AD, countryCode 376) is in between valid number lengths
    phoneHandler.validateLength({ countryCode: '376', nationalNumber: '1234567' }, 'AD');
    // > [Error: PHONE_NUMBER_INVALID_LENGTH]

    parsePhoneNumber

    phoneHandler.parsePhoneNumber(phoneNumberToParse, ?regionCode)

    Parse a string and return a phoneObj object (or an Error object if parsing failed), given the string parameters phoneNumberToParse and optional regionCode.

    The optional regionCode parameter provides a fallback if the country code cannot be extracted from the phoneNumberToParse string. If the string contains + followed by the country code, then regionCode can be safely omitted. In both cases, however, the handler needs to have already been instantiated with metadata for the expected region(s).

    The possible error messages are:

    • 'PHONE_INVALID_COUNTRY_CODE'
    • 'PHONE_NUMBER_TOO_SHORT'
    • 'PHONE_NUMBER_TOO_LONG'
    • 'PHONE_NOT_A_NUMBER'
    • 'PHONE_TOO_SHORT_AFTER_IDD'
    Examples
    phoneHandler.parsePhoneNumber('5101234567', 'US');
    // > { countryCode: '1', nationalNumber: '5101234567' }
    
    phoneHandler.parsePhoneNumber('ABC', 'US');
    // > [Error: PHONE_NOT_A_NUMBER]
    
    // regionCode is optional if string has '+' followed by country code
    phoneHandler.parsePhoneNumber('+1 510-123-4567'); // international format
    phoneHandler.parsePhoneNumber('+15101234567'); // E.164 format
    phoneHandler.parsePhoneNumber('tel:+1-510-123-4567'); // RFC 3966 format
    // > { countryCode: '1', nationalNumber: '5101234567' }
    
    // Do not omit regionCode if phoneNumberString is missing '+'
    phoneHandler.parsePhoneNumber('15101234567');
    // > [Error: PHONE_INVALID_COUNTRY_CODE]
    h.parsePhoneNumber("15101234567", "US");
    // > { countryCode: '1', nationalNumber: '5101234567' }

    getExampleNumberForType

    phoneHandler.getExampleNumberForType(type, regionCode)

    Return an example phoneObj object, given the string parameters type and regionCode.

    The type parameter is an enum based on libphonenumber i18n.phonenumbers.PhoneNumberType and can be any of the following strings:

    • 'FIXED_LINE'
    • 'MOBILE'
    • 'FIXED_LINE_OR_MOBILE'
    • 'TOLL_FREE'
    • 'PREMIUM_RATE'
    • 'SHARED_COST'
    • 'VOIP'
    • 'PERSONAL_NUMBER'
    • 'PAGER'
    • 'UAN'
    • 'VOICEMAIL'
    • 'UNKNOWN'
    Example
    phoneHandler.getExampleNumberForType('MOBILE', 'US');
    // > { countryCode: '1', nationalNumber: '2015550123' }

    inferPhoneNumberType

    phoneHandler.inferPhoneNumberType(phoneObj)

    Return a string indicating the phone number type (see getExampleNumberForType), given a valid phoneobj. Returns 'UNKNOWN' if metadata has not been loaded for the region of the phone number or the number type otherwise cannot be inferred.

    Examples
    phoneHandler.inferPhoneNumberType({ countryCode: '1', nationalNumber: '5105261568' });
    // > 'FIXED_LINE_OR_MOBILE'
    
    // GB landline
    phoneHandler.inferPhoneNumberType({ countryCode: '44', nationalNumber: '1212345678' });
    // > 'FIXED_LINE'
    
    // GB mobile number
    phoneHandler.inferPhoneNumberType({ countryCode: '44', nationalNumber: '7400123456' })
    // > 'MOBILE'
    
    // invalid GB phone number
    phoneHandler.inferPhoneNumberType({ countryCode: '44', nationalNumber: '999999' });
    // > 'UNKNOWN'

    inferPhoneNumberRegion

    phoneHandler.inferPhoneNumberRegion(phoneObj)

    Return the two letter region code associated with a valid phoneObj.

    Returns null if the region cannot be determined. (This can happen if metadata has not been loaded for the region associated with the phoneObj.countryCode.

    phoneHandler.inferPhoneNumberRegion({ countryCode: '44', nationalNumber: '1212345678' });
    // > 'GB'
    
    phoneHandler.inferPhoneNumberRegion({ countryCode: '99', nationalNumber: '1212345678' });
    // > null

    getAsYouTypeFormatter

    phoneHandler.getAsYouTypeFormatter(regionCode)

    Return a new AsYouTypeFormatter object instantiated for the given regionCode.

    Example
    var formatter = phoneHandler.getAsYouTypeFormatter('GB');
    // > AsYouTypeFormatter object initialized to Great Britain

    AsYouTypeFormatter methods

    The initialized AsYouTypeFormatter object itself exposes the following methods:

    inputDigit
    formatter.inputDigit(digit)

    Given a digit (number or string), output the phone number formatted thus far (given the history of inputted digits).

    Note that digit can also be '+' or '*'

    Example
    formatter.inputDigit('5'); // > '5'
    formatter.inputDigit('1'); // > '51'
    formatter.inputDigit('0'); // > '510'
    formatter.inputDigit('1'); // > '510-1'
    formatter.inputDigit('2'); // > '510-12'
    formatter.inputDigit('3'); // > '510-123'
    formatter.inputDigit('4'); // > '510-1234'
    formatter.inputDigit('5'); // > '(510) 123-45'
    formatter.inputDigit('6'); // > '(510) 123-456'
    formatter.inputDigit('7'); // > '(510) 123-4567'
    clear
    formatter.clear()

    Clear the formatter state.

    Example
    formatter.inputDigit('5'); // > '5'
    formatter.inputDigit('1'); // > '51'
    formatter.inputDigit('0'); // > '510'
    formatter.inputDigit('1'); // > '510-1'
    formatter.inputDigit('2'); // > '510-12'
    formatter.clear();
    formatter.inputDigit('9'); // > '9'
    formatter.inputDigit('1'); // > '91'
    formatter.inputDigit('9'); // > '919'
    formatter.inputDigit('4'); // > '919-4'
    formatter.inputDigit('8'); // > '919-48'
    ...
    inputDigitAndRememberPosition
    formatter.inputDigitAndRememberPosition(digit)

    Same as inputDigit, but remembers the (1-indexed) position where the digit was entered, to be retrieved later by getRememberedPosition. This position can update as formatting characters are inserted by the AsYouTypeFormatter.

    getRememberedPosition
    formatter.getRememberedPosition()

    Returns the (1-indexed) position (as a number) of the digit previously passed to inputDigitAndRememberPosition.

    Example
    // getRememberedPosition starts out as 0
    formatter.getRememberedPosition(); // > 0
    
    formatter.inputDigitAndRememberPosition(5); // > '5'
    
    // the 5 was inputted at position 1
    formatter.getRememberedPosition(); // > 1
    
    // input additional digits until parens are inserted
    formatter.inputDigit(1); // > '51'
    formatter.inputDigit(0); // > '510'
    formatter.inputDigit(1); // > '510-1'
    formatter.inputDigit(2); // > '510-12'
    formatter.inputDigit(3); // > '510-123'
    formatter.inputDigit(4); // > '510-1234'
    formatter.inputDigit(5); // > '(510) 123-45'
    
    // now the original 5 is at position 2, since an open paren was inserted before it
    formatter.getRememberedPosition(); // 2

    Development

    see DEVELOPMENT.md

    Install

    npm i fullstack-phone

    DownloadsWeekly Downloads

    36

    Version

    1.93.0

    License

    Apache-2.0

    Unpacked Size

    1.09 MB

    Total Files

    9

    Last publish

    Collaborators

    • dwbruhn