Deprecate phone number discovery notification
This commit is contained in:
parent
63509b8965
commit
d7b09b9703
21 changed files with 196 additions and 517 deletions
|
@ -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",
|
||||
|
|
1
images/icons/v2/merge-16.svg
Normal file
1
images/icons/v2/merge-16.svg
Normal 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 |
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
applyChangeToConversation(targetConversation, {
|
||||
[key]: value,
|
||||
},
|
||||
targetConversationWasCreated
|
||||
);
|
||||
});
|
||||
} 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,
|
||||
{
|
||||
applyChangeToConversation(targetConversation, {
|
||||
[unused.key]: unused.value,
|
||||
},
|
||||
targetConversationWasCreated
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
{
|
||||
applyChangeToConversation(targetConversation, {
|
||||
[key]: value,
|
||||
},
|
||||
willMerge || targetConversationWasCreated
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
{
|
||||
applyChangeToConversation(targetConversation, {
|
||||
[key]: value,
|
||||
},
|
||||
targetConversationWasCreated
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -338,6 +338,7 @@ export function ConversationList({
|
|||
'typingContactId',
|
||||
'unblurredAvatarPath',
|
||||
'unreadCount',
|
||||
'uuid',
|
||||
]);
|
||||
const { badges, title, unreadCount, lastMessage } = itemProps;
|
||||
result = (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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} />} />;
|
||||
}
|
|
@ -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
4
ts/model-types.d.ts
vendored
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()}`
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
112
ts/sql/migrations/73-remove-phone-number-discovery.ts
Normal file
112
ts/sql/migrations/73-remove-phone-number-discovery.ts
Normal 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!');
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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./
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -339,6 +339,7 @@ describe('pnp/PNI Signature', function needsName() {
|
|||
}
|
||||
|
||||
debug('Verify final state');
|
||||
|
||||
{
|
||||
const newState = await phone.waitForStorageState({
|
||||
after: state,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue