extractCountryCallingCode.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import stripIddPrefix from './stripIddPrefix.js'
  2. import extractCountryCallingCodeFromInternationalNumberWithoutPlusSign from './extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js'
  3. import Metadata from '../metadata.js'
  4. import { MAX_LENGTH_COUNTRY_CODE } from '../constants.js'
  5. /**
  6. * Converts a phone number digits (possibly with a `+`)
  7. * into a calling code and the rest phone number digits.
  8. * The "rest phone number digits" could include
  9. * a national prefix, carrier code, and national
  10. * (significant) number.
  11. * @param {string} number — Phone number digits (possibly with a `+`).
  12. * @param {string} [country] — Default country.
  13. * @param {string} [callingCode] — Default calling code (some phone numbering plans are non-geographic).
  14. * @param {object} metadata
  15. * @return {object} `{ countryCallingCodeSource: string?, countryCallingCode: string?, number: string }`
  16. * @example
  17. * // Returns `{ countryCallingCode: "1", number: "2133734253" }`.
  18. * extractCountryCallingCode('2133734253', 'US', null, metadata)
  19. * extractCountryCallingCode('2133734253', null, '1', metadata)
  20. * extractCountryCallingCode('+12133734253', null, null, metadata)
  21. * extractCountryCallingCode('+12133734253', 'RU', null, metadata)
  22. */
  23. export default function extractCountryCallingCode(
  24. number,
  25. country,
  26. callingCode,
  27. metadata
  28. ) {
  29. if (!number) {
  30. return {}
  31. }
  32. let isNumberWithIddPrefix
  33. // If this is not an international phone number,
  34. // then either extract an "IDD" prefix, or extract a
  35. // country calling code from a number by autocorrecting it
  36. // by prepending a leading `+` in cases when it starts
  37. // with the country calling code.
  38. // https://wikitravel.org/en/International_dialling_prefix
  39. // https://github.com/catamphetamine/libphonenumber-js/issues/376
  40. if (number[0] !== '+') {
  41. // Convert an "out-of-country" dialing phone number
  42. // to a proper international phone number.
  43. const numberWithoutIDD = stripIddPrefix(number, country, callingCode, metadata)
  44. // If an IDD prefix was stripped then
  45. // convert the number to international one
  46. // for subsequent parsing.
  47. if (numberWithoutIDD && numberWithoutIDD !== number) {
  48. isNumberWithIddPrefix = true
  49. number = '+' + numberWithoutIDD
  50. } else {
  51. // Check to see if the number starts with the country calling code
  52. // for the default country. If so, we remove the country calling code,
  53. // and do some checks on the validity of the number before and after.
  54. // https://github.com/catamphetamine/libphonenumber-js/issues/376
  55. if (country || callingCode) {
  56. const {
  57. countryCallingCode,
  58. number: shorterNumber
  59. } = extractCountryCallingCodeFromInternationalNumberWithoutPlusSign(
  60. number,
  61. country,
  62. callingCode,
  63. metadata
  64. )
  65. if (countryCallingCode) {
  66. return {
  67. countryCallingCodeSource: 'FROM_NUMBER_WITHOUT_PLUS_SIGN',
  68. countryCallingCode,
  69. number: shorterNumber
  70. }
  71. }
  72. }
  73. return {
  74. // No need to set it to `UNSPECIFIED`. It can be just `undefined`.
  75. // countryCallingCodeSource: 'UNSPECIFIED',
  76. number
  77. }
  78. }
  79. }
  80. // Fast abortion: country codes do not begin with a '0'
  81. if (number[1] === '0') {
  82. return {}
  83. }
  84. metadata = new Metadata(metadata)
  85. // The thing with country phone codes
  86. // is that they are orthogonal to each other
  87. // i.e. there's no such country phone code A
  88. // for which country phone code B exists
  89. // where B starts with A.
  90. // Therefore, while scanning digits,
  91. // if a valid country code is found,
  92. // that means that it is the country code.
  93. //
  94. let i = 2
  95. while (i - 1 <= MAX_LENGTH_COUNTRY_CODE && i <= number.length) {
  96. const countryCallingCode = number.slice(1, i)
  97. if (metadata.hasCallingCode(countryCallingCode)) {
  98. metadata.selectNumberingPlan(countryCallingCode)
  99. return {
  100. countryCallingCodeSource: isNumberWithIddPrefix ? 'FROM_NUMBER_WITH_IDD' : 'FROM_NUMBER_WITH_PLUS_SIGN',
  101. countryCallingCode,
  102. number: number.slice(i)
  103. }
  104. }
  105. i++
  106. }
  107. return {}
  108. }
  109. // The possible values for the returned `countryCallingCodeSource` are:
  110. //
  111. // Copy-pasted from:
  112. // https://github.com/google/libphonenumber/blob/master/resources/phonenumber.proto
  113. //
  114. // // The source from which the country_code is derived. This is not set in the
  115. // // general parsing method, but in the method that parses and keeps raw_input.
  116. // // New fields could be added upon request.
  117. // enum CountryCodeSource {
  118. // // Default value returned if this is not set, because the phone number was
  119. // // created using parse, not parseAndKeepRawInput. hasCountryCodeSource will
  120. // // return false if this is the case.
  121. // UNSPECIFIED = 0;
  122. //
  123. // // The country_code is derived based on a phone number with a leading "+",
  124. // // e.g. the French number "+33 1 42 68 53 00".
  125. // FROM_NUMBER_WITH_PLUS_SIGN = 1;
  126. //
  127. // // The country_code is derived based on a phone number with a leading IDD,
  128. // // e.g. the French number "011 33 1 42 68 53 00", as it is dialled from US.
  129. // FROM_NUMBER_WITH_IDD = 5;
  130. //
  131. // // The country_code is derived based on a phone number without a leading
  132. // // "+", e.g. the French number "33 1 42 68 53 00" when defaultCountry is
  133. // // supplied as France.
  134. // FROM_NUMBER_WITHOUT_PLUS_SIGN = 10;
  135. //
  136. // // The country_code is derived NOT based on the phone number itself, but
  137. // // from the defaultCountry parameter provided in the parsing function by the
  138. // // clients. This happens mostly for numbers written in the national format
  139. // // (without country code). For example, this would be set when parsing the
  140. // // French number "01 42 68 53 00", when defaultCountry is supplied as
  141. // // France.
  142. // FROM_DEFAULT_COUNTRY = 20;
  143. // }