mozambique-utils
Version:
Easy to use parsers and validators for mozambican (🇲🇿) data-type formatted strings.
584 lines (563 loc) • 19.9 kB
JavaScript
/**
* The MIT License (MIT)
*
* Copyright (c) 2021 Kishan Jadav
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var isString = require('lodash/isString');
var isEmpty = require('lodash/isEmpty');
var get = require('lodash/get');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var isString__default = /*#__PURE__*/_interopDefaultLegacy(isString);
var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
var get__default = /*#__PURE__*/_interopDefaultLegacy(get);
var version = "1.1.1";
/**
* Checks if `value` is of type string
*
* @param {unknwon} value The value to check.
* @returns {boolean} true if string otherwise false.
*/
var assert_string = function (value) {
if (!isString__default['default'](value)) {
throw new Error("Expected a string");
}
};
/* ------------------------------- Number Validation Patterns ------------------------------ */
var NDC_MOBILE_SUFFIX = {
all: "234567",
tmcel: "23",
movitel: "67",
vodacom: "45", // 8+vodacom_suffix - E.g: 84 85
};
var NDC_LANDLINE = {
tete: "252",
pemba: "272",
beira: "23",
chokwe: "281",
manica: "251",
xaixai: "282",
maputo: "21",
nampula: "26",
lichinga: "271",
inhambane: "293",
quelimane: "24",
vilanculos: "258"
};
/**
* Matches any mozambican mobile phone number from any carrier/operator.
* E.g: 841122434 or +258841122434 or 258841122434 or even 00258841122434
*
* @constant
* @type {RegExp}
*/
var MobilePattern = /^((\+|00)?258)?((8[234567])\d{7})$/;
/**
* Matches any mozambican landline phone number from the incumbent fixed operator (Tmcel)
* E.g: 21351100 or +25821351100 or 25821351100 or 0025821351100
*
* @constant
* @type {RegExp}
*/
var LandlinePattern = /^((\+|00)?258)?((2(5[0128]|7[12]|8[12]|93))(\d{5})|(2[1346])(\d{6}))$/;
/**
* Matches mozambican landline NDC of the incumbent fixed operator phone number (Tmcel)
* E.g: 21 or 293 or 24 or 252, etc..
*
* @see {@link https://www.itu.int/dms_pub/itu-t/oth/02/02/T02020000910003PDFE.pdf} (page 5) for a full list of available NDC
* @constant
* @type {RegExp}
*/
var LandlineNDCPattern = /^(2(5[0128]|7[12]|8[12]|93)|2[1346])$/;
/**
* Matches Vodacom's mobile National Destination Code.
* E.g: 84 or 85
*
* @constant
* @type {RegExp}
*/
var VodacomNDCPattern = /^8[45]$/;
/**
* Matches Tmcel mobile National Destination Code.
* E.g: 82 or 83
*
* @constant
* @type {RegExp}
*/
var TmcelNDCPattern = /^8[23]$/;
/**
* Matches Movitel mobile National Destination Code.
* E.g: 86 or 87
*
* @constant
* @type {RegExp}
*/
var MovitelNDCPattern = /^8[67]$/;
/* ------------------------------- NUIT validation patterns ------------------------------ */
/**
* Matches mozambican Número Único de Identificação Tributária (NUIT)
* - NUIT has 9 digits.
*/
var NUITPattern = /^\d{9}$/;
/* ------------------------ IBAN validation patterns ------------------------ */
var IBANPattern = /^(MZ[0-9]{2})\d{21}$/i;
/**
* Get the network operator from any mozambican valid `ndc` (National Destination Code),
* which can be, for example, 21, 293, 84, 85, 86, etc...
*
* @param ndc The national destination code (E.g: 82, 84 or 293) to check for.
* @throws if the provided ndc is not of type string
* @returns {NetworkOperatorSpec} network operator information.
*/
var ndc_to_operator = function (ndc) {
assert_string(ndc);
if (VodacomNDCPattern.test(ndc)) {
return {
name: "Vodacom Moçambique",
shortName: "Vodacom"
};
}
else if (MovitelNDCPattern.test(ndc)) {
return {
name: "Movitel, S.A.",
shortName: "Movitel"
};
}
else if (TmcelNDCPattern.test(ndc) || LandlineNDCPattern.test(ndc)) {
return {
name: "Moçambique Telecom, S.A.",
shortName: "Tmcel"
};
}
else {
return null;
}
};
/**
* Sanitise a string by removing all whitespaces, dashes and commas.
* @param str The string to sanitise
* @returns {string} a sanitised string.
*/
var sanitise_string = function (str) {
if (str) {
str = str.replace(/[\s-]/g, ""); // remove all whitespaces, dash and commas
}
return str;
};
/**
* Validates any mozambican mobile number.
*
* @param {string} num The mobile number to check for.
* @returns {MobileNumberValidationResult} an object telling if the provided number str is valid or not. And if valid, some extra details about that number, otherwise null.
*
* @throws if the provided argument is not of type string.
*/
var parseMobileNumber = function (num) {
// Assert string
assert_string(num);
// Sanitise the string:
num = sanitise_string(num);
/**
* Is the mobile number a valid mozambican one?
* @type {boolean}
*/
var valid = false;
/**
* More information related to the landline number when it is valid.
* @type {MobileNumberSpec | null}
*/
var data = null;
// The check through regex of the provided number.
var matched = num.match(MobilePattern);
if (matched) {
// Is valid.
valid = true;
/**
* The requested number
* @type {string}
*/
var number = num;
/**
* Does the requested number include the country code with or without the international access prefix?
* Does it include either 258, +258, 00258
* @type {boolean}
*/
var includesCountryCode = matched[1] !== undefined;
/**
* The provided number in local format.
* This is also refered as the National (Significant) Number or NSN for short.
*
* It the 9 digit phone number (including the NDC) that people can use to call others locally,
* without the need of using the international access prefix (+|00) and/or the country code (258).
* @type {string}
*/
var localFormat = matched[3];
/**
* The provided number formatted to use for international calls.
* This is the `localFormat` with the country code prefixed to it.
* @type {string}
*/
var internationalFormat = "+258" + localFormat;
/**
* National destination code are the leading digits of the National (Significant) Number.
* E.g: 82, 84, 86, etc..
* @type {string}
*/
var nationalDestinationCode = matched[4];
/**
* The network operator whose NDC is assigned to.
* @type {NetworkOperatorSpec | null}
*/
var operator = ndc_to_operator(nationalDestinationCode);
/**
* The type of line the phone number is of.
* Will always be "mobile" for this method.
*
* @constant
* @type {LineType}
*/
var lineType = "mobile";
data = {
number: number,
localFormat: localFormat,
internationalFormat: internationalFormat,
includesCountryCode: includesCountryCode,
nationalDestinationCode: nationalDestinationCode,
operator: operator,
lineType: lineType,
};
}
// Return the result of the validation
return {
valid: valid,
data: data
};
};
/**
* A list of area codes in mozambique
*
* @see {@link https://www.itu.int/dms_pub/itu-t/oth/02/02/T02020000910003PDFE.pdf}
* @see {@link https://en.wikipedia.org/wiki/Telephone_numbers_in_Mozambique}
*/
var regions = {
"21": "maputo",
"23": "beira",
"24": "quelimane",
"26": "nampula",
"258": "vilanculos",
"251": "manica",
"252": "tete",
"271": "lichinga",
"272": "pemba",
"281": "chokwe",
"282": "xai-xai",
"293": "inhambane"
};
/**
* Get the landline region for a given mozambican National Destination Code | Area code.
*
* @param ndc a valid national destination code | area code of a mozambican landline number.
* @returns the region name (city) of the given ndc. Or null if an invalid ndc is given.
*/
var ndc_to_landline_region = function (ndc) {
assert_string(ndc);
if (LandlineNDCPattern.test(ndc)) {
return regions[ndc];
}
return null;
};
/**
* Validates any mozambican landline number
* (in pt we call telefone fixo)
*
* @param num The landline number to check.
*
* @returns a result object containing if the requested `num` is valid or not and some extra information.
* @throws if the provided argument is not of type string.
*/
var parseLandlineNumber = function (num) {
// Assert string
assert_string(num);
// Sanitise the number
num = sanitise_string(num);
/**
* Is the landline number a valid mozambican one?
* @type {boolean}
*/
var valid = false;
/**
* More information related to the landline number when it is valid.
* @type {LandlineNumberSpec | null}
*/
var data = null;
// The check through regex of the provided number.
var matched = num.match(LandlinePattern);
if (matched) {
// Is valid.
valid = true;
/**
* The requested number
* @type {string}
*/
var number = num;
/**
* Does the requested number include the country code with or without the international access prefix?
* Does it include either 258, +258, 00258
* @type {boolean}
*/
var includesCountryCode = matched[1] !== undefined;
/**
* The provided number in local format.
* This is also refered as the National (Significant) Number or NSN for short.
*
* It the 8 digit phone number (including the NDC) that people can use to call others locally,
* without the need of using the international access prefix (+|00) and/or the country code (258).
* @type {string}
*/
var localFormat = matched[3];
/**
* The provided number formatted to use for international calls.
* This is the `localFormat` with the country code prefixed to it.
* @type {string}
*/
var internationalFormat = "+258" + localFormat;
/**
* Also refered as the Area code. E.g: 21, 293, etc..
* These are the leading digits of the National (Significant) Number.
* @type {string}
*/
// Note: matched[4] is returned when the ndc has 3 digits, otherwise the 2 digit one can be found on matched[7]
var nationalDestinationCode = matched[4] ? matched[4] : matched[7];
/**
* The city where the `nationalDestinationCode` aka Area code, is of.
* @type {string}
*/
var region = ndc_to_landline_region(nationalDestinationCode);
/**
* The incumbent fixed operator (Tmcel)
* @type {NetworkOperatorSpec | null}
*/
var operator = ndc_to_operator(nationalDestinationCode); // the network operator.
/**
* The type of line the phone number is of.
* Will always be landline for this method.
* @constant
* @type {LineType}
*/
var lineType = "landline";
data = {
number: number,
localFormat: localFormat,
internationalFormat: internationalFormat,
includesCountryCode: includesCountryCode,
nationalDestinationCode: nationalDestinationCode,
operator: operator,
region: region,
lineType: lineType,
};
}
return {
valid: valid,
data: data
};
};
/**
* Get a customized mobile number regex pattern
*
* @param countryCode If the country code should by
* @param operators a string of operators to allow. Provide an empty array for all operators.
* @returns the customized regex that can be used to test a number.
*/
var get_mobile_number_pattern = function (countryCodeOpt, operators) {
var countryCodePreffix;
switch (countryCodeOpt) {
case "required":
// Must have a country code.
countryCodePreffix = "((\\+|00)?258)";
break;
case "off":
// Must not have country code in it.
countryCodePreffix = "()";
break;
case "optional":
default:
// Optional. Can have a country code or not!
countryCodePreffix = "((\\+|00)?258)?";
break;
}
var ndcSuffix;
if (!isEmpty__default['default'](operators)) {
// For a list of operators
operators.forEach(function (operator) {
var sfx = get__default['default'](NDC_MOBILE_SUFFIX, operator);
if (sfx) {
ndcSuffix += sfx;
}
});
}
else {
// For all operators by default
ndcSuffix = NDC_MOBILE_SUFFIX.all;
}
return new RegExp("^" + countryCodePreffix + "((8[" + ndcSuffix + "])\\d{7})$");
};
/**
* Returns whether the given mozambican mobile number is valid or not.
*
* @param {string} num the number to check
* @returns true if valid, otherwise false.
*/
var isMobileNumberValid = function (num, options) {
// Assert argument is string.
assert_string(num);
// Sanitise the number:
num = sanitise_string(num);
// Parse the options
options = options || {};
// Defaults
var allowedOperators = options.allowedOperators || [];
var countryCode = options.countryCode || "optional";
var patt = get_mobile_number_pattern(countryCode, allowedOperators);
return patt.test(num);
};
/**
* Check if a mozambican Número Único de Identificação Tributária (NUIT)
* is valid or not.
* For it to be valid, it must include 9 digits.
*
* @param {string} nuit the NUIT to check
* @returns {boolean} true if valid, otherwise false.
*/
var isNUITValid = function (nuit) {
// Assert type string
assert_string(nuit);
// Sanitise nuit
nuit = sanitise_string(nuit);
if (nuit) {
return NUITPattern.test(nuit);
}
return false;
};
/**
* Checks whether a mozambican International Bank Account Number (IBAN)
* is valid through ISO 13616-compliant national IBAN format.
*
* Check constraints includes:
* - Starts with the country code MZ
* - Length of 25
* - Applies the check character system MOD 97-10 (see ISO 7064) - (hint: should be 1)!
*
* @see https://www.iso.org/standard/81090.html ISO-13616 Structure of the IBAN
* @see https://www.iso.org/standard/31531.html ISO-7064 IT - Security Techniques - Check character systems
*
* @param {string} iban the mozambican IBAN to check.
* @returns {boolean} true if valid, otherwise false.
*/
var isIBANValid = function (iban) {
// Assert type string
assert_string(iban);
// Sanitise the iban
iban = sanitise_string(iban);
iban = iban.toLowerCase();
if (iban) {
// Make sure it has a length of 25 (including the two trailing country code and all)
var isPatternValid = IBANPattern.test(iban);
if (!isPatternValid)
return false;
// Check digits based on the scheme defined in ISO-7064 (MOD 97).
// Move the first four chars to the right end of the iban
var displaced = iban.substring(4, iban.length) + iban.substring(0, 4);
// Convert the alpha characters to numeric characters in accordance to 6.2.3 of ISO-1316-1.
var MZ_DIGITS = "2235";
var numeric = displaced.replace("mz", MZ_DIGITS);
// Calculate the modulo 97 (the remainder after division by 97) - should be 1!
var remainder = Array.from(numeric).map(function (c) { return parseInt(c); }).reduce(function (remainder, value) { return (remainder * 10 + value) % 97; }, 0);
return remainder === 1;
}
return false;
};
var get_landline_number_pattern = function (countryCodeOpt, regions) {
// Constant
var MAXIMUM_NSN_DIGITS = 8; // maximum national signficant number digits for landlines in mozambique.
var countryCodePreffix;
switch (countryCodeOpt) {
case "required":
// Must have a country code
countryCodePreffix = "((\\+|00)?258)";
break;
case "off":
// Must have a country code
countryCodePreffix = "()";
break;
case "optional":
default:
// Optional. Can have a country code or not!
countryCodePreffix = "((\\+|00)?258)?";
break;
}
var numPattern = "";
if (!isEmpty__default['default'](regions)) {
// For a list of regions
numPattern += "(";
regions.forEach(function (region, index) {
var ndcForRegion = get__default['default'](NDC_LANDLINE, region);
var nsnLength = MAXIMUM_NSN_DIGITS - ndcForRegion.length;
numPattern += "(" + ndcForRegion + ")(\\d{" + nsnLength + "})";
var isLastRegionOnList = ((regions.length - 1) === index);
if (!isLastRegionOnList) {
numPattern += "|";
}
});
numPattern += ")";
}
else {
// for all regions
numPattern = "((2(5[0128]|7[12]|8[12]|93))(\\d{5})|(2[1346])(\\d{6}))";
}
return new RegExp("^" + countryCodePreffix + numPattern + "$");
};
/**
* Returns whether the given mozambican landline number is valid or not.
*
* @param {string} num the number to check
* @returns true if valid, otherwise false.
*/
var isLandlineNumberValid = function (num, options) {
// Assert argument is string.
assert_string(num);
// Sanitise the number:
num = sanitise_string(num);
// Parse the options
options = options || {};
// Defaults
var allowedRegions = options.allowedRegions || [];
var countryCode = options.countryCode || "optional";
var patt = get_landline_number_pattern(countryCode, allowedRegions);
return patt.test(num);
};
exports.isIBANValid = isIBANValid;
exports.isLandlineNumberValid = isLandlineNumberValid;
exports.isMobileNumberValid = isMobileNumberValid;
exports.isNUITValid = isNUITValid;
exports.parseLandlineNumber = parseLandlineNumber;
exports.parseMobileNumber = parseMobileNumber;
exports.version = version;