Use other sync messages for conversations missing ACI

This commit is contained in:
Scott Nonnenberg 2025-02-07 00:40:25 +10:00 committed by GitHub
commit c279108c75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 224 additions and 93 deletions

View file

@ -220,6 +220,8 @@ import {
import { markFailed } from '../../test-node/util/messageFailures'; import { markFailed } from '../../test-node/util/messageFailures';
import { cleanupMessages } from '../../util/cleanup'; import { cleanupMessages } from '../../util/cleanup';
import { MessageModel } from '../../models/messages'; import { MessageModel } from '../../models/messages';
import type { ConversationModel } from '../../models/conversations';
// State // State
export type DBConversationType = ReadonlyDeep<{ export type DBConversationType = ReadonlyDeep<{
@ -3579,17 +3581,10 @@ function revokePendingMembershipsFromGroupV2(
} }
async function syncMessageRequestResponse( async function syncMessageRequestResponse(
conversationData: ConversationType, conversation: ConversationModel,
response: Proto.SyncMessage.MessageRequestResponse.Type, response: Proto.SyncMessage.MessageRequestResponse.Type,
{ shouldSave = true } = {} { shouldSave = true } = {}
): Promise<void> { ): Promise<void> {
const conversation = window.ConversationController.get(conversationData.id);
if (!conversation) {
throw new Error(
`syncMessageRequestResponse: No conversation found for conversation ${conversationData.id}`
);
}
// In GroupsV2, this may modify the server. We only want to continue if those // In GroupsV2, this may modify the server. We only want to continue if those
// server updates were successful. // server updates were successful.
await conversation.applyMessageRequestResponse(response, { shouldSave }); await conversation.applyMessageRequestResponse(response, { shouldSave });
@ -3603,11 +3598,18 @@ async function syncMessageRequestResponse(
return; return;
} }
const threadAci = conversation.getAci();
if (!threadAci) {
log.warn(
'syncMessageRequestResponse: No ACI for target conversation, not sending'
);
return;
}
try { try {
await singleProtoJobQueue.add( await singleProtoJobQueue.add(
MessageSender.getMessageRequestResponseSync({ MessageSender.getMessageRequestResponseSync({
threadE164: conversation.get('e164'), threadAci,
threadAci: conversation.getAci(),
groupId, groupId,
type: response, type: response,
}) })
@ -3651,7 +3653,13 @@ function reportSpam(
} }
const conversation = getConversationForReportSpam(conversationOrGroup); const conversation = getConversationForReportSpam(conversationOrGroup);
if (conversation == null) { const conversationModel = window.ConversationController.get(
conversation?.id
);
if (!conversation || !conversationModel) {
log.error(
`reportSpam: Conversation for report spam not found ${conversation?.id}. Doing nothing.`
);
return; return;
} }
@ -3664,7 +3672,10 @@ function reportSpam(
idForLogging, idForLogging,
task: async () => { task: async () => {
await Promise.all([ await Promise.all([
syncMessageRequestResponse(conversation, messageRequestEnum.SPAM), syncMessageRequestResponse(
conversationModel,
messageRequestEnum.SPAM
),
addReportSpamJob({ addReportSpamJob({
conversation, conversation,
getMessageServerGuidsForSpam: getMessageServerGuidsForSpam:
@ -3700,68 +3711,112 @@ function blockAndReportSpam(
const conversationForSpam = const conversationForSpam =
getConversationForReportSpam(conversationOrGroup); getConversationForReportSpam(conversationOrGroup);
const conversationModel = window.ConversationController.get(
conversationForSpam?.id
);
if (!conversationForSpam || !conversationModel) {
log.error(
`reportSpam: Conversation for report spam not found ${conversationForSpam?.id}. Doing nothing.`
);
return;
}
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
const idForLogging = getConversationIdForLogging(conversationOrGroup); const idForLogging = getConversationIdForLogging(conversationOrGroup);
drop( if (conversationModel.getAci()) {
longRunningTaskWrapper({ drop(
name: 'blockAndReportSpam', longRunningTaskWrapper({
idForLogging, name: 'blockAndReportSpam',
task: async () => { idForLogging,
await Promise.all([ task: async () => {
syncMessageRequestResponse( await Promise.all([
conversationOrGroup, syncMessageRequestResponse(
messageRequestEnum.BLOCK_AND_SPAM conversationModel,
), messageRequestEnum.BLOCK_AND_SPAM
conversationForSpam != null && ),
addReportSpamJob({ conversationForSpam != null &&
conversation: conversationForSpam, addReportSpamJob({
getMessageServerGuidsForSpam: conversation: conversationForSpam,
DataReader.getMessageServerGuidsForSpam, getMessageServerGuidsForSpam:
jobQueue: reportSpamJobQueue, DataReader.getMessageServerGuidsForSpam,
}), jobQueue: reportSpamJobQueue,
]); }),
]);
dispatch({ dispatch({
type: SHOW_TOAST, type: SHOW_TOAST,
payload: { payload: {
toastType: ToastType.ReportedSpamAndBlocked, toastType: ToastType.ReportedSpamAndBlocked,
}, },
}); });
}, },
}) })
); );
} else {
try {
await singleProtoJobQueue.add(
MessageSender.getBlockSync(
window.textsecure.storage.blocked.getBlockedData()
)
);
} catch (error) {
log.error(
`blockConversation/${idForLogging}: Failed to queue block sync message`,
Errors.toLogFormat(error)
);
}
}
}; };
} }
function acceptConversation( function acceptConversation(
conversationId: string conversationId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> { ): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return async (dispatch, getState) => { return async dispatch => {
const conversationSelector = getConversationSelector(getState()); const conversation = window.ConversationController.get(conversationId);
const conversationOrGroup = conversationSelector(conversationId); if (!conversation) {
if (!conversationOrGroup) {
throw new Error( throw new Error(
'acceptConversation: Expected a conversation to be found. Doing nothing' 'acceptConversation: Expected a conversation to be found. Doing nothing'
); );
} }
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
const idForLogging = getConversationIdForLogging(conversationOrGroup); const idForLogging = getConversationIdForLogging(conversation.attributes);
drop( if (conversation.getAci()) {
longRunningTaskWrapper({ drop(
name: 'acceptConversation', longRunningTaskWrapper({
idForLogging, name: 'acceptConversation',
task: async () => { idForLogging,
await syncMessageRequestResponse( task: async () => {
conversationOrGroup, await syncMessageRequestResponse(
messageRequestEnum.ACCEPT conversation,
); messageRequestEnum.ACCEPT
}, );
}) },
); })
);
} else {
await conversation.applyMessageRequestResponse(
messageRequestEnum.ACCEPT,
{
shouldSave: true,
}
);
try {
await singleProtoJobQueue.add(
MessageSender.getBlockSync(
window.textsecure.storage.blocked.getBlockedData()
)
);
} catch (error) {
log.error(
`acceptConversation/${idForLogging}: Failed to queue sync message`,
Errors.toLogFormat(error)
);
}
}
dispatch({ dispatch({
type: 'NOOP', type: 'NOOP',
@ -3794,10 +3849,8 @@ function removeConversation(conversationId: string): ShowToastActionType {
function blockConversation( function blockConversation(
conversationId: string conversationId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> { ): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return (dispatch, getState) => { return async dispatch => {
const conversationSelector = getConversationSelector(getState()); const conversation = window.ConversationController.get(conversationId);
const conversation = conversationSelector(conversationId);
if (!conversation) { if (!conversation) {
throw new Error( throw new Error(
'blockConversation: Expected a conversation to be found. Doing nothing' 'blockConversation: Expected a conversation to be found. Doing nothing'
@ -3805,20 +3858,41 @@ function blockConversation(
} }
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
const idForLogging = getConversationIdForLogging(conversation); const idForLogging = getConversationIdForLogging(conversation.attributes);
drop( if (conversation.getAci()) {
longRunningTaskWrapper({ drop(
name: 'blockConversation', longRunningTaskWrapper({
idForLogging, name: 'blockConversation',
task: async () => { idForLogging,
await syncMessageRequestResponse( task: async () => {
conversation, await syncMessageRequestResponse(
messageRequestEnum.BLOCK conversation,
); messageRequestEnum.BLOCK
}, );
}) },
); })
);
} else {
// In GroupsV2, this may modify the server. We only want to continue if those
// server updates were successful.
await conversation.applyMessageRequestResponse(messageRequestEnum.BLOCK, {
shouldSave: true,
});
try {
await singleProtoJobQueue.add(
MessageSender.getBlockSync(
window.textsecure.storage.blocked.getBlockedData()
)
);
} catch (error) {
log.error(
`blockConversation/${idForLogging}: Failed to queue block sync message`,
Errors.toLogFormat(error)
);
}
}
dispatch({ dispatch({
type: 'NOOP', type: 'NOOP',
@ -3830,9 +3904,8 @@ function blockConversation(
function deleteConversation( function deleteConversation(
conversationId: string conversationId: string
): ThunkAction<void, RootStateType, unknown, NoopActionType> { ): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return (dispatch, getState) => { return async dispatch => {
const conversationSelector = getConversationSelector(getState()); const conversation = window.ConversationController.get(conversationId);
const conversation = conversationSelector(conversationId);
if (!conversation) { if (!conversation) {
throw new Error( throw new Error(
'deleteConversation: Expected a conversation to be found. Doing nothing' 'deleteConversation: Expected a conversation to be found. Doing nothing'
@ -3840,20 +3913,24 @@ function deleteConversation(
} }
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
const idForLogging = getConversationIdForLogging(conversation); const idForLogging = getConversationIdForLogging(conversation.attributes);
drop( if (conversation.getAci()) {
longRunningTaskWrapper({ drop(
name: 'deleteConversation', longRunningTaskWrapper({
idForLogging, name: 'deleteConversation',
task: async () => { idForLogging,
await syncMessageRequestResponse( task: async () => {
conversation, await syncMessageRequestResponse(
messageRequestEnum.DELETE conversation,
); messageRequestEnum.DELETE
}, );
}) },
); })
);
} else {
await conversation.destroyMessages({ source: 'local-delete' });
}
dispatch({ dispatch({
type: 'NOOP', type: 'NOOP',

View file

@ -1796,6 +1796,40 @@ export default class MessageSender {
}); });
} }
static getBlockSync(
options: Readonly<{
e164s: Array<string>;
acis: Array<string>;
groupIds: Array<Uint8Array>;
}>
): SingleProtoJobData {
const myAci = window.textsecure.storage.user.getCheckedAci();
const syncMessage = MessageSender.createSyncMessage();
const blocked = new Proto.SyncMessage.Blocked();
blocked.numbers = options.e164s;
blocked.acis = options.acis;
blocked.groupIds = options.groupIds;
syncMessage.blocked = blocked;
const contentMessage = new Proto.Content();
contentMessage.syncMessage = syncMessage;
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return {
contentHint: ContentHint.RESENDABLE,
serviceId: myAci,
isSyncMessage: true,
protoBase64: Bytes.toBase64(
Proto.Content.encode(contentMessage).finish()
),
type: 'blockSync',
urgent: false,
};
}
static getMessageRequestResponseSync( static getMessageRequestResponseSync(
options: Readonly<{ options: Readonly<{
threadE164?: string; threadE164?: string;

View file

@ -3,9 +3,11 @@
import { without } from 'lodash'; import { without } from 'lodash';
import type { StorageInterface } from '../../types/Storage.d';
import type { ServiceIdString } from '../../types/ServiceId';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import * as Bytes from '../../Bytes';
import { isAciString } from '../../util/isAciString';
import type { StorageInterface } from '../../types/Storage.d';
import type { AciString, ServiceIdString } from '../../types/ServiceId';
export const BLOCKED_NUMBERS_ID = 'blocked'; export const BLOCKED_NUMBERS_ID = 'blocked';
export const BLOCKED_UUIDS_ID = 'blocked-uuids'; export const BLOCKED_UUIDS_ID = 'blocked-uuids';
@ -99,4 +101,22 @@ export class Blocked {
log.info(`removing group(${groupId} from blocked list`); log.info(`removing group(${groupId} from blocked list`);
await this.storage.put(BLOCKED_GROUPS_ID, without(groupIds, groupId)); await this.storage.put(BLOCKED_GROUPS_ID, without(groupIds, groupId));
} }
public getBlockedData(): {
e164s: Array<string>;
acis: Array<AciString>;
groupIds: Array<Uint8Array>;
} {
const e164s = this.getBlockedNumbers();
const acis = this.getBlockedServiceIds().filter(item => isAciString(item));
const groupIds = this.getBlockedGroups().map(item =>
Bytes.fromBase64(item)
);
return {
e164s,
acis,
groupIds,
};
}
} }

View file

@ -49,13 +49,13 @@ export const sendTypesEnum = z.enum([
'pniIdentitySyncRequest', // urgent because we need our PNI to be fully functional 'pniIdentitySyncRequest', // urgent because we need our PNI to be fully functional
// The actual sync messages, which we never send, just receive - non-urgent // The actual sync messages, which we never send, just receive - non-urgent
'blockSync',
'configurationSync', 'configurationSync',
'contactSync', 'contactSync',
'keySync', 'keySync',
'pniIdentitySync', 'pniIdentitySync',
// Syncs, default non-urgent // Syncs, default non-urgent
'blockSync',
'deleteForMeSync', 'deleteForMeSync',
'fetchLatestManifestSync', 'fetchLatestManifestSync',
'fetchLocalProfileSync', 'fetchLocalProfileSync',