UNPKG

@jakguru/phone-object

Version:

An immutable data structure representing a specific phone number and accompanying methods. It contains class and instance methods of creating, parsing, validating, and formatting phone numbers. Based on google-libphonenumber, which is in turn based on Goo

491 lines 21.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Phone_instances, _Phone_phone, _Phone_country, _Phone_util, _Phone_parsed, _Phone_valid, _Phone_type, _Phone_guessCountry, _Phone_guessCountryWithoutPrefix, _Phone_sortCountriesByPrefix, _Phone_getPhoneNumberType, _Phone_getCorrectedNANPCountry; Object.defineProperty(exports, "__esModule", { value: true }); exports.Phone = void 0; /** * The `allCountries` variable is imported from the `country-telephone-data` library and represents an array of objects containing information about all countries recognized by the library. * @type {Array<Country>} */ const country_telephone_data_1 = require("country-telephone-data"); /** * The `libPhoneNumber` module is imported from the `google-libphonenumber` library and contains functions for parsing, formatting, and validating international phone numbers. * @namespace * @alias libPhoneNumber */ const libPhoneNumber = __importStar(require("google-libphonenumber")); /** * The `isos` variable is imported from the `./countries` module and represents a set of all ISO 3166-1 alpha-2 codes recognized by the library. * @type {Set<Country>} */ const countries_1 = require("./countries"); /** * The `timezones` variable is imported from the `./countries` module and represents an object containing information about all timezones recognized by the library. * @type {Object<Country, CountryTimezone>} */ const countries_2 = require("./countries"); /** * The `areaCodeMap` variable is imported from the `./nampa` module and represents a map of NANPA area codes to NANPA country codes. */ const nampa_1 = require("./nampa"); /** * The `nanpaCountries` variable is imported from the `./nampa` module and represents a list of NANPA country codes. */ const nampa_2 = require("./nampa"); /** * The `Phone` class represents a phone number and provides methods to retrieve information about it. * It implements the `PhoneModel` interface. * @class * @implements {PhoneModel} */ class Phone { /** * The `Phone` class constructor takes a `phone` argument of type `RawPhoneType` and an optional `country` argument of type `RawCountryType`. * It initializes `Phone` instance. * @constructor * @param {RawPhoneType} phone - The phone number to be parsed and validated. * @param {RawCountryType} [country] - The country code of the phone number. If not provided, the country will be guessed based on the phone number. */ constructor(phone, country) { _Phone_instances.add(this); /** * The phone number as a string stripped of all non-numeric characters. * @type {string} * @private * @readonly */ _Phone_phone.set(this, void 0); /** * The country code of the phone number. * @type {CountryOrUnknown} * @private * @readonly */ _Phone_country.set(this, void 0); /** * The `PhoneNumberUtil` instance used to parse and validate phone numbers. * @type {libPhoneNumber.PhoneNumberUtil} * @private * @readonly */ _Phone_util.set(this, void 0); /** * The parsed `PhoneNumber` object representing the phone number. * @type {libPhoneNumber.PhoneNumber | undefined} * @private * @readonly */ _Phone_parsed.set(this, void 0); /** * Whether the phone number uses a valid format for the parsed country or not. * @type {boolean} * @private * @readonly */ _Phone_valid.set(this, false /** * The type of the phone number. * @type {PhoneTypes} * @private * @readonly */ ); /** * The type of the phone number. * @type {PhoneTypes} * @private * @readonly */ _Phone_type.set(this, 'INVALID' /** * The `Phone` class constructor takes a `phone` argument of type `RawPhoneType` and an optional `country` argument of type `RawCountryType`. * It initializes `Phone` instance. * @constructor * @param {RawPhoneType} phone - The phone number to be parsed and validated. * @param {RawCountryType} [country] - The country code of the phone number. If not provided, the country will be guessed based on the phone number. */ ); __classPrivateFieldSet(this, _Phone_phone, String(phone).replace(/\D/g, ''), "f"); const countryTest = 'string' === typeof country ? country.trim().toUpperCase() : 'XX'; __classPrivateFieldSet(this, _Phone_country, countries_1.isos.has(countryTest) ? countryTest : 'XX', "f"); __classPrivateFieldSet(this, _Phone_util, libPhoneNumber.PhoneNumberUtil.getInstance(), "f"); if (__classPrivateFieldGet(this, _Phone_country, "f") === 'XX') { __classPrivateFieldSet(this, _Phone_country, __classPrivateFieldGet(this, _Phone_instances, "m", _Phone_guessCountry).call(this, __classPrivateFieldGet(this, _Phone_phone, "f")), "f"); } try { __classPrivateFieldSet(this, _Phone_parsed, __classPrivateFieldGet(this, _Phone_util, "f").parseAndKeepRawInput(__classPrivateFieldGet(this, _Phone_phone, "f"), __classPrivateFieldGet(this, _Phone_country, "f")), "f"); } catch { // noop } if (__classPrivateFieldGet(this, _Phone_parsed, "f")) { try { __classPrivateFieldSet(this, _Phone_valid, __classPrivateFieldGet(this, _Phone_util, "f").isValidNumber(__classPrivateFieldGet(this, _Phone_parsed, "f")), "f"); __classPrivateFieldSet(this, _Phone_type, __classPrivateFieldGet(this, _Phone_instances, "m", _Phone_getPhoneNumberType).call(this, __classPrivateFieldGet(this, _Phone_parsed, "f")), "f"); } catch { // noop } } if (true === __classPrivateFieldGet(this, _Phone_valid, "f") && nampa_2.nanpaCountries.includes(__classPrivateFieldGet(this, _Phone_country, "f"))) { __classPrivateFieldSet(this, _Phone_country, __classPrivateFieldGet(this, _Phone_instances, "m", _Phone_getCorrectedNANPCountry).call(this), "f"); } Object.defineProperty(this, 'country', { value: this.country, writable: false }); Object.defineProperty(this, 'valid', { value: this.valid, writable: false }); Object.defineProperty(this, 'type', { value: this.type, writable: false }); Object.defineProperty(this, 'mobile', { value: this.mobile, writable: false }); Object.defineProperty(this, 'raw', { value: this.raw, writable: false }); Object.defineProperty(this, 'national', { value: this.national, writable: false }); Object.defineProperty(this, 'international', { value: this.international, writable: false }); Object.defineProperty(this, 'e164', { value: this.e164, writable: false }); Object.defineProperty(this, 'timezone', { value: this.timezone, writable: false }); Object.freeze(this); } /** * Returns the country code of the phone number. * @readonly * @type {CountryOrUnknown} */ get country() { return __classPrivateFieldGet(this, _Phone_country, "f"); } /** * Returns whether the phone number uses a valid format for the parsed country or not. * @readonly * @type {boolean} */ get valid() { return __classPrivateFieldGet(this, _Phone_valid, "f"); } /** * Returns the type of the phone number. * @readonly * @type {PhoneTypes} */ get type() { return __classPrivateFieldGet(this, _Phone_type, "f"); } /** * Returns whether the phone number is a mobile number or not. * @readonly * @type {boolean} */ get mobile() { return 'string' === typeof this.type && ['MOBILE', 'FIXED_LINE_OR_MOBILE'].includes(this.type); } /** * Returns the raw phone number. * @readonly * @type {string} * * @remarks * If the phone number is not a valid phone number, the initial phone number which was passed to the constructor is returned. */ get raw() { if (!__classPrivateFieldGet(this, _Phone_parsed, "f")) { return __classPrivateFieldGet(this, _Phone_phone, "f"); } return __classPrivateFieldGet(this, _Phone_util, "f").format(__classPrivateFieldGet(this, _Phone_parsed, "f"), libPhoneNumber.PhoneNumberFormat.E164).replace(/\D/g, ''); } /** * Returns the national format of the phone number. * @readonly * @type {string} * * @remarks * If the phone number is not a valid phone number, the initial phone number which was passed to the constructor is returned. */ get national() { if (!__classPrivateFieldGet(this, _Phone_parsed, "f")) { return __classPrivateFieldGet(this, _Phone_phone, "f"); } return __classPrivateFieldGet(this, _Phone_util, "f").format(__classPrivateFieldGet(this, _Phone_parsed, "f"), libPhoneNumber.PhoneNumberFormat.NATIONAL); } /** * Returns the international format of the phone number. * @readonly * @type {string} * * @remarks * If the phone number is not a valid phone number, the initial phone number which was passed to the constructor is returned. */ get international() { if (!__classPrivateFieldGet(this, _Phone_parsed, "f")) { return __classPrivateFieldGet(this, _Phone_phone, "f"); } return __classPrivateFieldGet(this, _Phone_util, "f").format(__classPrivateFieldGet(this, _Phone_parsed, "f"), libPhoneNumber.PhoneNumberFormat.INTERNATIONAL); } /** * Returns the E.164 format of the phone number. * @readonly * @type {string} * * @remarks * If the phone number is not a valid phone number, the initial phone number which was passed to the constructor is returned. */ get e164() { if (!__classPrivateFieldGet(this, _Phone_parsed, "f")) { return __classPrivateFieldGet(this, _Phone_phone, "f"); } return __classPrivateFieldGet(this, _Phone_util, "f").format(__classPrivateFieldGet(this, _Phone_parsed, "f"), libPhoneNumber.PhoneNumberFormat.E164); } /** * Returns the timezone of the phone number. * @readonly * @type {PossiblePhoneTimezone} */ get timezone() { if (countries_2.timezones.has(__classPrivateFieldGet(this, _Phone_country, "f"))) { return countries_2.timezones.get(__classPrivateFieldGet(this, _Phone_country, "f")); } return 'UTC'; } /** * Returns an object representation of the phone number. * @returns {{ * phone: string; * country: string; * valid: boolean; * type: PhoneTypes; * mobile: boolean; * raw: string; * national: string; * international: string; * e164: string; * timezone: PossiblePhoneTimezone; * }} - An object representation of the phone number. */ toObject() { return { /** * The phone number as a string stripped of all non-numeric characters. */ phone: __classPrivateFieldGet(this, _Phone_phone, "f"), /** * The country code of the phone number. * @remarks * In cases where the country was not recognized and could not be guessed from the phone number, `'XX'` is used. * @type {CountryOrUnknown} */ country: __classPrivateFieldGet(this, _Phone_country, "f"), /** * Whether the phone number uses a valid format for the parsed country or not. * @type {boolean} */ valid: this.valid, /** * The type of the phone number. * @type {PhoneTypes} */ type: this.type, /** * Whether the phone number is possibly a mobile number or not. * @type {boolean} */ mobile: this.mobile, /** * The phone number as a string stripped of all non-numeric characters. * @type {string} */ raw: this.raw, /** * The phone number as a string in the national format for the parsed country. * @type {string} */ national: this.national, /** * The phone number as a string in the international format for the parsed country. * @type {string} */ international: this.international, /** * The phone number as a string in the E.164 format. * @type {string} */ e164: this.e164, /** * The estimated timezone of the phone number based on the phone number's country. It can be either a `CountryTimezone` or `'UTC'`. */ timezone: this.timezone, }; } /** * Returns a JSON representation of the phone number. * @returns {{ * phone: string; * country: string; * valid: boolean; * type: PhoneTypes; * mobile: boolean; * raw: string; * national: string; * international: string; * e164: string; * timezone: PossiblePhoneTimezone; * }} - A JSON-safe representation of the phone number. */ toJSON() { return this.toObject(); } /** * Returns the E.164 format of the phone number as a string. * @returns {string} - The E.164 format of the phone number. */ toString() { return this.e164; } /** * Returns a stringified representation of the phone number. * @returns {string} - A string representation of the phone number. */ inspect() { return `Phone { phone: ${__classPrivateFieldGet(this, _Phone_phone, "f")}, country: ${__classPrivateFieldGet(this, _Phone_country, "f")}, valid: ${this.valid === true ? 'true' : 'false'}, type: ${this.type}, mobile: ${this.mobile === true ? 'true' : 'false'}, raw: ${this.raw}, national: ${this.national}, international: ${this.international}, e164: ${this.e164}, timezone: ${this.timezone}, }`; } /** * Serializes the phone object to an obfuscated string which can be used to recreate the phone object from the `Phone.deserialize` method. * @returns {string} - The serialized phone object. */ serialize() { const hash = Buffer.from(JSON.stringify({ phone: this.raw, country: this.country })).toString('base64'); return Buffer.from(JSON.stringify({ phone: this.raw, country: this.country, hash })).toString('base64'); } /** * Creates a new phone object from a serialized phone object. * @param serialized The serialized phone object returned from the `Phone.serialize` method. * @returns A Phone instance with the same properties as the original phone object. * @throws An error if the serialized phone object is not valid. */ static deserialize(serialized) { let asJsonString; try { asJsonString = Buffer.from(serialized, 'base64').toString('utf-8'); } catch { throw new Error('Not a valid serialized phone object'); } let asJson; try { asJson = JSON.parse(asJsonString); } catch { throw new Error('Not a valid serialized phone object'); } if (!asJson.phone || !asJson.country || !asJson.hash) { throw new Error('Not a valid serialized phone object'); } const hash = Buffer.from(JSON.stringify({ phone: asJson.phone, country: asJson.country })).toString('base64'); if (hash !== asJson.hash) { throw new Error('Not a valid serialized phone object'); } return new Phone(asJson.phone, asJson.country === 'XX' ? undefined : asJson.country); } } exports.Phone = Phone; _Phone_phone = new WeakMap(), _Phone_country = new WeakMap(), _Phone_util = new WeakMap(), _Phone_parsed = new WeakMap(), _Phone_valid = new WeakMap(), _Phone_type = new WeakMap(), _Phone_instances = new WeakSet(), _Phone_guessCountry = function _Phone_guessCountry(phone) { const potentials = country_telephone_data_1.allCountries .map((c) => ({ iso: c.iso2.toUpperCase(), prefix: String(c.dialCode).trim().replace(/\D/g, ''), })) .filter((c) => phone.substring(0, c.prefix.length) === c.prefix) .sort(__classPrivateFieldGet(this, _Phone_instances, "m", _Phone_sortCountriesByPrefix)) .filter((c) => { // done like this because typescript thinks that T.constructor is not a type of T but a plain function // https://stackoverflow.com/a/61444747/10645758 const t = this.constructor; const tp = new t(phone, c.iso); return tp.valid; }); if (potentials.length >= 1) { return potentials[0].iso; } return __classPrivateFieldGet(this, _Phone_instances, "m", _Phone_guessCountryWithoutPrefix).call(this, phone); }, _Phone_guessCountryWithoutPrefix = function _Phone_guessCountryWithoutPrefix(phone) { const potentials = country_telephone_data_1.allCountries .map((c) => ({ iso: c.iso2.toUpperCase(), prefix: String(c.dialCode).trim().replace(/\D/g, ''), })) .sort(__classPrivateFieldGet(this, _Phone_instances, "m", _Phone_sortCountriesByPrefix)) .filter((c) => { // done like this because typescript thinks that T.constructor is not a type of T but a plain function // https://stackoverflow.com/a/61444747/10645758 const t = this.constructor; const tp = new t(phone, c.iso); return tp.valid; }); if (potentials.length >= 1) { return potentials[0].iso; } return 'XX'; }, _Phone_sortCountriesByPrefix = function _Phone_sortCountriesByPrefix(a, b) { if (a.prefix.length === b.prefix.length) { const intA = parseInt(a.prefix); const intB = parseInt(b.prefix); if (intA === intB) { return a.iso.localeCompare(b.iso); } return intA > intB ? 1 : -1; } return a.prefix.length > b.prefix.length ? 1 : -1; }, _Phone_getPhoneNumberType = function _Phone_getPhoneNumberType(parsed) { if (!parsed) { return 'INVALID'; } const type = __classPrivateFieldGet(this, _Phone_util, "f").getNumberType(parsed); const typeValues = Object.values(libPhoneNumber.PhoneNumberType); const typeKeys = Object.keys(libPhoneNumber.PhoneNumberType); const typeIndex = typeValues.indexOf(type); return typeKeys[typeIndex] || 'INVALID'; }, _Phone_getCorrectedNANPCountry = function _Phone_getCorrectedNANPCountry() { const ak = this.raw.substring(1, 4); if (nampa_1.areaCodeMap[ak] && 'string' === typeof nampa_1.areaCodeMap[ak]) { return nampa_1.areaCodeMap[ak]; } return 'XX'; }; //# sourceMappingURL=phone.js.map