formatPhoneNumberForMobileDialing.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // This function is copy-pasted from
  2. // https://github.com/googlei18n/libphonenumber/blob/master/javascript/i18n/phonenumbers/phonenumberutil.js
  3. // It hasn't been tested. It's not currently exported.
  4. // Carriers codes aren't part of this library.
  5. // Send a PR if you want to add them.
  6. import Metadata from './metadata.js'
  7. import format from './format.js'
  8. import getNumberType from './helpers/getNumberType.js'
  9. import checkNumberLength from './helpers/checkNumberLength.js'
  10. import getCountryCallingCode from './getCountryCallingCode.js'
  11. const REGION_CODE_FOR_NON_GEO_ENTITY = '001'
  12. /**
  13. * Returns a number formatted in such a way that it can be dialed from a mobile
  14. * phone in a specific region. If the number cannot be reached from the region
  15. * (e.g. some countries block toll-free numbers from being called outside of the
  16. * country), the method returns an empty string.
  17. *
  18. * @param {object} number - a `parse()`d phone number to be formatted.
  19. * @param {string} from_country - the region where the call is being placed.
  20. * @param {boolean} with_formatting - whether the number should be returned with
  21. * formatting symbols, such as spaces and dashes.
  22. * @return {string}
  23. */
  24. export default function(number, from_country, with_formatting, metadata) {
  25. metadata = new Metadata(metadata)
  26. // Validate `from_country`.
  27. if (!metadata.hasCountry(from_country)) {
  28. throw new Error(`Unknown country: ${from_country}`)
  29. }
  30. // Not using the extension, as that part cannot normally be dialed
  31. // together with the main number.
  32. number = {
  33. phone: number.phone,
  34. country: number.country
  35. }
  36. const number_type = getNumberType(number, undefined, metadata.metadata)
  37. const is_valid_number = number_type === number
  38. let formatted_number
  39. if (country === from_country) {
  40. const is_fixed_line_or_mobile =
  41. number_type === 'FIXED_LINE' ||
  42. number_type === 'MOBILE' ||
  43. number_type === 'FIXED_LINE_OR_MOBILE'
  44. // Carrier codes may be needed in some countries. We handle this here.
  45. if (country == 'BR' && is_fixed_line_or_mobile) {
  46. formatted_number =
  47. carrierCode ?
  48. formatNationalNumberWithPreferredCarrierCode(number) :
  49. // Brazilian fixed line and mobile numbers need to be dialed with a
  50. // carrier code when called within Brazil. Without that, most of the
  51. // carriers won't connect the call. Because of that, we return an
  52. // empty string here.
  53. ''
  54. } else if (getCountryCallingCode(country, metadata.metadata) === '1') {
  55. // For NANPA countries, we output international format for numbers that
  56. // can be dialed internationally, since that always works, except for
  57. // numbers which might potentially be short numbers, which are always
  58. // dialled in national format.
  59. // Select country for `checkNumberLength()`.
  60. metadata.country(country)
  61. if (can_be_internationally_dialled(number) &&
  62. checkNumberLength(number.phone, metadata) !== 'TOO_SHORT') {
  63. formatted_number = format(number, 'INTERNATIONAL', metadata.metadata)
  64. }
  65. else {
  66. formatted_number = format(number, 'NATIONAL', metadata.metadata)
  67. }
  68. }
  69. else {
  70. // For non-geographic countries, Mexican and Chilean fixed line and
  71. // mobile numbers, we output international format for numbers that can be
  72. // dialed internationally, as that always works.
  73. if (
  74. (
  75. country === REGION_CODE_FOR_NON_GEO_ENTITY
  76. ||
  77. // MX fixed line and mobile numbers should always be formatted in
  78. // international format, even when dialed within MX. For national
  79. // format to work, a carrier code needs to be used, and the correct
  80. // carrier code depends on if the caller and callee are from the
  81. // same local area. It is trickier to get that to work correctly than
  82. // using international format, which is tested to work fine on all
  83. // carriers.
  84. //
  85. // CL fixed line numbers need the national prefix when dialing in the
  86. // national format, but don't have it when used for display. The
  87. // reverse is true for mobile numbers. As a result, we output them in
  88. // the international format to make it work.
  89. //
  90. // UZ mobile and fixed-line numbers have to be formatted in
  91. // international format or prefixed with special codes like 03, 04
  92. // (for fixed-line) and 05 (for mobile) for dialling successfully
  93. // from mobile devices. As we do not have complete information on
  94. // special codes and to be consistent with formatting across all
  95. // phone types we return the number in international format here.
  96. //
  97. ((country === 'MX' || country === 'CL' || country == 'UZ') && is_fixed_line_or_mobile)
  98. )
  99. &&
  100. can_be_internationally_dialled(number)
  101. ) {
  102. formatted_number = format(number, 'INTERNATIONAL')
  103. }
  104. else {
  105. formatted_number = format(number, 'NATIONAL')
  106. }
  107. }
  108. }
  109. else if (is_valid_number && can_be_internationally_dialled(number)) {
  110. // We assume that short numbers are not diallable from outside their region,
  111. // so if a number is not a valid regular length phone number, we treat it as
  112. // if it cannot be internationally dialled.
  113. return with_formatting ?
  114. format(number, 'INTERNATIONAL', metadata.metadata) :
  115. format(number, 'E.164', metadata.metadata)
  116. }
  117. if (!with_formatting) {
  118. return diallable_chars(formatted_number)
  119. }
  120. return formatted_number
  121. }
  122. function can_be_internationally_dialled(number) {
  123. return true
  124. }
  125. /**
  126. * A map that contains characters that are essential when dialling. That means
  127. * any of the characters in this map must not be removed from a number when
  128. * dialling, otherwise the call will not reach the intended destination.
  129. */
  130. const DIALLABLE_CHARACTERS = {
  131. '0': '0',
  132. '1': '1',
  133. '2': '2',
  134. '3': '3',
  135. '4': '4',
  136. '5': '5',
  137. '6': '6',
  138. '7': '7',
  139. '8': '8',
  140. '9': '9',
  141. '+': '+',
  142. '*': '*',
  143. '#': '#'
  144. }
  145. function diallable_chars(formatted_number) {
  146. let result = ''
  147. let i = 0
  148. while (i < formatted_number.length) {
  149. const character = formatted_number[i]
  150. if (DIALLABLE_CHARACTERS[character]) {
  151. result += character
  152. }
  153. i++
  154. }
  155. return result
  156. }
  157. function getPreferredDomesticCarrierCodeOrDefault() {
  158. throw new Error('carrier codes are not part of this library')
  159. }
  160. function formatNationalNumberWithCarrierCode() {
  161. throw new Error('carrier codes are not part of this library')
  162. }
  163. /**
  164. * Formats a phone number in national format for dialing using the carrier as
  165. * specified in the preferred_domestic_carrier_code field of the PhoneNumber
  166. * object passed in. If that is missing, use the {@code fallbackCarrierCode}
  167. * passed in instead. If there is no {@code preferred_domestic_carrier_code},
  168. * and the {@code fallbackCarrierCode} contains an empty string, return the
  169. * number in national format without any carrier code.
  170. *
  171. * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier
  172. * code passed in should take precedence over the number's
  173. * {@code preferred_domestic_carrier_code} when formatting.
  174. *
  175. * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  176. * formatted.
  177. * @param {string} fallbackCarrierCode the carrier selection code to be used, if
  178. * none is found in the phone number itself.
  179. * @return {string} the formatted phone number in national format for dialing
  180. * using the number's preferred_domestic_carrier_code, or the
  181. * {@code fallbackCarrierCode} passed in if none is found.
  182. */
  183. function formatNationalNumberWithPreferredCarrierCode(number) {
  184. return formatNationalNumberWithCarrierCode(
  185. number,
  186. carrierCode
  187. );
  188. }