1/** 2 * @license 3 * Copyright (C) 2018 The Libphonenumber Authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18/** 19 * @fileoverview Utility for international phone numbers. 20 * Functionality includes formatting, parsing and validation. 21 * (based on the java implementation). 22 * 23 * NOTE: A lot of methods in this class require Region Code strings. These must 24 * be provided using CLDR two-letter region-code format. These should be in 25 * upper-case. The list of the codes can be found here: 26 * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html 27 * 28 * @author James Wright 29 */ 30 31goog.provide('i18n.phonenumbers.ShortNumberInfo'); 32 33goog.require('goog.proto2.PbLiteSerializer'); 34goog.require('i18n.phonenumbers.PhoneMetadata'); 35goog.require('i18n.phonenumbers.PhoneNumber'); 36goog.require('i18n.phonenumbers.PhoneNumberDesc'); 37goog.require('i18n.phonenumbers.PhoneNumberUtil'); 38goog.require('i18n.phonenumbers.metadata'); 39goog.require('i18n.phonenumbers.shortnumbermetadata'); 40 41 42 43/** 44 * @constructor 45 * @private 46 */ 47i18n.phonenumbers.ShortNumberInfo = function() { 48 /** 49 * A mapping from region code to the short-number metadata for that region. 50 * @type {Object.<string, i18n.phonenumbers.PhoneMetadata>} 51 */ 52 this.regionToMetadataMap = {}; 53}; 54goog.addSingletonGetter(i18n.phonenumbers.ShortNumberInfo); 55 56 57/** 58 * In these countries, if extra digits are added to an emergency number, it no 59 * longer connects to the emergency service. 60 * @const 61 * @type {!Array<string>} 62 * @private 63 */ 64i18n.phonenumbers.ShortNumberInfo. 65 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_ = [ 66 'BR', 67 'CL', 68 'NI' 69 ]; 70 71 72/** 73 * @enum {number} Cost categories of short numbers. 74 */ 75i18n.phonenumbers.ShortNumberInfo.ShortNumberCost = { 76 TOLL_FREE: 0, 77 STANDARD_RATE: 1, 78 PREMIUM_RATE: 2, 79 UNKNOWN_COST: 3 80}; 81 82 83/** 84 * Returns a list with the region codes that match the specific country calling 85 * code. For non-geographical country calling codes, the region code 001 is 86 * returned. Also, in the case of no region code being found, an empty list 87 * is returned. 88 * @param {number} countryCallingCode 89 * @return {!Array<string>} The region codes that match the given country code. 90 * @private 91 */ 92i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodesForCountryCode_ = 93 function(countryCallingCode) { 94 var regionCodes = i18n.phonenumbers.metadata 95 .countryCodeToRegionCodeMap[countryCallingCode]; 96 return regionCodes ? regionCodes : []; 97}; 98 99 100/** 101 * Helper method to check that the country calling code of the number matches 102 * the region it's being dialed from. 103 * @param {i18n.phonenumbers.PhoneNumber} number 104 * @param {?string} regionDialingFrom 105 * @return {boolean} 106 * @private 107 */ 108i18n.phonenumbers.ShortNumberInfo.prototype.regionDialingFromMatchesNumber_ = 109 function(number, regionDialingFrom) { 110 var regionCodes = this.getRegionCodesForCountryCode_( 111 number.getCountryCodeOrDefault()); 112 return regionDialingFrom != null && regionCodes.includes(regionDialingFrom); 113}; 114 115 116/** 117 * Check whether a short number is a possible number when dialed from the given 118 * region. This provides a more lenient check than 119 * {@link #isValidShortNumberForRegion}. 120 * 121 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check 122 * @param {string} regionDialingFrom the region from which the number is dialed 123 * @return {boolean} whether the number is a possible short number 124 */ 125i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumberForRegion = 126 function(number, regionDialingFrom) { 127 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 128 return false; 129 } 130 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 131 if (!phoneMetadata) { 132 return false; 133 } 134 var numberLength = this.getNationalSignificantNumber_(number).length; 135 return phoneMetadata.getGeneralDesc().possibleLengthArray().includes( 136 numberLength); 137}; 138 139 140/** 141 * Check whether a short number is a possible number. If a country calling code 142 * is shared by multiple regions, this returns true if it's possible in any of 143 * them. This provides a more lenient check than {@link #isValidShortNumber}. 144 * See {@link #isPossibleShortNumberForRegion(PhoneNumber, String)} for details. 145 * 146 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check 147 * @return {boolean} whether the number is a possible short number 148 */ 149i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumber = 150 function(number) { 151 var regionCodes = this.getRegionCodesForCountryCode_( 152 number.getCountryCodeOrDefault()); 153 var shortNumberLength = this.getNationalSignificantNumber_(number).length; 154 for (var i = 0; i < regionCodes.length; i++) { 155 var region = regionCodes[i]; 156 var phoneMetadata = this.getMetadataForRegion_(region); 157 if (!phoneMetadata) { 158 continue; 159 } 160 var possibleLengths = phoneMetadata.getGeneralDesc().possibleLengthArray(); 161 if (possibleLengths.includes(shortNumberLength)) { 162 return true; 163 } 164 } 165 return false; 166}; 167 168 169/** 170 * Tests whether a short number matches a valid pattern in a region. Note that 171 * this doesn't verify the number is actually in use, which is impossible to 172 * tell by just looking at the number itself. 173 * 174 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 175 * want to test the validity 176 * @param {?string} regionDialingFrom the region from which the number is dialed 177 * @return {boolean} whether the short number matches a valid pattern 178 */ 179i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumberForRegion = 180 function(number, regionDialingFrom) { 181 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 182 return false; 183 } 184 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 185 if (!phoneMetadata) { 186 return false; 187 } 188 var shortNumber = this.getNationalSignificantNumber_(number); 189 var generalDesc = phoneMetadata.getGeneralDesc(); 190 if (!this.matchesPossibleNumberAndNationalNumber_(shortNumber, generalDesc)) { 191 return false; 192 } 193 var shortNumberDesc = phoneMetadata.getShortCode(); 194 return this.matchesPossibleNumberAndNationalNumber_(shortNumber, 195 shortNumberDesc); 196}; 197 198 199/** 200 * Tests whether a short number matches a valid pattern. If a country calling 201 * code is shared by multiple regions, this returns true if it's valid in any of 202 * them. Note that this doesn't verify the number is actually in use, which is 203 * impossible to tell by just looking at the number itself. See 204 * {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details. 205 * 206 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 207 * want to test the validity 208 * @return {boolean} whether the short number matches a valid pattern 209 */ 210i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumber = 211 function(number) { 212 var regionCodes = this.getRegionCodesForCountryCode_( 213 number.getCountryCodeOrDefault()); 214 var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number, 215 regionCodes); 216 if (regionCodes.length > 1 && regionCode != null) { 217 // If a matching region had been found for the phone number from among two 218 // or more regions, then we have already implicitly verified its validity 219 // for that region. 220 return true; 221 } 222 return this.isValidShortNumberForRegion(number, regionCode); 223}; 224 225 226/** 227 * Gets the expected cost category of a short number when dialed from a region 228 * (however, nothing is implied about its validity). If it is important that the 229 * number is valid, then its validity must first be checked using 230 * {@link #isValidShortNumberForRegion}. Note that emergency numbers are always 231 * considered toll-free. Example usage: 232 * <pre>{@code 233 * // The region for which the number was parsed and the region we subsequently 234 * // check against need not be the same. Here we parse the number in the US and 235 * // check it for Canada. 236 * PhoneNumber number = phoneUtil.parse("110", "US"); 237 * ... 238 * String regionCode = "CA"; 239 * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance(); 240 * if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) { 241 * ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number, 242 * regionCode); 243 * // Do something with the cost information here. 244 * }}</pre> 245 * 246 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 247 * want to know the expected cost category 248 * @param {string} regionDialingFrom the region from which the number is dialed 249 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the expected cost 250 * category for that region of the short number. Returns UNKNOWN_COST if the 251 * number does not match a cost category. Note that an invalid number may 252 * match any cost category. 253 * @package 254 */ 255// @VisibleForTesting 256i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCostForRegion = 257 function(number, regionDialingFrom) { 258 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 259 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 260 return ShortNumberCost.UNKNOWN_COST; 261 } 262 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 263 if (!phoneMetadata) { 264 return ShortNumberCost.UNKNOWN_COST; 265 } 266 var shortNumber = this.getNationalSignificantNumber_(number); 267 268 if (!phoneMetadata.getGeneralDesc().possibleLengthArray().includes( 269 shortNumber.length)) { 270 return ShortNumberCost.UNKNOWN_COST; 271 } 272 if (this.matchesPossibleNumberAndNationalNumber_( 273 shortNumber, phoneMetadata.getPremiumRate())) { 274 return ShortNumberCost.PREMIUM_RATE; 275 } 276 if (this.matchesPossibleNumberAndNationalNumber_( 277 shortNumber, phoneMetadata.getStandardRate())) { 278 return ShortNumberCost.STANDARD_RATE; 279 } 280 if (this.matchesPossibleNumberAndNationalNumber_( 281 shortNumber, phoneMetadata.getTollFree())) { 282 return ShortNumberCost.TOLL_FREE; 283 } 284 if (this.isEmergencyNumber(shortNumber, regionDialingFrom)) { 285 // Emergency numbers are implicitly toll-free 286 return ShortNumberCost.TOLL_FREE; 287 } 288 return ShortNumberCost.UNKNOWN_COST; 289}; 290 291 292/** 293 * Gets the expected cost category of a short number (however, nothing is 294 * implied about its validity). If the country calling code is unique to a 295 * region, this method behaves exactly the same as 296 * {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the 297 * country calling code is shared by multiple regions, then it returns the 298 * highest cost in the sequence PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, 299 * TOLL_FREE. The reason for the position of UNKNOWN_COST in this order is that 300 * if a number is UNKNOWN_COST in one region but STANDARD_RATE or TOLL_FREE in 301 * another, its expected cost cannot be estimated as one of the latter since it 302 * might be a PREMIUM_RATE number. 303 * <p> 304 * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, 305 * the expected cost returned by this method will be STANDARD_RATE, since the 306 * NANPA countries share the same country calling code. 307 * <p> 308 * Note: If the region from which the number is dialed is known, it is highly 309 * preferable to call {@link #getExpectedCostForRegion(PhoneNumber, String)} 310 * instead. 311 * 312 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 313 * want to know the expected cost category 314 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the highest 315 * expected cost category of the short number in the region(s) with the 316 * given country calling code 317 * @package 318 */ 319// @VisibleForTesting 320i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCost = function(number) { 321 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 322 var regionCodes = this.getRegionCodesForCountryCode_( 323 number.getCountryCodeOrDefault()); 324 if (regionCodes.length === 0) { 325 return ShortNumberCost.UNKNOWN_COST; 326 } 327 if (regionCodes.length === 1) { 328 return this.getExpectedCostForRegion(number, regionCodes[0]); 329 } 330 var cost = ShortNumberCost.TOLL_FREE; 331 for (var i = 0; i < regionCodes.length; i++) { 332 var regionCode = regionCodes[i]; 333 var costForRegion = this.getExpectedCostForRegion(number, regionCode); 334 switch (costForRegion) { 335 case ShortNumberCost.PREMIUM_RATE: 336 return ShortNumberCost.PREMIUM_RATE; 337 case ShortNumberCost.UNKNOWN_COST: 338 cost = ShortNumberCost.UNKNOWN_COST; 339 break; 340 case ShortNumberCost.STANDARD_RATE: 341 if (cost !== ShortNumberCost.UNKNOWN_COST) { 342 cost = ShortNumberCost.STANDARD_RATE; 343 } 344 break; 345 case ShortNumberCost.TOLL_FREE: 346 // Do nothing. 347 break; 348 default: 349 throw new Error('Unrecognized cost for region: ' + costForRegion); 350 } 351 } 352 return cost; 353}; 354 355 356/** 357 * Helper method to get the region code for a given phone number, from a list 358 * of possible region codes. If the list contains more than one region, the 359 * first region for which the number is valid is returned. 360 * @param {!i18n.phonenumbers.PhoneNumber} number 361 * @param {Array<string>} regionCodes 362 * @return {?string} 363 * @private 364 */ 365i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodeForShortNumberFromRegionList_ = 366 function(number, regionCodes) { 367 if (regionCodes.length === 0) { 368 return null; 369 } else if (regionCodes.length === 1) { 370 return regionCodes[0]; 371 } 372 var nationalNumber = this.getNationalSignificantNumber_(number); 373 for (var i = 0; i < regionCodes.length; i++) { 374 var regionCode = regionCodes[i]; 375 var phoneMetadata = this.getMetadataForRegion_(regionCode); 376 if (phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 377 nationalNumber, phoneMetadata.getShortCode())) { 378 return regionCode; 379 } 380 } 381 return null; 382}; 383 384 385/** 386 * Convenience method to get a list of what regions the library has metadata for 387 * @return {!Array<string>} the list of region codes 388 * @package 389 */ 390i18n.phonenumbers.ShortNumberInfo.prototype.getSupportedRegions = function() { 391 return Object.keys(i18n.phonenumbers.shortnumbermetadata.countryToMetadata) 392 .filter(function(regionCode) { 393 return isNaN(regionCode); 394 }); 395}; 396 397 398/** 399 * Gets a valid short number for the specified region. 400 * 401 * @param {?string} regionCode the region for which an example short number is 402 * needed 403 * @return {string} a valid short number for the specified region. Returns an 404 * empty string when the metadata does not contain such information. 405 * @package 406 */ 407i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumber = 408 function(regionCode) { 409 var phoneMetadata = this.getMetadataForRegion_(regionCode); 410 if (!phoneMetadata) { 411 return ''; 412 } 413 var desc = phoneMetadata.getShortCode(); 414 if (desc.hasExampleNumber()) { 415 return desc.getExampleNumber() || ''; 416 } 417 return ''; 418}; 419 420 421/** 422 * Gets a valid short number for the specified cost category. 423 * 424 * @param {string} regionCode the region for which an example short number is 425 * needed 426 * @param {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} cost the cost 427 * category of number that is needed 428 * @return {string} a valid short number for the specified region and cost 429 * category. Returns an empty string when the metadata does not contain such 430 * information, or the cost is UNKNOWN_COST. 431 */ 432i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumberForCost = 433 function(regionCode, cost) { 434 var phoneMetadata = this.getMetadataForRegion_(regionCode); 435 if (!phoneMetadata) { 436 return ''; 437 } 438 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 439 var desc = null; 440 switch (cost) { 441 case ShortNumberCost.TOLL_FREE: 442 desc = phoneMetadata.getTollFree(); 443 break; 444 case ShortNumberCost.STANDARD_RATE: 445 desc = phoneMetadata.getStandardRate(); 446 break; 447 case ShortNumberCost.PREMIUM_RATE: 448 desc = phoneMetadata.getPremiumRate(); 449 break; 450 default: 451 // UNKNOWN_COST numbers are computed by the process of elimination from 452 // the other cost categories. 453 } 454 if (desc && desc.hasExampleNumber()) { 455 return desc.getExampleNumber() || ''; 456 } 457 return ''; 458}; 459 460 461/** 462 * Returns true if the given number, exactly as dialed, might be used to 463 * connect to an emergency service in the given region. 464 * <p> 465 * This method accepts a string, rather than a PhoneNumber, because it needs 466 * to distinguish cases such as "+1 911" and "911", where the former may not 467 * connect to an emergency service in all cases but the latter would. This 468 * method takes into account cases where the number might contain formatting, 469 * or might have additional digits appended (when it is okay to do that in 470 * the specified region). 471 * 472 * @param {string} number the phone number to test 473 * @param {string} regionCode the region where the phone number is being 474 * dialed 475 * @return {boolean} whether the number might be used to connect to an 476 * emergency service in the given region 477 */ 478i18n.phonenumbers.ShortNumberInfo.prototype.connectsToEmergencyNumber = 479 function(number, regionCode) { 480 return this.matchesEmergencyNumberHelper_(number, regionCode, 481 true /* allows prefix match */); 482}; 483 484 485/** 486 * Returns true if the given number exactly matches an emergency service 487 * number in the given region. 488 * <p> 489 * This method takes into account cases where the number might contain 490 * formatting, but doesn't allow additional digits to be appended. Note that 491 * {@code isEmergencyNumber(number, region)} implies 492 * {@code connectsToEmergencyNumber(number, region)}. 493 * 494 * @param {string} number the phone number to test 495 * @param {string} regionCode the region where the phone number is being 496 * dialed 497 * @return {boolean} whether the number exactly matches an emergency services 498 * number in the given region. 499 */ 500i18n.phonenumbers.ShortNumberInfo.prototype.isEmergencyNumber = 501 function(number, regionCode) { 502 return this.matchesEmergencyNumberHelper_(number, regionCode, 503 false /* doesn't allow prefix match */); 504}; 505 506 507/** 508 * @param {?string} regionCode The region code to get metadata for 509 * @return {?i18n.phonenumbers.PhoneMetadata} The region code's metadata, or 510 * null if it is not available or the region code is invalid. 511 * @private 512 */ 513i18n.phonenumbers.ShortNumberInfo.prototype.getMetadataForRegion_ = 514 function(regionCode) { 515 if (!regionCode) { 516 return null; 517 } 518 regionCode = regionCode.toUpperCase(); 519 var metadata = this.regionToMetadataMap[regionCode]; 520 if (metadata == null) { 521 /** @type {goog.proto2.PbLiteSerializer} */ 522 var serializer = new goog.proto2.PbLiteSerializer(); 523 var metadataSerialized = 524 i18n.phonenumbers.shortnumbermetadata.countryToMetadata[regionCode]; 525 if (metadataSerialized == null) { 526 return null; 527 } 528 metadata = /** @type {i18n.phonenumbers.PhoneMetadata} */ ( 529 serializer.deserialize(i18n.phonenumbers.PhoneMetadata.getDescriptor(), 530 metadataSerialized)); 531 this.regionToMetadataMap[regionCode] = metadata; 532 } 533 return metadata; 534}; 535 536 537/** 538 * @param {string} number the number to match against 539 * @param {string} regionCode the region code to check against 540 * @param {boolean} allowPrefixMatch whether to allow prefix matching 541 * @return {boolean} True iff the number matches an emergency number for that 542 * particular region. 543 * @private 544 */ 545i18n.phonenumbers.ShortNumberInfo.prototype.matchesEmergencyNumberHelper_ = 546 function(number, regionCode, allowPrefixMatch) { 547 var possibleNumber = i18n.phonenumbers.PhoneNumberUtil 548 .extractPossibleNumber(number); 549 if (i18n.phonenumbers.PhoneNumberUtil.LEADING_PLUS_CHARS_PATTERN 550 .test(possibleNumber)) { 551 return false; 552 } 553 var metadata = this.getMetadataForRegion_(regionCode); 554 if (metadata == null || !metadata.hasEmergency()) { 555 return false; 556 } 557 558 var normalizedNumber = i18n.phonenumbers.PhoneNumberUtil 559 .normalizeDigitsOnly(possibleNumber); 560 var allowPrefixMatchForRegion = allowPrefixMatch && 561 !i18n.phonenumbers.ShortNumberInfo 562 .REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_.includes(regionCode); 563 var emergencyNumberPattern = metadata.getEmergency() 564 .getNationalNumberPatternOrDefault(); 565 var result = i18n.phonenumbers.PhoneNumberUtil.matchesEntirely( 566 emergencyNumberPattern, normalizedNumber); 567 return result || 568 (allowPrefixMatchForRegion && 569 i18n.phonenumbers.PhoneNumberUtil 570 .matchesPrefix(emergencyNumberPattern, normalizedNumber)); 571}; 572 573 574/** 575 * Given a valid short number, determines whether it is carrier-specific 576 * (however, nothing is implied about its validity). Carrier-specific numbers 577 * may connect to a different end-point, or not connect at all, depending on 578 * the user's carrier. If it is important that the number is valid, then its 579 * validity must first be checked using {@link #isValidShortNumber} or 580 * {@link #isValidShortNumberForRegion}. 581 * 582 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 583 * check 584 * @return {boolean} whether the short number is carrier-specific, assuming the 585 * input was a valid short number 586 */ 587i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecific = 588 function(number) { 589 var regionCodes = this.getRegionCodesForCountryCode_( 590 number.getCountryCodeOrDefault()); 591 var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number, 592 regionCodes); 593 var nationalNumber = this.getNationalSignificantNumber_(number); 594 var phoneMetadata = this.getMetadataForRegion_(regionCode); 595 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 596 nationalNumber, phoneMetadata.getCarrierSpecific()); 597}; 598 599 600/** 601 * Given a valid short number, determines whether it is carrier-specific when 602 * dialed from the given region (however, nothing is implied about its 603 * validity). Carrier-specific numbers may connect to a different end-point, or 604 * not connect at all, depending on the user's carrier. If it is important that 605 * the number is valid, then its validity must first be checked using 606 * {@link #isValidShortNumber} or {@link #isValidShortNumberForRegion}. Returns 607 * false if the number doesn't match the region provided. 608 * 609 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 610 * check 611 * @param {string} regionDialingFrom the region from which the number is dialed 612 * @return {boolean} whether the short number is carrier-specific in the 613 * provided region, assuming the input was a valid short number 614 */ 615i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecificForRegion = 616 function(number, regionDialingFrom) { 617 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 618 return false; 619 } 620 var nationalNumber = this.getNationalSignificantNumber_(number); 621 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 622 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 623 nationalNumber, phoneMetadata.getCarrierSpecific()); 624}; 625 626 627/** 628 * Given a valid short number, determines whether it is an SMS service 629 * (however, nothing is implied about its validity). An SMS service is where the 630 * primary or only intended usage is to receive and/or send text messages 631 * (SMSs). This includes MMS as MMS numbers downgrade to SMS if the other party 632 * isn't MMS-capable. If it is important that the number is valid, then its 633 * validity must first be checked using {@link #isValidShortNumber} or {@link 634 * #isValidShortNumberForRegion}. Returns false if the number doesn't match the 635 * region provided. 636 * 637 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 638 * check 639 * @param {string} regionDialingFrom the region from which the number is dialed 640 * @return {boolean} whether the short number is an SMS service in the provided 641 * region, assuming the input was a valid short number 642 */ 643i18n.phonenumbers.ShortNumberInfo.prototype.isSmsServiceForRegion = 644 function(number, regionDialingFrom) { 645 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 646 return false; 647 } 648 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 649 var nationalNumber = this.getNationalSignificantNumber_(number); 650 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 651 nationalNumber, phoneMetadata.getSmsServices()); 652}; 653 654 655/** 656 * Gets the national significant number of a phone number. Note a national 657 * significant number doesn't contain a national prefix or any formatting. 658 * <p> 659 * This is a temporary duplicate of the {@code getNationalSignificantNumber} 660 * method from {@code PhoneNumberUtil}. Ultimately a canonical static version 661 * should exist in a separate utility class (to prevent {@code ShortNumberInfo} 662 * needing to depend on PhoneNumberUtil). 663 * 664 * @param {i18n.phonenumbers.PhoneNumber} number the phone number for which the 665 * national significant number is needed. 666 * @return {string} the national significant number of the PhoneNumber object 667 * passed in. 668 * @private 669 */ 670i18n.phonenumbers.ShortNumberInfo.prototype.getNationalSignificantNumber_ = 671 function(number) { 672 if (!number.hasNationalNumber()) { 673 return ''; 674 } 675 /** @type {string} */ 676 var nationalNumber = '' + number.getNationalNumber(); 677 // If leading zero(s) have been set, we prefix this now. Note that a single 678 // leading zero is not the same as a national prefix; leading zeros should be 679 // dialled no matter whether you are dialling from within or outside the 680 // country, national prefixes are added when formatting nationally if 681 // applicable. 682 if (number.hasItalianLeadingZero() && number.getItalianLeadingZero() && 683 number.getNumberOfLeadingZerosOrDefault() > 0) { 684 return Array(number.getNumberOfLeadingZerosOrDefault() + 1).join('0') + 685 nationalNumber; 686 } 687 return nationalNumber; 688}; 689 690 691/** 692 * Helper method to add in a performance optimization. 693 * TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth 694 * keeping this performance optimization. 695 * @param {string} number 696 * @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc 697 * @return {boolean} 698 * @private 699 */ 700i18n.phonenumbers.ShortNumberInfo.prototype 701 .matchesPossibleNumberAndNationalNumber_ = function(number, numberDesc) { 702 if (numberDesc.possibleLengthArray().length > 0 && 703 !numberDesc.possibleLengthArray().includes(number.length)) { 704 return false; 705 } 706 return i18n.phonenumbers.PhoneNumberUtil.matchesEntirely( 707 numberDesc.getNationalNumberPatternOrDefault(), number.toString()); 708}; 709