@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
JavaScript
"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