Bluesky app fork with some witchin' additions 馃挮
at main 162 lines 4.2 kB view raw
1import {t} from '@lingui/core/macro' 2import { 3 isSupportedCountry, 4 ParseError, 5 parsePhoneNumber, 6 parsePhoneNumberWithError, 7 type PhoneNumber, 8} from 'libphonenumber-js/max' 9 10import {type CountryCode} from '#/lib/international-telephone-codes' 11 12/** 13 * Intended for after the user has finished inputting their phone number. 14 */ 15export function processPhoneNumber( 16 number: string, 17 country: CountryCode, 18): 19 | { 20 valid: true 21 formatted: string 22 countryCode: CountryCode 23 } 24 | { 25 valid: false 26 reason?: string 27 } { 28 try { 29 const phoneNumber = parsePhoneNumberWithError(number, { 30 defaultCountry: country, 31 }) 32 if (!phoneNumber.isValid()) { 33 return {valid: false, reason: t`Invalid phone number`} 34 } 35 const type = phoneNumber.getType() 36 if ( 37 type !== 'MOBILE' && 38 type !== 'FIXED_LINE_OR_MOBILE' && 39 type !== 'PERSONAL_NUMBER' 40 ) { 41 return { 42 valid: false, 43 reason: t`Number should be a mobile number`, 44 } 45 } 46 let countryCode = country 47 if (phoneNumber.country && phoneNumber.country !== country) { 48 if (phoneNumber.country === 'AC' || phoneNumber.country === 'TA') { 49 countryCode = 'SH' 50 } else { 51 countryCode = phoneNumber.country 52 } 53 } 54 return { 55 valid: true, 56 formatted: formatE164lWithoutCountryCode(phoneNumber), 57 countryCode, 58 } 59 } catch (error) { 60 if (error instanceof ParseError) { 61 return {valid: false, reason: error.message} 62 } else { 63 return {valid: false} 64 } 65 } 66} 67 68/** 69 * Format a phone number as the international format with the prefix 70 * removed. 71 */ 72function formatE164lWithoutCountryCode(phoneNumber: PhoneNumber) { 73 const intl = phoneNumber.format('E.164') 74 const prefix = '+' + phoneNumber.countryCallingCode 75 return intl.replace(prefix, '').trim() 76} 77 78/** 79 * Takes a country code and a prefix-less phone number and constructs a full phone number. 80 * 81 * Does not have nice error handling - if you're unsure if the number is valid, use 82 * `processPhoneNumber` instead 83 */ 84export function constructFullPhoneNumber( 85 countryCode: CountryCode, 86 phoneNumber: string, 87) { 88 const result = parsePhoneNumber(phoneNumber, {defaultCountry: countryCode}) 89 if (!result.isValid()) 90 throw new Error('Invalid phone number passed to constructFullPhoneNumber') 91 return result.format('E.164') 92} 93 94/** 95 * Takes a phone number and applies human-readable formatting. Do not sent to the API - they 96 * expect E.164 format. 97 */ 98export function prettyPhoneNumber(phoneNumber: string) { 99 const result = parsePhoneNumber(phoneNumber) 100 return result.formatNational() 101} 102 103/** 104 * Attempts to parse a phone number from a string, and returns the country code 105 * and the rest of the number if possible. If the number is invalid, returns undefined. 106 */ 107export function getCountryCodeFromPastedNumber( 108 text: string, 109): {countryCode: CountryCode; rest: string} | undefined { 110 try { 111 const phoneNumber = parsePhoneNumber(text) 112 if (!phoneNumber.isValid()) { 113 return undefined 114 } 115 const countryCode = phoneNumber.country 116 // we don't have AC and TA in our dropdown - see `#/lib/international-telephone-codes` 117 if (countryCode && countryCode !== 'AC' && countryCode !== 'TA') { 118 return { 119 countryCode, 120 rest: formatE164lWithoutCountryCode(phoneNumber), 121 } 122 } else { 123 return undefined 124 } 125 } catch (error) { 126 return undefined 127 } 128} 129 130/** 131 * Normalizes a phone number into E.164 format 132 */ 133export function normalizePhoneNumber( 134 rawNumber: string, 135 countryCode: string | undefined, 136 fallbackCountryCode: CountryCode, 137): string | null { 138 try { 139 const result = parsePhoneNumber(rawNumber, { 140 defaultCountry: 141 countryCode && isSupportedCountry(countryCode) 142 ? countryCode 143 : fallbackCountryCode, 144 }) 145 146 if (!result.isValid()) return null 147 148 const type = result.getType() 149 if ( 150 type !== 'MOBILE' && 151 type !== 'FIXED_LINE_OR_MOBILE' && 152 type !== 'PERSONAL_NUMBER' 153 ) { 154 return null 155 } 156 157 return result.format('E.164') 158 } catch (error) { 159 console.log('Failed to normalize phone number:', error) 160 return null 161 } 162}