UNPKG

mozambique-utils

Version:

Easy to use parsers and validators for mozambican (🇲🇿) data-type formatted strings.

584 lines (563 loc) • 19.9 kB
/** * 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;