mirror of
https://github.com/itflow-org/itflow
synced 2026-08-27 01:45:13 +00:00
224 lines
9.4 KiB
JavaScript
224 lines
9.4 KiB
JavaScript
goog.provide("i18n.phonenumbers.demo");
|
|
// includes
|
|
goog.require("i18n.phonenumbers.PhoneNumberFormat");
|
|
goog.require("i18n.phonenumbers.PhoneNumberUtil");
|
|
goog.require("i18n.phonenumbers.Error");
|
|
goog.require("i18n.phonenumbers.AsYouTypeFormatter");
|
|
|
|
//* Libphonenumber enum name arrays - single source of truth lives in
|
|
//* src/js/constants.ts. The placeholder arrays below are replaced with the
|
|
//* real values at build time by scripts/build-utils.js (which matches the
|
|
//* exact `["__…__"]` strings).
|
|
const NUMBER_FORMATS = ["__NUMBER_FORMATS__"];
|
|
const NUMBER_TYPES = ["__NUMBER_TYPES__"];
|
|
const VALIDATION_ERRORS = ["__VALIDATION_ERRORS__"];
|
|
|
|
//* Build name -> integer maps from the arrays. Array index = libphonenumber
|
|
//* integer value, except NumberType.UNKNOWN which is -1.
|
|
const numberFormat = {};
|
|
NUMBER_FORMATS.forEach((name, i) => { numberFormat[name] = i; });
|
|
const numberType = {};
|
|
NUMBER_TYPES.forEach((name, i) => { numberType[name] = i; });
|
|
numberType["UNKNOWN"] = -1;
|
|
const validationError = {};
|
|
VALIDATION_ERRORS.forEach((name, i) => { validationError[name] = i; });
|
|
|
|
//* Reverse maps for converting libphonenumber integers back to string names.
|
|
const numberTypeName = {};
|
|
Object.keys(numberType).forEach((name) => { numberTypeName[numberType[name]] = name; });
|
|
const validationErrorName = {};
|
|
Object.keys(validationError).forEach((name) => { validationErrorName[validationError[name]] = name; });
|
|
|
|
//* Format the number as the user types.
|
|
const formatNumberAsYouType = (number, iso2) => {
|
|
try {
|
|
//* Have to clean it first, as AYTF stops formatting as soon as it hits any formatting char (even it's own)
|
|
//* (it's designed to be fed one char at a time, as opposed to every char every time).
|
|
const clean = number.replace(/[^+0-9]/g, "");
|
|
const formatter = new i18n.phonenumbers.AsYouTypeFormatter(iso2);
|
|
let result = "";
|
|
for (let i = 0; i < clean.length; i++) {
|
|
result = formatter.inputDigit(clean.charAt(i));
|
|
}
|
|
return result;
|
|
} catch {
|
|
return number;
|
|
}
|
|
};
|
|
|
|
//* Format the given number to the given format (string name like "E164").
|
|
const formatNumber = (number, iso2, formatName) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
if (phoneUtil.isPossibleNumber(numberObj)) {
|
|
const format =
|
|
typeof formatName === "undefined"
|
|
? i18n.phonenumbers.PhoneNumberFormat.E164
|
|
: numberFormat[formatName];
|
|
return phoneUtil.format(numberObj, format);
|
|
}
|
|
return number;
|
|
} catch {
|
|
return number;
|
|
}
|
|
};
|
|
|
|
//* Get an example number for the given country code, by number type name (e.g. "MOBILE"), in the given format name (e.g. "INTERNATIONAL").
|
|
const getExampleNumber = (iso2, numberTypeName, formatName) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.getExampleNumberForType(
|
|
iso2,
|
|
numberType[numberTypeName],
|
|
);
|
|
return phoneUtil.format(numberObj, numberFormat[formatName]);
|
|
} catch {
|
|
return "";
|
|
}
|
|
};
|
|
|
|
//* Get the core number, AKA the national significant number (NSN) - without any international dial code, or national prefix.
|
|
const getCoreNumber = (number, iso2) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
// getNationalNumber returns the NSN (no national prefix)
|
|
return numberObj.getNationalNumber().toString();
|
|
} catch {
|
|
return "";
|
|
}
|
|
};
|
|
|
|
//* Get the extension from the given number.
|
|
const getExtension = (number, iso2) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
return numberObj.getExtension();
|
|
} catch {
|
|
return "";
|
|
}
|
|
};
|
|
|
|
//* Get the type of the given number e.g. "FIXED_LINE" / "MOBILE", or null if it can't be determined.
|
|
const getNumberType = (number, iso2) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
const intType = phoneUtil.getNumberType(numberObj);
|
|
return numberTypeName[intType] || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
//* Get more info if the validation has failed e.g. "TOO_LONG" / "TOO_SHORT", or null if it can't be determined.
|
|
//* NOTE that isPossibleNumberWithReason returns a i18n.phonenumbers.PhoneNumberUtil.ValidationResult.
|
|
const getValidationError = (number, iso2) => {
|
|
if (!iso2) {
|
|
return "INVALID_COUNTRY_CODE";
|
|
}
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
const result = phoneUtil.isPossibleNumberWithReason(numberObj);
|
|
return validationErrorName[result] || null;
|
|
} catch (e) {
|
|
//* Here I convert thrown errors into ValidationResult enums (if possible).
|
|
//* errors are from i18n.phonenumbers.Error in the file https://github.com/googlei18n/libphonenumber/blob/master/javascript/i18n/phonenumbers/phonenumberutil.js.
|
|
if (e.message === i18n.phonenumbers.Error.INVALID_COUNTRY_CODE) {
|
|
return "INVALID_COUNTRY_CODE";
|
|
}
|
|
if (
|
|
//* Hack to solve issue where parseAndKeepRawInput throws weird error for zero or 1-digit (national) numbers e.g. "3" or "+13" s
|
|
number.length <= 3 ||
|
|
e.message === i18n.phonenumbers.Error.TOO_SHORT_AFTER_IDD ||
|
|
e.message === i18n.phonenumbers.Error.TOO_SHORT_NSN
|
|
) {
|
|
return "TOO_SHORT";
|
|
}
|
|
if (e.message === i18n.phonenumbers.Error.TOO_LONG) {
|
|
return "TOO_LONG";
|
|
}
|
|
|
|
//* Broken
|
|
return null;
|
|
}
|
|
};
|
|
|
|
//* FIXED_LINE_OR_MOBILE is its own category that is different to either MOBILE or FIXED_LINE (e.g. it is used for US numbers which could be used for either purpose - it should really be called something like FIXED_LINE_SLASH_MOBILE). So here we make it more user friendly by allowing it to be in any of those three categories. NOTE: this is actually in-line with how it behaves in other situations e.g. if you call isPossibleNumberForType with type="MOBILE" and the number set to a US number, it returns VALID even though it's type is technically FIXED_LINE_OR_MOBILE.
|
|
const getPatchedNumberTypeNames = (numberTypeNames) => {
|
|
const additions = [];
|
|
if (numberTypeNames.includes("FIXED_LINE_OR_MOBILE")) {
|
|
if (!numberTypeNames.includes("MOBILE")) {
|
|
additions.push("MOBILE");
|
|
}
|
|
if (!numberTypeNames.includes("FIXED_LINE")) {
|
|
additions.push("FIXED_LINE");
|
|
}
|
|
} else if (numberTypeNames.includes("MOBILE") || numberTypeNames.includes("FIXED_LINE")) {
|
|
additions.push("FIXED_LINE_OR_MOBILE");
|
|
}
|
|
return numberTypeNames.concat(additions);
|
|
};
|
|
|
|
//* Check if given number is valid using precise country/area-code matching rules.
|
|
//* Mirrors the plugin's iti.isValidNumberPrecise().
|
|
const isValidNumberPrecise = (number, iso2, numberTypeNames) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
const isValid = phoneUtil.isValidNumber(numberObj);
|
|
if (numberTypeNames) {
|
|
const patchedTypeNames = getPatchedNumberTypeNames(numberTypeNames);
|
|
const numberTypes = patchedTypeNames.map((typeName) => numberType[typeName]);
|
|
return isValid && numberTypes.includes(phoneUtil.getNumberType(numberObj));
|
|
}
|
|
return isValid;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//* Check if given number is valid based on its length (more future-proof than the precise check).
|
|
//* Mirrors the plugin's iti.isValidNumber().
|
|
const isValidNumber = (number, iso2, numberTypeNames) => {
|
|
try {
|
|
const phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
|
|
const numberObj = phoneUtil.parseAndKeepRawInput(number, iso2);
|
|
|
|
if (numberTypeNames) {
|
|
const patchedTypeNames = getPatchedNumberTypeNames(numberTypeNames);
|
|
for (let typeName of patchedTypeNames) {
|
|
//* Can't use phoneUtil.isPossibleNumberForType directly as it accepts IS_POSSIBLE_LOCAL_ONLY numbers e.g. local numbers that are much shorter.
|
|
const resultForType = phoneUtil.isPossibleNumberForTypeWithReason(numberObj, numberType[typeName]);
|
|
if (resultForType === i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//* Can't use phoneUtil.isPossibleNumber directly as it accepts IS_POSSIBLE_LOCAL_ONLY numbers e.g. local numbers that are much shorter.
|
|
const result = phoneUtil.isPossibleNumberWithReason(numberObj);
|
|
const isPossible = result === i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE;
|
|
return isPossible;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//* Exports
|
|
//* Note: goog.exportSymbol writes these onto the scope object, which the closure compiler output_wrapper (see scripts/build-utils.js) then exports as the default ES Module export.
|
|
goog.exportSymbol("utils", {});
|
|
goog.exportSymbol("utils.formatNumberAsYouType", formatNumberAsYouType);
|
|
goog.exportSymbol("utils.formatNumber", formatNumber);
|
|
goog.exportSymbol("utils.getExampleNumber", getExampleNumber);
|
|
goog.exportSymbol("utils.getExtension", getExtension);
|
|
goog.exportSymbol("utils.getNumberType", getNumberType);
|
|
goog.exportSymbol("utils.getValidationError", getValidationError);
|
|
goog.exportSymbol("utils.isValidNumber", isValidNumber);
|
|
goog.exportSymbol("utils.isValidNumberPrecise", isValidNumberPrecise);
|
|
goog.exportSymbol("utils.getCoreNumber", getCoreNumber);
|