diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 50142152458..65f5ce5a9ba 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1235,9 +1235,13 @@ "messageformat": "{obsoleteConversationTitle} and {conversationTitle} are the same account. Your message history for both chats are here.", "description": "Shown when we've discovered that two local conversations are the same remote account in an unusual way" }, - "icu:ConversationMerge--notification--no-e164": { + "icu:ConversationMerge--notification--with-e164": { + "messageformat": "Your message history with {conversationTitle} and their number {obsoleteConversationNumber} has been merged.", + "description": "Shown when we've discovered that two local conversations are the same remote account in an unusual way, but we have the phone number for the old conversation" + }, + "icu:ConversationMerge--notification--no-title": { "messageformat": "Your message history with {conversationTitle} and another chat that belonged to them has been merged.", - "description": "Shown when we've discovered that two local conversations are the same remote account in an unusual way, but we don't have the phone number for the old conversation" + "description": "Shown when we've discovered that two local conversations are the same remote account in an unusual way, but we don't have the title for the old conversation" }, "icu:ConversationMerge--learn-more": { "messageformat": "Learn More", @@ -1253,11 +1257,11 @@ }, "icu:PhoneNumberDiscovery--notification--withSharedGroup": { "messageformat": "{phoneNumber} belongs to {conversationTitle}. You're both members of {sharedGroup}.", - "description": "Shown when we've discovered a phone number for a contact you've been communicating with." + "description": "(deleted 2023/01/11) Shown when we've discovered a phone number for a contact you've been communicating with." }, "icu:PhoneNumberDiscovery--notification--noSharedGroup": { "messageformat": "{phoneNumber} belongs to {conversationTitle}", - "description": "Shown when we've discovered a phone number for a contact you've been communicating with, but you have no shared groups." + "description": "(deleted 2023/01/11) Shown when we've discovered a phone number for a contact you've been communicating with, but you have no shared groups." }, "quoteThumbnailAlt": { "message": "Thumbnail of image from quoted message", diff --git a/images/icons/v2/merge-16.svg b/images/icons/v2/merge-16.svg new file mode 100644 index 00000000000..35e7496d426 --- /dev/null +++ b/images/icons/v2/merge-16.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stylesheets/components/SystemMessage.scss b/stylesheets/components/SystemMessage.scss index bb63dfd169a..2f265af108b 100644 --- a/stylesheets/components/SystemMessage.scss +++ b/stylesheets/components/SystemMessage.scss @@ -281,6 +281,13 @@ '../images/icons/v2/credit-card-16.svg' ); } + + &--icon-merge::before { + @include system-message-icon( + '../images/icons/v2/merge-16.svg', + '../images/icons/v2/merge-16.svg' + ); + } } &--error { diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 0a1cc50a9f2..dbeb61bf6cd 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -49,8 +49,7 @@ function applyChangeToConversation( conversation: ConversationModel, suggestedChange: Partial< Pick - >, - disableDiscoveryNotification?: boolean + > ) { const change = { ...suggestedChange }; @@ -84,9 +83,7 @@ function applyChangeToConversation( conversation.updateUuid(change.uuid); } if (hasOwnProperty.call(change, 'e164')) { - conversation.updateE164(change.e164, { - disableDiscoveryNotification, - }); + conversation.updateE164(change.e164); } if (hasOwnProperty.call(change, 'pni')) { conversation.updatePni(change.pni); @@ -485,7 +482,6 @@ export class ConversationController { const aci = providedAci ? UUID.cast(providedAci) : undefined; const pni = providedPni ? UUID.cast(providedPni) : undefined; - let targetConversationWasCreated = false; const mergePromises: Array> = []; if (!aci && !e164 && !pni) { @@ -524,13 +520,9 @@ export class ConversationController { `${logId}: No match for ${key}, applying to target conversation` ); // Note: This line might erase a known e164 or PNI - applyChangeToConversation( - targetConversation, - { - [key]: value, - }, - targetConversationWasCreated - ); + applyChangeToConversation(targetConversation, { + [key]: value, + }); } else { unusedMatches.push(item); } @@ -578,7 +570,6 @@ export class ConversationController { // If PNI match already has an ACI, then we need to create a new one if (!targetConversation) { targetConversation = this.getOrCreate(unused.value, 'private'); - targetConversationWasCreated = true; log.info( `${logId}: Match on ${key} already had ${unused.key}, ` + `so created new target conversation - ${targetConversation.idForLogging()}` @@ -588,13 +579,9 @@ export class ConversationController { log.info( `${logId}: Applying new value for ${unused.key} to target conversation` ); - applyChangeToConversation( - targetConversation, - { - [unused.key]: unused.value, - }, - targetConversationWasCreated - ); + applyChangeToConversation(targetConversation, { + [unused.key]: unused.value, + }); }); unusedMatches = []; @@ -633,20 +620,16 @@ export class ConversationController { if ((key === 'pni' || key === 'e164') && match.get('uuid') === pni) { change.uuid = undefined; } - applyChangeToConversation(match, change, targetConversationWasCreated); + applyChangeToConversation(match, change); // Note: The PNI check here is just to be bulletproof; if we know a UUID is a PNI, // then that should be put in the UUID field as well! const willMerge = !match.get('uuid') && !match.get('e164') && !match.get('pni'); - applyChangeToConversation( - targetConversation, - { - [key]: value, - }, - willMerge || targetConversationWasCreated - ); + applyChangeToConversation(targetConversation, { + [key]: value, + }); if (willMerge) { log.warn( @@ -666,13 +649,9 @@ export class ConversationController { } else if (targetConversation && !targetConversation?.get(key)) { // This is mostly for the situation where PNI was erased when updating e164 log.debug(`${logId}: Re-adding ${key} on target conversation`); - applyChangeToConversation( - targetConversation, - { - [key]: value, - }, - targetConversationWasCreated - ); + applyChangeToConversation(targetConversation, { + [key]: value, + }); } if (!targetConversation) { @@ -739,7 +718,7 @@ export class ConversationController { // `identifier` would resolve to uuid if we had both, so fix up e164 if (normalizedUuid && e164) { - newConvo.updateE164(e164, { disableDiscoveryNotification: true }); + newConvo.updateE164(e164); } return newConvo; @@ -1131,7 +1110,7 @@ export class ConversationController { const titleIsUseful = Boolean( obsoleteTitleInfo && getTitleNoDefault(obsoleteTitleInfo) ); - if (!fromPniSignature && obsoleteTitleInfo && titleIsUseful) { + if (obsoleteTitleInfo && titleIsUseful && !fromPniSignature) { drop(current.addConversationMerge(obsoleteTitleInfo)); } diff --git a/ts/components/ConversationList.tsx b/ts/components/ConversationList.tsx index b1f8f34c2e3..932696f5b50 100644 --- a/ts/components/ConversationList.tsx +++ b/ts/components/ConversationList.tsx @@ -338,6 +338,7 @@ export function ConversationList({ 'typingContactId', 'unblurredAvatarPath', 'unreadCount', + 'uuid', ]); const { badges, title, unreadCount, lastMessage } = itemProps; result = ( diff --git a/ts/components/conversation/ConversationMergeNotification.stories.tsx b/ts/components/conversation/ConversationMergeNotification.stories.tsx index 61a642981fb..e32a6d63689 100644 --- a/ts/components/conversation/ConversationMergeNotification.stories.tsx +++ b/ts/components/conversation/ConversationMergeNotification.stories.tsx @@ -17,14 +17,25 @@ export default { const createProps = (overrideProps: Partial = {}): PropsType => ({ i18n, conversationTitle: overrideProps.conversationTitle || 'John Fire', + obsoleteConversationNumber: + overrideProps.obsoleteConversationNumber || '(555) 333-1111', obsoleteConversationTitle: - overrideProps.obsoleteConversationTitle || '(555) 333-1111', + overrideProps.obsoleteConversationTitle || 'John Obsolete', }); export function Basic(): JSX.Element { return ; } +export function WithNoObsoleteNumber(): JSX.Element { + return ( + + ); +} + export function WithNoObsoleteTitle(): JSX.Element { return ( } button={ obsoleteConversationTitle ? ( diff --git a/ts/components/conversation/PhoneNumberDiscoveryNotification.stories.tsx b/ts/components/conversation/PhoneNumberDiscoveryNotification.stories.tsx deleted file mode 100644 index f782619a798..00000000000 --- a/ts/components/conversation/PhoneNumberDiscoveryNotification.stories.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import * as React from 'react'; - -import { setupI18n } from '../../util/setupI18n'; -import enMessages from '../../../_locales/en/messages.json'; -import type { PropsType } from './PhoneNumberDiscoveryNotification'; -import { PhoneNumberDiscoveryNotification } from './PhoneNumberDiscoveryNotification'; - -const i18n = setupI18n('en', enMessages); - -export default { - title: 'Components/Conversation/PhoneNumberDiscoveryNotification', -}; - -const createProps = (overrideProps: Partial = {}): PropsType => ({ - i18n, - conversationTitle: overrideProps.conversationTitle || 'Mr. Fire', - phoneNumber: overrideProps.phoneNumber || '+1 (000) 123-4567', - sharedGroup: overrideProps.sharedGroup, -}); - -export function Basic(): JSX.Element { - return ; -} - -export function WithSharedGroup(): JSX.Element { - return ( - - ); -} diff --git a/ts/components/conversation/PhoneNumberDiscoveryNotification.tsx b/ts/components/conversation/PhoneNumberDiscoveryNotification.tsx deleted file mode 100644 index 6580aaa9ba0..00000000000 --- a/ts/components/conversation/PhoneNumberDiscoveryNotification.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import React from 'react'; - -import type { LocalizerType } from '../../types/Util'; -import { SystemMessage } from './SystemMessage'; -import { Emojify } from './Emojify'; -import { getStringForPhoneNumberDiscovery } from '../../util/getStringForPhoneNumberDiscovery'; - -export type PropsDataType = { - conversationTitle: string; - phoneNumber: string; - sharedGroup?: string; -}; -export type PropsType = PropsDataType & { - i18n: LocalizerType; -}; - -export function PhoneNumberDiscoveryNotification( - props: PropsType -): JSX.Element { - const { conversationTitle, i18n, sharedGroup, phoneNumber } = props; - const message = getStringForPhoneNumberDiscovery({ - conversationTitle, - i18n, - phoneNumber, - sharedGroup, - }); - - return } />; -} diff --git a/ts/components/conversation/TimelineItem.tsx b/ts/components/conversation/TimelineItem.tsx index c7a5111116d..f6ce1852fcc 100644 --- a/ts/components/conversation/TimelineItem.tsx +++ b/ts/components/conversation/TimelineItem.tsx @@ -50,8 +50,6 @@ import type { PropsType as PaymentEventNotificationPropsType } from './PaymentEv import { PaymentEventNotification } from './PaymentEventNotification'; import type { PropsDataType as ConversationMergeNotificationPropsType } from './ConversationMergeNotification'; import { ConversationMergeNotification } from './ConversationMergeNotification'; -import type { PropsDataType as PhoneNumberDiscoveryNotificationPropsType } from './PhoneNumberDiscoveryNotification'; -import { PhoneNumberDiscoveryNotification } from './PhoneNumberDiscoveryNotification'; import type { FullJSXType } from '../Intl'; import { TimelineMessage } from './TimelineMessage'; @@ -119,10 +117,6 @@ type ConversationMergeNotificationType = { type: 'conversationMerge'; data: ConversationMergeNotificationPropsType; }; -type PhoneNumberDiscoveryNotificationType = { - type: 'phoneNumberDiscovery'; - data: PhoneNumberDiscoveryNotificationPropsType; -}; type PaymentEventType = { type: 'paymentEvent'; data: Omit; @@ -138,7 +132,6 @@ export type TimelineItemType = ( | GroupV1MigrationType | GroupV2ChangeType | MessageType - | PhoneNumberDiscoveryNotificationType | ProfileChangeNotificationType | ResetSessionNotificationType | SafetyNumberNotificationType @@ -319,14 +312,6 @@ export class TimelineItem extends React.PureComponent { i18n={i18n} /> ); - } else if (item.type === 'phoneNumberDiscovery') { - notification = ( - - ); } else if (item.type === 'resetSessionNotification') { notification = ( diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 1c636538888..664c47c9aa6 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -176,7 +176,6 @@ export type MessageAttributesType = { | 'incoming' | 'keychange' | 'outgoing' - | 'phone-number-discovery' | 'profile-change' | 'story' | 'timer-notification' @@ -209,9 +208,6 @@ export type MessageAttributesType = { source?: string; sourceUuid?: string; }; - phoneNumberDiscovery?: { - e164: string; - }; conversationMerge?: { renderInfo: ConversationRenderInfoType; }; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 89e26521850..c1c2d1bb546 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -1912,14 +1912,7 @@ export class ConversationModel extends window.Backbone }; } - updateE164( - e164?: string | null, - { - disableDiscoveryNotification, - }: { - disableDiscoveryNotification?: boolean; - } = {} - ): void { + updateE164(e164?: string | null): void { const oldValue = this.get('e164'); if (e164 === oldValue) { return; @@ -1927,15 +1920,6 @@ export class ConversationModel extends window.Backbone this.set('e164', e164 || undefined); - // We just discovered a new phone number for this account. If we're not merging - // then we'll add a standalone notification here. - const haveSentMessage = Boolean( - this.get('profileSharing') || this.get('sentMessageCount') - ); - if (!oldValue && e164 && haveSentMessage && !disableDiscoveryNotification) { - void this.addPhoneNumberDiscovery(e164); - } - // This user changed their phone number if (oldValue && e164) { void this.addChangeNumberNotification(oldValue, e164); @@ -3083,43 +3067,6 @@ export class ConversationModel extends window.Backbone }); } - async addPhoneNumberDiscovery(e164: string): Promise { - log.info( - `addPhoneNumberDiscovery/${this.idForLogging()}: Adding for ${e164}` - ); - - const timestamp = Date.now(); - const message: MessageAttributesType = { - id: generateGuid(), - conversationId: this.id, - type: 'phone-number-discovery', - sent_at: timestamp, - timestamp, - received_at: window.Signal.Util.incrementMessageCounter(), - received_at_ms: timestamp, - phoneNumberDiscovery: { - e164, - }, - readStatus: ReadStatus.Read, - seenStatus: SeenStatus.Unseen, - schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, - }; - - const id = await window.Signal.Data.saveMessage(message, { - ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(), - forceSave: true, - }); - const model = window.MessageController.register( - id, - new window.Whisper.Message({ - ...message, - id, - }) - ); - - this.trigger('newmessage', model); - } - async addConversationMerge( renderInfo: ConversationRenderInfoType ): Promise { @@ -4845,7 +4792,6 @@ export class ConversationModel extends window.Backbone // same before/after string, even if someone is moving from just first name to // first/last name in their profile data. const nameChanged = oldName !== newName; - if (!isMe(this.attributes) && hadPreviousName && nameChanged) { const change = { type: 'name', @@ -4902,8 +4848,10 @@ export class ConversationModel extends window.Backbone profileKey: string | undefined, { viaStorageServiceSync = false } = {} ): Promise { + const oldProfileKey = this.get('profileKey'); + // profileKey is a string so we can compare it directly - if (this.get('profileKey') !== profileKey) { + if (oldProfileKey !== profileKey) { log.info( `Setting sealedSender to UNKNOWN for conversation ${this.idForLogging()}` ); diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 57a100aef64..5891ed6f77d 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -115,7 +115,6 @@ import { isVerifiedChange, processBodyRanges, isConversationMerge, - isPhoneNumberDiscovery, } from '../state/selectors/message'; import { isInCall, @@ -174,8 +173,7 @@ import { GiftBadgeStates } from '../components/conversation/Message'; import { downloadAttachment } from '../util/downloadAttachment'; import type { StickerWithHydratedData } from '../types/Stickers'; import { getStringForConversationMerge } from '../util/getStringForConversationMerge'; -import { getStringForPhoneNumberDiscovery } from '../util/getStringForPhoneNumberDiscovery'; -import { getTitle, renderNumber } from '../util/getTitle'; +import { getTitleNoDefault, getNumber } from '../util/getTitle'; import dataInterface from '../sql/Client'; function isSameUuid( @@ -409,7 +407,6 @@ export class MessageModel extends window.Backbone.Model { !isGroupV1Migration(attributes) && !isGroupV2Change(attributes) && !isKeyChange(attributes) && - !isPhoneNumberDiscovery(attributes) && !isProfileChange(attributes) && !isUniversalTimerNotification(attributes) && !isUnsupportedMessage(attributes) && @@ -499,7 +496,10 @@ export class MessageModel extends window.Backbone.Model { return { text: getStringForConversationMerge({ - obsoleteConversationTitle: getTitle( + obsoleteConversationTitle: getTitleNoDefault( + attributes.conversationMerge.renderInfo + ), + obsoleteConversationNumber: getNumber( attributes.conversationMerge.renderInfo ), conversationTitle: conversation.getTitle(), @@ -508,24 +508,6 @@ export class MessageModel extends window.Backbone.Model { }; } - if (isPhoneNumberDiscovery(attributes)) { - const conversation = this.getConversation(); - strictAssert(conversation, 'getNotificationData/isPhoneNumberDiscovery'); - strictAssert( - attributes.phoneNumberDiscovery, - 'getNotificationData/isPhoneNumberDiscovery/phoneNumberDiscovery' - ); - - return { - text: getStringForPhoneNumberDiscovery({ - phoneNumber: renderNumber(attributes.phoneNumberDiscovery.e164), - conversationTitle: conversation.getTitle(), - sharedGroup: conversation.get('sharedGroupNames')?.[0], - i18n: window.i18n, - }), - }; - } - if (isChatSessionRefreshed(attributes)) { return { emoji: '🔁', @@ -1219,7 +1201,6 @@ export class MessageModel extends window.Backbone.Model { const isUniversalTimerNotificationValue = isUniversalTimerNotification(attributes); const isConversationMergeValue = isConversationMerge(attributes); - const isPhoneNumberDiscoveryValue = isPhoneNumberDiscovery(attributes); const isPayment = messageHasPaymentEvent(attributes); @@ -1251,8 +1232,7 @@ export class MessageModel extends window.Backbone.Model { isKeyChangeValue || isProfileChangeValue || isUniversalTimerNotificationValue || - isConversationMergeValue || - isPhoneNumberDiscoveryValue; + isConversationMergeValue; return !hasSomethingToDisplay; } diff --git a/ts/sql/migrations/73-remove-phone-number-discovery.ts b/ts/sql/migrations/73-remove-phone-number-discovery.ts new file mode 100644 index 00000000000..ee3766ad1bb --- /dev/null +++ b/ts/sql/migrations/73-remove-phone-number-discovery.ts @@ -0,0 +1,112 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Database } from '@signalapp/better-sqlite3'; + +import type { LoggerType } from '../../types/Logging'; + +export default function updateToSchemaVersion73( + currentVersion: number, + db: Database, + logger: LoggerType +): void { + if (currentVersion >= 73) { + return; + } + + db.transaction(() => { + db.exec( + ` + --- Delete deprecated notifications + DELETE FROM messages WHERE type IS 'phone-number-discovery'; + + --- These will be re-added below + DROP INDEX messages_preview; + DROP INDEX messages_activity; + DROP INDEX message_user_initiated; + + --- These will also be re-added below + ALTER TABLE messages DROP COLUMN shouldAffectActivity; + ALTER TABLE messages DROP COLUMN shouldAffectPreview; + ALTER TABLE messages DROP COLUMN isUserInitiatedMessage; + + --- Note: These generated columns were originally introduced in migration 71, and + --- are mostly the same + + --- (change: removed phone-number-discovery) + ALTER TABLE messages + ADD COLUMN shouldAffectActivity INTEGER + GENERATED ALWAYS AS ( + type IS NULL + OR + type NOT IN ( + 'change-number-notification', + 'conversation-merge', + 'group-v1-migration', + 'keychange', + 'message-history-unsynced', + 'profile-change', + 'story', + 'universal-timer-notification', + 'verified-change' + ) + ); + + --- (change: removed phone-number-discovery + --- (now matches the above list) + ALTER TABLE messages + ADD COLUMN shouldAffectPreview INTEGER + GENERATED ALWAYS AS ( + type IS NULL + OR + type NOT IN ( + 'change-number-notification', + 'conversation-merge', + 'group-v1-migration', + 'keychange', + 'message-history-unsynced', + 'profile-change', + 'story', + 'universal-timer-notification', + 'verified-change' + ) + ); + + --- Note: This list only differs from the above on these types: + --- group-v2-change + + --- (change: removed phone-number-discovery + ALTER TABLE messages + ADD COLUMN isUserInitiatedMessage INTEGER + GENERATED ALWAYS AS ( + type IS NULL + OR + type NOT IN ( + 'change-number-notification', + 'conversation-merge', + 'group-v1-migration', + 'group-v2-change', + 'keychange', + 'message-history-unsynced', + 'profile-change', + 'story', + 'universal-timer-notification', + 'verified-change' + ) + ); + + CREATE INDEX messages_preview ON messages + (conversationId, shouldAffectPreview, isGroupLeaveEventFromOther, expiresAt, received_at, sent_at); + + CREATE INDEX messages_activity ON messages + (conversationId, shouldAffectActivity, isTimerChangeFromSync, isGroupLeaveEventFromOther, received_at, sent_at); + + CREATE INDEX message_user_initiated ON messages (isUserInitiatedMessage); + ` + ); + + db.pragma('user_version = 73'); + })(); + + logger.info('updateToSchemaVersion73: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index b85de24b92e..1454715876a 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -48,6 +48,7 @@ import updateToSchemaVersion69 from './69-group-call-ring-cancellations'; import updateToSchemaVersion70 from './70-story-reply-index'; import updateToSchemaVersion71 from './71-merge-notifications'; import updateToSchemaVersion72 from './72-optimize-call-id-message-lookup'; +import updateToSchemaVersion73 from './73-remove-phone-number-discovery'; function updateToSchemaVersion1( currentVersion: number, @@ -1965,6 +1966,7 @@ export const SCHEMA_VERSIONS = [ updateToSchemaVersion70, updateToSchemaVersion71, updateToSchemaVersion72, + updateToSchemaVersion73, ]; export function updateSchema(db: Database, logger: LoggerType): void { diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index e7ea6c2a1af..5c77ce6dceb 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -34,7 +34,6 @@ import type { PropsDataType as GroupV1MigrationPropsType } from '../../component import type { PropsDataType as DeliveryIssuePropsType } from '../../components/conversation/DeliveryIssueNotification'; import type { PropsType as PaymentEventNotificationPropsType } from '../../components/conversation/PaymentEventNotification'; import type { PropsDataType as ConversationMergePropsType } from '../../components/conversation/ConversationMergeNotification'; -import type { PropsDataType as PhoneNumberDiscoveryPropsType } from '../../components/conversation/PhoneNumberDiscoveryNotification'; import type { PropsData as GroupNotificationProps, ChangeType, @@ -119,7 +118,7 @@ import { calculateExpirationTimestamp } from '../../util/expirationTimer'; import { isSignalConversation } from '../../util/isSignalConversation'; import type { AnyPaymentEvent } from '../../types/Payment'; import { isPaymentNotificationEvent } from '../../types/Payment'; -import { getTitle, renderNumber } from '../../util/getTitle'; +import { getTitleNoDefault, getNumber } from '../../util/getTitle'; export { isIncoming, isOutgoing, isStory }; @@ -895,13 +894,6 @@ export function getPropsForBubble( timestamp, }; } - if (isPhoneNumberDiscovery(message)) { - return { - type: 'phoneNumberDiscovery', - data: getPhoneNumberDiscovery(message, options), - timestamp, - }; - } if ( messageHasPaymentEvent(message) && @@ -1422,37 +1414,16 @@ export function getPropsForConversationMerge( const conversation = getConversation(message, conversationSelector); const conversationTitle = conversation.title; - const { type, e164 } = conversationMerge.renderInfo; - const obsoleteConversationTitle = e164 ? getTitle({ type, e164 }) : undefined; + const { renderInfo } = conversationMerge; + const obsoleteConversationTitle = getTitleNoDefault(renderInfo); + const obsoleteConversationNumber = getNumber(renderInfo); return { conversationTitle, obsoleteConversationTitle, + obsoleteConversationNumber, }; } -export function isPhoneNumberDiscovery( - message: MessageWithUIFieldsType -): boolean { - return message.type === 'phone-number-discovery'; -} -export function getPhoneNumberDiscovery( - message: MessageWithUIFieldsType, - { conversationSelector }: GetPropsForBubbleOptions -): PhoneNumberDiscoveryPropsType { - const { phoneNumberDiscovery } = message; - if (!phoneNumberDiscovery) { - throw new Error( - 'getPhoneNumberDiscovery: message is missing phoneNumberDiscovery!' - ); - } - - const conversation = getConversation(message, conversationSelector); - const conversationTitle = conversation.title; - const sharedGroup = conversation.sharedGroupNames[0]; - const phoneNumber = renderNumber(phoneNumberDiscovery.e164); - - return { conversationTitle, sharedGroup, phoneNumber }; -} // Delivery Issue diff --git a/ts/test-mock/pnp/learn_test.ts b/ts/test-mock/pnp/learn_test.ts deleted file mode 100644 index 9e7526e35c9..00000000000 --- a/ts/test-mock/pnp/learn_test.ts +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { assert } from 'chai'; -import { UUIDKind, Proto, StorageState } from '@signalapp/mock-server'; -import type { PrimaryDevice } from '@signalapp/mock-server'; -import createDebug from 'debug'; - -import * as durations from '../../util/durations'; -import { Bootstrap } from '../bootstrap'; -import type { App } from '../bootstrap'; - -export const debug = createDebug('mock:test:pni-signature'); - -describe('pnp/learn', function needsName() { - this.timeout(durations.MINUTE); - - let bootstrap: Bootstrap; - let app: App; - let contactA: PrimaryDevice; - - beforeEach(async () => { - bootstrap = new Bootstrap(); - await bootstrap.init(); - - const { server, phone } = bootstrap; - - contactA = await server.createPrimaryDevice({ - profileName: 'contactA', - }); - - let state = StorageState.getEmpty(); - - state = state.updateAccount({ - profileKey: phone.profileKey.serialize(), - e164: phone.device.number, - }); - - state = state.addContact( - contactA, - { - whitelisted: false, - identityKey: contactA.getPublicKey(UUIDKind.ACI).serialize(), - serviceE164: undefined, - givenName: 'ContactA', - }, - UUIDKind.ACI - ); - - // Just to make PNI Contact visible in the left pane - state = state.pin(contactA, UUIDKind.ACI); - - await phone.setStorageState(state); - - app = await bootstrap.link(); - }); - - afterEach(async function after() { - if (this.currentTest?.state !== 'passed') { - await bootstrap.saveLogs(app); - } - - await app.close(); - await bootstrap.teardown(); - }); - - it('shows Learned Number notification if we find out number later', async () => { - const { desktop, phone } = bootstrap; - - const window = await app.getWindow(); - - debug('Open conversation with contactA'); - { - const leftPane = window.locator('.left-pane-wrapper'); - - await leftPane - .locator('_react=ConversationListItem[title = "ContactA"]') - .click(); - - await window.locator('.module-conversation-hero').waitFor(); - } - - debug('Verify starting state'); - { - // No messages - const messages = window.locator('.module-message__text'); - assert.strictEqual(await messages.count(), 0, 'message count'); - - // No notifications - const notifications = window.locator('.SystemMessage'); - assert.strictEqual(await notifications.count(), 0, 'notification count'); - } - - debug('Send message to contactA'); - { - const composeArea = window.locator( - '.composition-area-wrapper, .conversation .ConversationView' - ); - const compositionInput = composeArea.locator('_react=CompositionInput'); - - await compositionInput.type('message to contactA'); - await compositionInput.press('Enter'); - } - - debug('Wait for the message to contactA'); - { - const { source, body } = await contactA.waitForMessage(); - - assert.strictEqual( - source, - desktop, - 'first message must have valid source' - ); - assert.strictEqual( - body, - 'message to contactA', - 'message must have correct body' - ); - } - - debug('Add phone number to contactA via storage service'); - { - const state = await phone.expectStorageState('consistency check'); - const updated = await phone.setStorageState( - state - .removeRecord( - item => - item.record.contact?.serviceUuid === - contactA.device.getUUIDByKind(UUIDKind.ACI) - ) - .addContact( - contactA, - { - identityState: Proto.ContactRecord.IdentityState.DEFAULT, - whitelisted: true, - identityKey: contactA.getPublicKey(UUIDKind.ACI).serialize(), - givenName: 'ContactA', - serviceE164: contactA.device.number, - }, - UUIDKind.ACI - ) - ); - - const updatedStorageVersion = updated.version; - - await phone.sendFetchStorage({ - timestamp: bootstrap.getTimestamp(), - }); - - await app.waitForManifestVersion(updatedStorageVersion); - } - - debug('Verify final state'); - { - // One outgoing message - const messages = window.locator('.module-message__text'); - assert.strictEqual(await messages.count(), 1, 'messages'); - - // One 'learned number' notification - const notifications = window.locator('.SystemMessage'); - assert.strictEqual(await notifications.count(), 1, 'notifications'); - - const first = await notifications.first(); - assert.match(await first.innerText(), /belongs to ContactA$/); - } - }); - - it('Does not show Learned Number notification if no sent, not in allowlist', async () => { - const { phone } = bootstrap; - - const window = await app.getWindow(); - - debug('Open conversation with contactA'); - { - const leftPane = window.locator('.left-pane-wrapper'); - - await leftPane - .locator('_react=ConversationListItem[title = "ContactA"]') - .click(); - - await window.locator('.module-conversation-hero').waitFor(); - } - - debug('Verify starting state'); - { - // No messages - const messages = window.locator('.module-message__text'); - assert.strictEqual(await messages.count(), 0, 'message count'); - - // No notifications - const notifications = window.locator('.SystemMessage'); - assert.strictEqual(await notifications.count(), 0, 'notification count'); - } - - debug('Add phone number to contactA via storage service'); - { - const state = await phone.expectStorageState('consistency check'); - const updated = await phone.setStorageState( - state - .removeRecord( - item => - item.record.contact?.serviceUuid === - contactA.device.getUUIDByKind(UUIDKind.ACI) - ) - .addContact( - contactA, - { - identityState: Proto.ContactRecord.IdentityState.DEFAULT, - whitelisted: false, - identityKey: contactA.getPublicKey(UUIDKind.ACI).serialize(), - givenName: 'ContactA', - serviceE164: contactA.device.number, - }, - UUIDKind.ACI - ) - ); - - const updatedStorageVersion = updated.version; - - await phone.sendFetchStorage({ - timestamp: bootstrap.getTimestamp(), - }); - - await app.waitForManifestVersion(updatedStorageVersion); - } - - debug('Verify final state'); - { - // No messages - const messages = window.locator('.module-message__text'); - assert.strictEqual(await messages.count(), 0, 'messages'); - - // No 'learned number' notification - const notifications = window.locator('.SystemMessage'); - assert.strictEqual(await notifications.count(), 0, 'notifications'); - } - }); -}); diff --git a/ts/test-mock/pnp/merge_test.ts b/ts/test-mock/pnp/merge_test.ts index 959d7829685..517272949fd 100644 --- a/ts/test-mock/pnp/merge_test.ts +++ b/ts/test-mock/pnp/merge_test.ts @@ -220,7 +220,7 @@ describe('pnp/merge', function needsName() { const first = await notifications.first(); assert.match( await first.innerText(), - /and ACI Contact are the same account. Your message history for both chats are here./ + /Your message history with ACI Contact and their number .* has been merged./ ); } }); diff --git a/ts/test-mock/pnp/pni_signature_test.ts b/ts/test-mock/pnp/pni_signature_test.ts index bbfd752245f..67485998535 100644 --- a/ts/test-mock/pnp/pni_signature_test.ts +++ b/ts/test-mock/pnp/pni_signature_test.ts @@ -339,6 +339,7 @@ describe('pnp/PNI Signature', function needsName() { } debug('Verify final state'); + { const newState = await phone.waitForStorageState({ after: state, diff --git a/ts/util/getStringForConversationMerge.ts b/ts/util/getStringForConversationMerge.ts index 4bcd695ef3f..a2a424795e1 100644 --- a/ts/util/getStringForConversationMerge.ts +++ b/ts/util/getStringForConversationMerge.ts @@ -5,19 +5,28 @@ import type { LocalizerType } from '../types/Util'; export function getStringForConversationMerge({ obsoleteConversationTitle, + obsoleteConversationNumber, conversationTitle, i18n, }: { obsoleteConversationTitle: string | undefined; + obsoleteConversationNumber: string | undefined; conversationTitle: string; i18n: LocalizerType; }): string { if (!obsoleteConversationTitle) { - return i18n('icu:ConversationMerge--notification--no-e164', { + return i18n('icu:ConversationMerge--notification--no-title', { conversationTitle, }); } + if (obsoleteConversationNumber) { + return i18n('icu:ConversationMerge--notification--with-e164', { + conversationTitle, + obsoleteConversationNumber, + }); + } + return i18n('icu:ConversationMerge--notification', { obsoleteConversationTitle, conversationTitle, diff --git a/ts/util/getStringForPhoneNumberDiscovery.ts b/ts/util/getStringForPhoneNumberDiscovery.ts deleted file mode 100644 index d8291d926b8..00000000000 --- a/ts/util/getStringForPhoneNumberDiscovery.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import type { LocalizerType } from '../types/Util'; - -export function getStringForPhoneNumberDiscovery({ - phoneNumber, - i18n, - conversationTitle, - sharedGroup, -}: { - phoneNumber: string; - i18n: LocalizerType; - conversationTitle: string; - sharedGroup?: string; -}): string { - if (sharedGroup) { - return i18n('icu:PhoneNumberDiscovery--notification--withSharedGroup', { - phoneNumber, - conversationTitle, - sharedGroup, - }); - } - - return i18n('icu:PhoneNumberDiscovery--notification--noSharedGroup', { - phoneNumber, - conversationTitle, - }); -}