From 8edb05487482ce21a0c7dd7f1f30ae5a296b9d21 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:25:43 -0600 Subject: [PATCH] Send alternate numbers to CDSI Co-authored-by: yash-signal --- package-lock.json | 8 +- package.json | 2 +- ts/ConversationController.ts | 12 +-- ts/state/ducks/accounts.ts | 10 +- ts/updateConversationsWithUuidLookup.ts | 8 +- ts/util/getServiceIdsForE164s.ts | 94 ++++++++++++++++++- ts/util/lookupConversationWithoutServiceId.ts | 10 +- 7 files changed, 116 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd23777f1628..92985b0737f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "form-data": "4.0.1", "fs-extra": "11.2.0", "fuse.js": "6.5.3", - "google-libphonenumber": "3.2.38", + "google-libphonenumber": "^3.2.39", "got": "11.8.5", "heic-convert": "2.1.0", "humanize-duration": "3.27.1", @@ -17417,9 +17417,9 @@ "dev": true }, "node_modules/google-libphonenumber": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.38.tgz", - "integrity": "sha512-t/K0dsVmA0gMMVLJgcMeB9g1Ar4ANVWfkY+AJGSdfyJ2Ay7Bu8ceLYpUlC6FZSilZgaF1qbkM9tZydGBEBHqAg==", + "version": "3.2.39", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.39.tgz", + "integrity": "sha512-dpCbkY6ZxHXIHEFDwSir/gPBWkn22e2EixBv47guVs/NE8+qd35f1yu+fxQ8awRnHEXC60uhcPM9mbqmrD6nmw==", "license": "(MIT AND Apache-2.0)", "engines": { "node": ">=0.10" diff --git a/package.json b/package.json index cb10a833fbaf..2f4343ecb275 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "form-data": "4.0.1", "fs-extra": "11.2.0", "fuse.js": "6.5.3", - "google-libphonenumber": "3.2.38", + "google-libphonenumber": "3.2.39", "got": "11.8.5", "heic-convert": "2.1.0", "humanize-duration": "3.27.1", diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 3604809e713a..5546d78183dd 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -1402,15 +1402,15 @@ export class ConversationController { async _forgetE164(e164: string): Promise { const { server } = window.textsecure; strictAssert(server, 'Server must be initialized'); - const { entries: serviceIdMap } = await getServiceIdsForE164s(server, [ - e164, - ]); + const { entries: serviceIdMap, transformedE164s } = + await getServiceIdsForE164s(server, [e164]); - const pni = serviceIdMap.get(e164)?.pni; + const e164ToUse = transformedE164s.get(e164) ?? e164; + const pni = serviceIdMap.get(e164ToUse)?.pni; - log.info(`ConversationController: forgetting e164=${e164} pni=${pni}`); + log.info(`ConversationController: forgetting e164=${e164ToUse} pni=${pni}`); - const convos = [this.get(e164), this.get(pni)]; + const convos = [this.get(e164ToUse), this.get(pni)]; for (const convo of convos) { if (!convo) { diff --git a/ts/state/ducks/accounts.ts b/ts/state/ducks/accounts.ts index 19fe362bc55b..43627df27ed8 100644 --- a/ts/state/ducks/accounts.ts +++ b/ts/state/ducks/accounts.ts @@ -93,17 +93,17 @@ function checkForAccount( log.info(`checkForAccount: looking ${phoneNumber} up on server`); try { - const { entries: serviceIdLookup } = await getServiceIdsForE164s(server, [ - phoneNumber, - ]); - const maybePair = serviceIdLookup.get(phoneNumber); + const { entries: serviceIdLookup, transformedE164s } = + await getServiceIdsForE164s(server, [phoneNumber]); + const phoneNumberToUse = transformedE164s.get(phoneNumber) ?? phoneNumber; + const maybePair = serviceIdLookup.get(phoneNumberToUse); if (maybePair) { const { conversation: maybeMerged } = window.ConversationController.maybeMergeContacts({ aci: maybePair.aci, pni: maybePair.pni, - e164: phoneNumber, + e164: phoneNumberToUse, reason: 'checkForAccount', }); serviceId = maybeMerged.getServiceId(); diff --git a/ts/updateConversationsWithUuidLookup.ts b/ts/updateConversationsWithUuidLookup.ts index ed5284a6f760..618038036920 100644 --- a/ts/updateConversationsWithUuidLookup.ts +++ b/ts/updateConversationsWithUuidLookup.ts @@ -27,7 +27,8 @@ export async function updateConversationsWithUuidLookup({ return; } - const { entries: serverLookup } = await getServiceIdsForE164s(server, e164s); + const { entries: serverLookup, transformedE164s } = + await getServiceIdsForE164s(server, e164s); await Promise.all( conversations.map(async conversation => { @@ -38,13 +39,14 @@ export async function updateConversationsWithUuidLookup({ let finalConversation: ConversationModel; - const pairFromServer = serverLookup.get(e164); + const e164ToUse = transformedE164s.get(e164) ?? e164; + const pairFromServer = serverLookup.get(e164ToUse); if (pairFromServer) { const { conversation: maybeFinalConversation } = conversationController.maybeMergeContacts({ aci: pairFromServer.aci, pni: pairFromServer.pni, - e164, + e164: e164ToUse, reason: 'updateConversationsWithUuidLookup', }); assertDev( diff --git a/ts/util/getServiceIdsForE164s.ts b/ts/util/getServiceIdsForE164s.ts index ffdf744481cb..c89b9c4d0bb4 100644 --- a/ts/util/getServiceIdsForE164s.ts +++ b/ts/util/getServiceIdsForE164s.ts @@ -6,11 +6,73 @@ import type { WebAPIType } from '../textsecure/WebAPI'; import type { AciString } from '../types/ServiceId'; import * as log from '../logging/log'; import { isDirectConversation, isMe } from './whatTypeOfConversation'; +import { parseNumber } from './libphonenumberUtil'; + +type PhoneNumberTransformation = { + oldPattern: RegExp; + newPattern: RegExp; + oldToNew: (e164: string) => string; + newToOld: (e164: string) => string; +}; + +const PHONE_TRANSFORMATIONS: Record = { + '229': { + // Benin + oldPattern: /^\+229\d{8}$/, + newPattern: /^\+22901\d{8}$/, + oldToNew: (e164: string) => e164.replace(/^\+229(\d{8})$/, '+22901$1'), + newToOld: (e164: string) => e164.replace(/^\+22901(\d{8})$/, '+229$1'), + }, + '52': { + // Mexico + oldPattern: /^\+521\d{10}$/, + newPattern: /^\+52\d{10}$/, + oldToNew: (e164: string) => e164.replace(/^\+521(\d{10})$/, '+52$1'), + newToOld: (e164: string) => e164.replace(/^\+52(\d{10})$/, '+521$1'), + }, + '54': { + // Argentina + oldPattern: /^\+54\d{10}$/, + newPattern: /^\+549\d{10}$/, + oldToNew: (e164: string) => e164.replace(/^\+54(\d{10})$/, '+549$1'), + newToOld: (e164: string) => e164.replace(/^\+549(\d{10})$/, '+54$1'), + }, +}; + +type ReturnType = CDSResponseType & { + // Maps from provided E164 phone numbers to their alternate representations + // found in CDSI. If a E164 appears as a key in this map, you should use the + // corresponding E164 value for any subsequent operations, as that's + // the format stored in CDSI's database. + transformedE164s: Map; +}; export async function getServiceIdsForE164s( server: Pick, e164s: ReadonlyArray -): Promise { +): Promise { + const expandedE164s = new Set(e164s); + + const transformationMap = new Map(); + + for (const e164 of e164s) { + const parsedNumber = parseNumber(e164); + + if (parsedNumber.isValidNumber && parsedNumber.countryCode) { + const transform = PHONE_TRANSFORMATIONS[parsedNumber.countryCode]; + if (transform) { + if (transform.oldPattern.test(e164)) { + const newFormat = transform.oldToNew(e164); + expandedE164s.add(newFormat); + transformationMap.set(e164, newFormat); + } else if (transform.newPattern.test(e164)) { + const oldFormat = transform.newToOld(e164); + expandedE164s.add(oldFormat); + transformationMap.set(e164, oldFormat); + } + } + } + } // Note: these have no relationship to supplied e164s. We just provide // all available information to the server so that it could return as many // ACI+PNI+E164 matches as possible. @@ -35,16 +97,40 @@ export async function getServiceIdsForE164s( acisAndAccessKeys.push({ aci, accessKey }); } + const expandedE164sArray = Array.from(expandedE164s); + log.info( - `getServiceIdsForE164s(${e164s}): acis=${acisAndAccessKeys.length} ` + + `getServiceIdsForE164s(${expandedE164sArray}): acis=${acisAndAccessKeys.length} ` + `accessKeys=${acisAndAccessKeys.length}` ); - return server.cdsLookup({ - e164s, + const response = await server.cdsLookup({ + e164s: expandedE164sArray, acisAndAccessKeys, returnAcisWithoutUaks: false, useLibsignal: window.Signal.RemoteConfig.isEnabled( 'desktop.cdsiViaLibsignal' ), }); + + const e164sWithVariantsInCdsi = new Map( + Array.from(transformationMap).filter(([providedE164, alternateE164]) => { + if ( + response.entries.has(providedE164) && + response.entries.has(alternateE164) + ) { + log.warn(`both ${providedE164} and ${alternateE164} are in CDSI`); + return false; + } + if (response.entries.has(alternateE164)) { + return true; + } + + return false; + }) + ); + + return { + ...response, + transformedE164s: e164sWithVariantsInCdsi, + }; } diff --git a/ts/util/lookupConversationWithoutServiceId.ts b/ts/util/lookupConversationWithoutServiceId.ts index 5125798bb3ac..58cd7679b8d9 100644 --- a/ts/util/lookupConversationWithoutServiceId.ts +++ b/ts/util/lookupConversationWithoutServiceId.ts @@ -71,18 +71,18 @@ export async function lookupConversationWithoutServiceId( try { let conversationId: string | undefined; if (options.type === 'e164') { - const { entries: serverLookup } = await getServiceIdsForE164s(server, [ - options.e164, - ]); + const { entries: serverLookup, transformedE164s } = + await getServiceIdsForE164s(server, [options.e164]); + const e164ToUse = transformedE164s.get(options.e164) ?? options.e164; - const maybePair = serverLookup.get(options.e164); + const maybePair = serverLookup.get(e164ToUse); if (maybePair) { const { conversation } = window.ConversationController.maybeMergeContacts({ aci: maybePair.aci, pni: maybePair.pni, - e164: options.e164, + e164: e164ToUse, reason: 'startNewConversationWithoutUuid(e164)', }); conversationId = conversation?.id;