Deprecate phone number discovery notification

This commit is contained in:
Fedor Indutny 2023-01-12 14:18:08 -08:00 committed by GitHub
parent 63509b8965
commit d7b09b9703
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 196 additions and 517 deletions

View file

@ -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",

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path fill="#000" fill-rule="evenodd" d="M7.6 2.533a.5.5 0 0 1 .8 0l2 2.667a.5.5 0 0 1-.4.8H8.75v.028c0 1.424.01 2.813.457 4.075.432 1.22 1.29 2.361 3.112 3.218a.75.75 0 1 1-.638 1.358C9.74 13.765 8.622 12.518 8 11.122c-.622 1.396-1.74 2.643-3.68 3.557a.75.75 0 1 1-.64-1.358c1.822-.857 2.681-1.998 3.113-3.218.447-1.262.457-2.65.457-4.075V6H6a.5.5 0 0 1-.4-.8l2-2.667Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 473 B

View file

@ -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 {

View file

@ -49,8 +49,7 @@ function applyChangeToConversation(
conversation: ConversationModel,
suggestedChange: Partial<
Pick<ConversationAttributesType, 'uuid' | 'e164' | 'pni'>
>,
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<Promise<void>> = [];
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));
}

View file

@ -338,6 +338,7 @@ export function ConversationList({
'typingContactId',
'unblurredAvatarPath',
'unreadCount',
'uuid',
]);
const { badges, title, unreadCount, lastMessage } = itemProps;
result = (

View file

@ -17,14 +17,25 @@ export default {
const createProps = (overrideProps: Partial<PropsType> = {}): 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 <ConversationMergeNotification {...createProps()} />;
}
export function WithNoObsoleteNumber(): JSX.Element {
return (
<ConversationMergeNotification
{...createProps()}
obsoleteConversationNumber={undefined}
/>
);
}
export function WithNoObsoleteTitle(): JSX.Element {
return (
<ConversationMergeNotification

View file

@ -14,16 +14,23 @@ import { Intl } from '../Intl';
export type PropsDataType = {
conversationTitle: string;
obsoleteConversationTitle: string | undefined;
obsoleteConversationNumber: string | undefined;
};
export type PropsType = PropsDataType & {
i18n: LocalizerType;
};
export function ConversationMergeNotification(props: PropsType): JSX.Element {
const { conversationTitle, obsoleteConversationTitle, i18n } = props;
const {
conversationTitle,
obsoleteConversationTitle,
obsoleteConversationNumber,
i18n,
} = props;
const message = getStringForConversationMerge({
conversationTitle,
obsoleteConversationTitle,
obsoleteConversationNumber,
i18n,
});
@ -40,7 +47,7 @@ export function ConversationMergeNotification(props: PropsType): JSX.Element {
return (
<>
<SystemMessage
icon="profile"
icon="merge"
contents={<Emojify text={message} />}
button={
obsoleteConversationTitle ? (

View file

@ -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> = {}): PropsType => ({
i18n,
conversationTitle: overrideProps.conversationTitle || 'Mr. Fire',
phoneNumber: overrideProps.phoneNumber || '+1 (000) 123-4567',
sharedGroup: overrideProps.sharedGroup,
});
export function Basic(): JSX.Element {
return <PhoneNumberDiscoveryNotification {...createProps()} />;
}
export function WithSharedGroup(): JSX.Element {
return (
<PhoneNumberDiscoveryNotification
{...createProps({
sharedGroup: 'Animal Lovers',
})}
/>
);
}

View file

@ -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 <SystemMessage icon="profile" contents={<Emojify text={message} />} />;
}

View file

@ -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<PaymentEventNotificationPropsType, 'i18n'>;
@ -138,7 +132,6 @@ export type TimelineItemType = (
| GroupV1MigrationType
| GroupV2ChangeType
| MessageType
| PhoneNumberDiscoveryNotificationType
| ProfileChangeNotificationType
| ResetSessionNotificationType
| SafetyNumberNotificationType
@ -319,14 +312,6 @@ export class TimelineItem extends React.PureComponent<PropsType> {
i18n={i18n}
/>
);
} else if (item.type === 'phoneNumberDiscovery') {
notification = (
<PhoneNumberDiscoveryNotification
{...reducedProps}
{...item.data}
i18n={i18n}
/>
);
} else if (item.type === 'resetSessionNotification') {
notification = (
<ResetSessionNotification {...reducedProps} i18n={i18n} />

4
ts/model-types.d.ts vendored
View file

@ -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;
};

View file

@ -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<void> {
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<void> {
@ -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<boolean> {
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()}`
);

View file

@ -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<MessageAttributesType> {
!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<MessageAttributesType> {
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<MessageAttributesType> {
};
}
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<MessageAttributesType> {
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<MessageAttributesType> {
isKeyChangeValue ||
isProfileChangeValue ||
isUniversalTimerNotificationValue ||
isConversationMergeValue ||
isPhoneNumberDiscoveryValue;
isConversationMergeValue;
return !hasSomethingToDisplay;
}

View file

@ -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!');
}

View file

@ -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 {

View file

@ -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

View file

@ -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');
}
});
});

View file

@ -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./
);
}
});

View file

@ -339,6 +339,7 @@ describe('pnp/PNI Signature', function needsName() {
}
debug('Verify final state');
{
const newState = await phone.waitForStorageState({
after: state,

View file

@ -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,

View file

@ -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,
});
}