parse.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. "use strict";
  2. function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports["default"] = parse;
  7. var _constants = require("./constants.js");
  8. var _ParseError = _interopRequireDefault(require("./ParseError.js"));
  9. var _metadata = _interopRequireDefault(require("./metadata.js"));
  10. var _isViablePhoneNumber = _interopRequireWildcard(require("./helpers/isViablePhoneNumber.js"));
  11. var _extractExtension = _interopRequireDefault(require("./helpers/extension/extractExtension.js"));
  12. var _parseIncompletePhoneNumber = _interopRequireDefault(require("./parseIncompletePhoneNumber.js"));
  13. var _getCountryCallingCode = _interopRequireDefault(require("./getCountryCallingCode.js"));
  14. var _isPossible = require("./isPossible.js");
  15. var _PhoneNumber = _interopRequireDefault(require("./PhoneNumber.js"));
  16. var _matchesEntirely = _interopRequireDefault(require("./helpers/matchesEntirely.js"));
  17. var _extractCountryCallingCode = _interopRequireDefault(require("./helpers/extractCountryCallingCode.js"));
  18. var _extractNationalNumber = _interopRequireDefault(require("./helpers/extractNationalNumber.js"));
  19. var _stripIddPrefix = _interopRequireDefault(require("./helpers/stripIddPrefix.js"));
  20. var _getCountryByCallingCode = _interopRequireDefault(require("./helpers/getCountryByCallingCode.js"));
  21. var _extractFormattedPhoneNumberFromPossibleRfc3966NumberUri = _interopRequireDefault(require("./helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js"));
  22. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  23. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  24. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
  25. // This is a port of Google Android `libphonenumber`'s
  26. // `phonenumberutil.js` of December 31th, 2018.
  27. //
  28. // https://github.com/googlei18n/libphonenumber/commits/master/javascript/i18n/phonenumbers/phonenumberutil.js
  29. // import { parseRFC3966 } from './helpers/RFC3966.js'
  30. // We don't allow input strings for parsing to be longer than 250 chars.
  31. // This prevents malicious input from consuming CPU.
  32. var MAX_INPUT_STRING_LENGTH = 250; // This consists of the plus symbol, digits, and arabic-indic digits.
  33. var PHONE_NUMBER_START_PATTERN = new RegExp('[' + _constants.PLUS_CHARS + _constants.VALID_DIGITS + ']'); // Regular expression of trailing characters that we want to remove.
  34. // A trailing `#` is sometimes used when writing phone numbers with extensions in US.
  35. // Example: "+1 (645) 123 1234-910#" number has extension "910".
  36. var AFTER_PHONE_NUMBER_END_PATTERN = new RegExp('[^' + _constants.VALID_DIGITS + '#' + ']+$');
  37. var USE_NON_GEOGRAPHIC_COUNTRY_CODE = false; // Examples:
  38. //
  39. // ```js
  40. // parse('8 (800) 555-35-35', 'RU')
  41. // parse('8 (800) 555-35-35', 'RU', metadata)
  42. // parse('8 (800) 555-35-35', { country: { default: 'RU' } })
  43. // parse('8 (800) 555-35-35', { country: { default: 'RU' } }, metadata)
  44. // parse('+7 800 555 35 35')
  45. // parse('+7 800 555 35 35', metadata)
  46. // ```
  47. //
  48. /**
  49. * Parses a phone number.
  50. *
  51. * parse('123456789', { defaultCountry: 'RU', v2: true }, metadata)
  52. * parse('123456789', { defaultCountry: 'RU' }, metadata)
  53. * parse('123456789', undefined, metadata)
  54. *
  55. * @param {string} input
  56. * @param {object} [options]
  57. * @param {object} metadata
  58. * @return {object|PhoneNumber?} If `options.v2: true` flag is passed, it returns a `PhoneNumber?` instance. Otherwise, returns an object of shape `{ phone: '...', country: '...' }` (or just `{}` if no phone number was parsed).
  59. */
  60. function parse(text, options, metadata) {
  61. // If assigning the `{}` default value is moved to the arguments above,
  62. // code coverage would decrease for some weird reason.
  63. options = options || {};
  64. metadata = new _metadata["default"](metadata); // Validate `defaultCountry`.
  65. if (options.defaultCountry && !metadata.hasCountry(options.defaultCountry)) {
  66. if (options.v2) {
  67. throw new _ParseError["default"]('INVALID_COUNTRY');
  68. }
  69. throw new Error("Unknown country: ".concat(options.defaultCountry));
  70. } // Parse the phone number.
  71. var _parseInput = parseInput(text, options.v2, options.extract),
  72. formattedPhoneNumber = _parseInput.number,
  73. ext = _parseInput.ext,
  74. error = _parseInput.error; // If the phone number is not viable then return nothing.
  75. if (!formattedPhoneNumber) {
  76. if (options.v2) {
  77. if (error === 'TOO_SHORT') {
  78. throw new _ParseError["default"]('TOO_SHORT');
  79. }
  80. throw new _ParseError["default"]('NOT_A_NUMBER');
  81. }
  82. return {};
  83. }
  84. var _parsePhoneNumber = parsePhoneNumber(formattedPhoneNumber, options.defaultCountry, options.defaultCallingCode, metadata),
  85. country = _parsePhoneNumber.country,
  86. nationalNumber = _parsePhoneNumber.nationalNumber,
  87. countryCallingCode = _parsePhoneNumber.countryCallingCode,
  88. countryCallingCodeSource = _parsePhoneNumber.countryCallingCodeSource,
  89. carrierCode = _parsePhoneNumber.carrierCode;
  90. if (!metadata.hasSelectedNumberingPlan()) {
  91. if (options.v2) {
  92. throw new _ParseError["default"]('INVALID_COUNTRY');
  93. }
  94. return {};
  95. } // Validate national (significant) number length.
  96. if (!nationalNumber || nationalNumber.length < _constants.MIN_LENGTH_FOR_NSN) {
  97. // Won't throw here because the regexp already demands length > 1.
  98. /* istanbul ignore if */
  99. if (options.v2) {
  100. throw new _ParseError["default"]('TOO_SHORT');
  101. } // Google's demo just throws an error in this case.
  102. return {};
  103. } // Validate national (significant) number length.
  104. //
  105. // A sidenote:
  106. //
  107. // They say that sometimes national (significant) numbers
  108. // can be longer than `MAX_LENGTH_FOR_NSN` (e.g. in Germany).
  109. // https://github.com/googlei18n/libphonenumber/blob/7e1748645552da39c4e1ba731e47969d97bdb539/resources/phonenumber.proto#L36
  110. // Such numbers will just be discarded.
  111. //
  112. if (nationalNumber.length > _constants.MAX_LENGTH_FOR_NSN) {
  113. if (options.v2) {
  114. throw new _ParseError["default"]('TOO_LONG');
  115. } // Google's demo just throws an error in this case.
  116. return {};
  117. }
  118. if (options.v2) {
  119. var phoneNumber = new _PhoneNumber["default"](countryCallingCode, nationalNumber, metadata.metadata);
  120. if (country) {
  121. phoneNumber.country = country;
  122. }
  123. if (carrierCode) {
  124. phoneNumber.carrierCode = carrierCode;
  125. }
  126. if (ext) {
  127. phoneNumber.ext = ext;
  128. }
  129. phoneNumber.__countryCallingCodeSource = countryCallingCodeSource;
  130. return phoneNumber;
  131. } // Check if national phone number pattern matches the number.
  132. // National number pattern is different for each country,
  133. // even for those ones which are part of the "NANPA" group.
  134. var valid = (options.extended ? metadata.hasSelectedNumberingPlan() : country) ? (0, _matchesEntirely["default"])(nationalNumber, metadata.nationalNumberPattern()) : false;
  135. if (!options.extended) {
  136. return valid ? result(country, nationalNumber, ext) : {};
  137. } // isInternational: countryCallingCode !== undefined
  138. return {
  139. country: country,
  140. countryCallingCode: countryCallingCode,
  141. carrierCode: carrierCode,
  142. valid: valid,
  143. possible: valid ? true : options.extended === true && metadata.possibleLengths() && (0, _isPossible.isPossibleNumber)(nationalNumber, metadata) ? true : false,
  144. phone: nationalNumber,
  145. ext: ext
  146. };
  147. }
  148. /**
  149. * Extracts a formatted phone number from text.
  150. * Doesn't guarantee that the extracted phone number
  151. * is a valid phone number (for example, doesn't validate its length).
  152. * @param {string} text
  153. * @param {boolean} [extract] — If `false`, then will parse the entire `text` as a phone number.
  154. * @param {boolean} [throwOnError] — By default, it won't throw if the text is too long.
  155. * @return {string}
  156. * @example
  157. * // Returns "(213) 373-4253".
  158. * extractFormattedPhoneNumber("Call (213) 373-4253 for assistance.")
  159. */
  160. function _extractFormattedPhoneNumber(text, extract, throwOnError) {
  161. if (!text) {
  162. return;
  163. }
  164. if (text.length > MAX_INPUT_STRING_LENGTH) {
  165. if (throwOnError) {
  166. throw new _ParseError["default"]('TOO_LONG');
  167. }
  168. return;
  169. }
  170. if (extract === false) {
  171. return text;
  172. } // Attempt to extract a possible number from the string passed in
  173. var startsAt = text.search(PHONE_NUMBER_START_PATTERN);
  174. if (startsAt < 0) {
  175. return;
  176. }
  177. return text // Trim everything to the left of the phone number
  178. .slice(startsAt) // Remove trailing non-numerical characters
  179. .replace(AFTER_PHONE_NUMBER_END_PATTERN, '');
  180. }
  181. /**
  182. * @param {string} text - Input.
  183. * @param {boolean} v2 - Legacy API functions don't pass `v2: true` flag.
  184. * @param {boolean} [extract] - Whether to extract a phone number from `text`, or attempt to parse the entire text as a phone number.
  185. * @return {object} `{ ?number, ?ext }`.
  186. */
  187. function parseInput(text, v2, extract) {
  188. // // Parse RFC 3966 phone number URI.
  189. // if (text && text.indexOf('tel:') === 0) {
  190. // return parseRFC3966(text)
  191. // }
  192. // let number = extractFormattedPhoneNumber(text, extract, v2)
  193. var number = (0, _extractFormattedPhoneNumberFromPossibleRfc3966NumberUri["default"])(text, {
  194. extractFormattedPhoneNumber: function extractFormattedPhoneNumber(text) {
  195. return _extractFormattedPhoneNumber(text, extract, v2);
  196. }
  197. }); // If the phone number is not viable, then abort.
  198. if (!number) {
  199. return {};
  200. }
  201. if (!(0, _isViablePhoneNumber["default"])(number)) {
  202. if ((0, _isViablePhoneNumber.isViablePhoneNumberStart)(number)) {
  203. return {
  204. error: 'TOO_SHORT'
  205. };
  206. }
  207. return {};
  208. } // Attempt to parse extension first, since it doesn't require region-specific
  209. // data and we want to have the non-normalised number here.
  210. var withExtensionStripped = (0, _extractExtension["default"])(number);
  211. if (withExtensionStripped.ext) {
  212. return withExtensionStripped;
  213. }
  214. return {
  215. number: number
  216. };
  217. }
  218. /**
  219. * Creates `parse()` result object.
  220. */
  221. function result(country, nationalNumber, ext) {
  222. var result = {
  223. country: country,
  224. phone: nationalNumber
  225. };
  226. if (ext) {
  227. result.ext = ext;
  228. }
  229. return result;
  230. }
  231. /**
  232. * Parses a viable phone number.
  233. * @param {string} formattedPhoneNumber — Example: "(213) 373-4253".
  234. * @param {string} [defaultCountry]
  235. * @param {string} [defaultCallingCode]
  236. * @param {Metadata} metadata
  237. * @return {object} Returns `{ country: string?, countryCallingCode: string?, nationalNumber: string? }`.
  238. */
  239. function parsePhoneNumber(formattedPhoneNumber, defaultCountry, defaultCallingCode, metadata) {
  240. // Extract calling code from phone number.
  241. var _extractCountryCallin = (0, _extractCountryCallingCode["default"])((0, _parseIncompletePhoneNumber["default"])(formattedPhoneNumber), defaultCountry, defaultCallingCode, metadata.metadata),
  242. countryCallingCodeSource = _extractCountryCallin.countryCallingCodeSource,
  243. countryCallingCode = _extractCountryCallin.countryCallingCode,
  244. number = _extractCountryCallin.number; // Choose a country by `countryCallingCode`.
  245. var country;
  246. if (countryCallingCode) {
  247. metadata.selectNumberingPlan(countryCallingCode);
  248. } // If `formattedPhoneNumber` is passed in "national" format
  249. // then `number` is defined and `countryCallingCode` is `undefined`.
  250. else if (number && (defaultCountry || defaultCallingCode)) {
  251. metadata.selectNumberingPlan(defaultCountry, defaultCallingCode);
  252. if (defaultCountry) {
  253. country = defaultCountry;
  254. } else {
  255. /* istanbul ignore if */
  256. if (USE_NON_GEOGRAPHIC_COUNTRY_CODE) {
  257. if (metadata.isNonGeographicCallingCode(defaultCallingCode)) {
  258. country = '001';
  259. }
  260. }
  261. }
  262. countryCallingCode = defaultCallingCode || (0, _getCountryCallingCode["default"])(defaultCountry, metadata.metadata);
  263. } else return {};
  264. if (!number) {
  265. return {
  266. countryCallingCodeSource: countryCallingCodeSource,
  267. countryCallingCode: countryCallingCode
  268. };
  269. }
  270. var _extractNationalNumbe = (0, _extractNationalNumber["default"])((0, _parseIncompletePhoneNumber["default"])(number), metadata),
  271. nationalNumber = _extractNationalNumbe.nationalNumber,
  272. carrierCode = _extractNationalNumbe.carrierCode; // Sometimes there are several countries
  273. // corresponding to the same country phone code
  274. // (e.g. NANPA countries all having `1` country phone code).
  275. // Therefore, to reliably determine the exact country,
  276. // national (significant) number should have been parsed first.
  277. //
  278. // When `metadata.json` is generated, all "ambiguous" country phone codes
  279. // get their countries populated with the full set of
  280. // "phone number type" regular expressions.
  281. //
  282. var exactCountry = (0, _getCountryByCallingCode["default"])(countryCallingCode, {
  283. nationalNumber: nationalNumber,
  284. defaultCountry: defaultCountry,
  285. metadata: metadata
  286. });
  287. if (exactCountry) {
  288. country = exactCountry;
  289. /* istanbul ignore if */
  290. if (exactCountry === '001') {// Can't happen with `USE_NON_GEOGRAPHIC_COUNTRY_CODE` being `false`.
  291. // If `USE_NON_GEOGRAPHIC_COUNTRY_CODE` is set to `true` for some reason,
  292. // then remove the "istanbul ignore if".
  293. } else {
  294. metadata.country(country);
  295. }
  296. }
  297. return {
  298. country: country,
  299. countryCallingCode: countryCallingCode,
  300. countryCallingCodeSource: countryCallingCodeSource,
  301. nationalNumber: nationalNumber,
  302. carrierCode: carrierCode
  303. };
  304. }
  305. //# sourceMappingURL=parse.js.map