Improve story DOE flow
This commit is contained in:
parent
5e9744d62a
commit
37d383f344
15 changed files with 630 additions and 245 deletions
|
@ -16,6 +16,7 @@ import { sendNormalMessage } from './helpers/sendNormalMessage';
|
|||
import { sendDirectExpirationTimerUpdate } from './helpers/sendDirectExpirationTimerUpdate';
|
||||
import { sendGroupUpdate } from './helpers/sendGroupUpdate';
|
||||
import { sendDeleteForEveryone } from './helpers/sendDeleteForEveryone';
|
||||
import { sendDeleteStoryForEveryone } from './helpers/sendDeleteStoryForEveryone';
|
||||
import { sendProfileKey } from './helpers/sendProfileKey';
|
||||
import { sendReaction } from './helpers/sendReaction';
|
||||
import { sendStory } from './helpers/sendStory';
|
||||
|
@ -41,6 +42,7 @@ import type { UUIDStringType } from '../types/UUID';
|
|||
// these values, you'll likely need to write a database migration.
|
||||
export const conversationQueueJobEnum = z.enum([
|
||||
'DeleteForEveryone',
|
||||
'DeleteStoryForEveryone',
|
||||
'DirectExpirationTimerUpdate',
|
||||
'GroupUpdate',
|
||||
'NormalMessage',
|
||||
|
@ -61,6 +63,25 @@ export type DeleteForEveryoneJobData = z.infer<
|
|||
typeof deleteForEveryoneJobDataSchema
|
||||
>;
|
||||
|
||||
const deleteStoryForEveryoneJobDataSchema = z.object({
|
||||
type: z.literal(conversationQueueJobEnum.enum.DeleteStoryForEveryone),
|
||||
conversationId: z.string(),
|
||||
storyId: z.string(),
|
||||
targetTimestamp: z.number(),
|
||||
updatedStoryRecipients: z
|
||||
.array(
|
||||
z.object({
|
||||
destinationUuid: z.string(),
|
||||
distributionListIds: z.array(z.string()),
|
||||
isAllowedToReply: z.boolean(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
export type DeleteStoryForEveryoneJobData = z.infer<
|
||||
typeof deleteStoryForEveryoneJobDataSchema
|
||||
>;
|
||||
|
||||
const expirationTimerUpdateJobDataSchema = z.object({
|
||||
type: z.literal(conversationQueueJobEnum.enum.DirectExpirationTimerUpdate),
|
||||
conversationId: z.string(),
|
||||
|
@ -120,6 +141,7 @@ export type StoryJobData = z.infer<typeof storyJobDataSchema>;
|
|||
|
||||
export const conversationQueueJobDataSchema = z.union([
|
||||
deleteForEveryoneJobDataSchema,
|
||||
deleteStoryForEveryoneJobDataSchema,
|
||||
expirationTimerUpdateJobDataSchema,
|
||||
groupUpdateJobDataSchema,
|
||||
normalMessageSendJobDataSchema,
|
||||
|
@ -334,6 +356,9 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
|
|||
case jobSet.DeleteForEveryone:
|
||||
await sendDeleteForEveryone(conversation, jobBundle, data);
|
||||
break;
|
||||
case jobSet.DeleteStoryForEveryone:
|
||||
await sendDeleteStoryForEveryone(conversation, jobBundle, data);
|
||||
break;
|
||||
case jobSet.DirectExpirationTimerUpdate:
|
||||
await sendDirectExpirationTimerUpdate(conversation, jobBundle, data);
|
||||
break;
|
||||
|
|
|
@ -55,15 +55,22 @@ export async function sendDeleteForEveryone(
|
|||
targetTimestamp,
|
||||
} = data;
|
||||
|
||||
const logId = `sendDeleteForEveryone(${conversation.idForLogging()}, ${messageId})`;
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.error(`Failed to fetch message ${messageId}. Failing job.`);
|
||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const story = isStory(message.attributes);
|
||||
if (story && !isGroupV2(conversation.attributes)) {
|
||||
log.error(`${logId}: 1-on-1 Story DOE must use its own job. Failing job`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldContinue) {
|
||||
log.info('Ran out of time. Giving up on sending delete for everyone');
|
||||
log.info(`${logId}: Ran out of time. Giving up on sending`);
|
||||
updateMessageWithFailure(message, [new Error('Ran out of time!')], log);
|
||||
return;
|
||||
}
|
||||
|
@ -73,8 +80,6 @@ export async function sendDeleteForEveryone(
|
|||
const contentHint = ContentHint.RESENDABLE;
|
||||
const messageIds = [messageId];
|
||||
|
||||
const logId = `deleteForEveryone/${conversation.idForLogging()}`;
|
||||
|
||||
const deletedForEveryoneSendStatus = message.get(
|
||||
'deletedForEveryoneSendStatus'
|
||||
);
|
||||
|
@ -97,9 +102,8 @@ export async function sendDeleteForEveryone(
|
|||
'conversationQueue/sendDeleteForEveryone',
|
||||
async abortSignal => {
|
||||
log.info(
|
||||
`Sending deleteForEveryone to conversation ${logId}`,
|
||||
`with timestamp ${timestamp}`,
|
||||
`for message ${targetTimestamp}`
|
||||
`${logId}: Sending deleteForEveryone with timestamp ${timestamp}` +
|
||||
`for message ${targetTimestamp}, isStory=${story}`
|
||||
);
|
||||
|
||||
let profileKey: Uint8Array | undefined;
|
||||
|
|
306
ts/jobs/helpers/sendDeleteStoryForEveryone.ts
Normal file
306
ts/jobs/helpers/sendDeleteStoryForEveryone.ts
Normal file
|
@ -0,0 +1,306 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as Errors from '../../types/errors';
|
||||
import { getSendOptions } from '../../util/getSendOptions';
|
||||
import { isDirectConversation, isMe } from '../../util/whatTypeOfConversation';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import {
|
||||
handleMultipleSendErrors,
|
||||
maybeExpandErrors,
|
||||
} from './handleMultipleSendErrors';
|
||||
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
||||
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import type {
|
||||
ConversationQueueJobBundle,
|
||||
DeleteStoryForEveryoneJobData,
|
||||
} from '../conversationJobQueue';
|
||||
import { getUntrustedConversationUuids } from './getUntrustedConversationUuids';
|
||||
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
import { SendMessageProtoError } from '../../textsecure/Errors';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { isStory } from '../../messages/helpers';
|
||||
|
||||
export async function sendDeleteStoryForEveryone(
|
||||
ourConversation: ConversationModel,
|
||||
{
|
||||
isFinalAttempt,
|
||||
messaging,
|
||||
shouldContinue,
|
||||
timestamp,
|
||||
timeRemaining,
|
||||
log,
|
||||
}: ConversationQueueJobBundle,
|
||||
data: DeleteStoryForEveryoneJobData
|
||||
): Promise<void> {
|
||||
const { storyId, targetTimestamp, updatedStoryRecipients } = data;
|
||||
|
||||
const logId = `sendDeleteStoryForEveryone(${storyId})`;
|
||||
|
||||
const message = await getMessageById(storyId);
|
||||
if (!message) {
|
||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldContinue) {
|
||||
log.info(`${logId}: Ran out of time. Giving up on sending`);
|
||||
updateMessageWithFailure(message, [new Error('Ran out of time!')], log);
|
||||
return;
|
||||
}
|
||||
|
||||
strictAssert(
|
||||
isMe(ourConversation.attributes),
|
||||
'Story DOE must be sent on our conversaton'
|
||||
);
|
||||
strictAssert(isStory(message.attributes), 'Story message must be a story');
|
||||
|
||||
const sendType = 'deleteForEveryone';
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
const contentHint = ContentHint.RESENDABLE;
|
||||
|
||||
const deletedForEveryoneSendStatus = message.get(
|
||||
'deletedForEveryoneSendStatus'
|
||||
);
|
||||
strictAssert(
|
||||
deletedForEveryoneSendStatus,
|
||||
`${logId}: message does not have deletedForEveryoneSendStatus`
|
||||
);
|
||||
const recipientIds = Object.entries(deletedForEveryoneSendStatus)
|
||||
.filter(([_, isSent]) => !isSent)
|
||||
.map(([conversationId]) => conversationId);
|
||||
|
||||
const untrustedUuids = getUntrustedConversationUuids(recipientIds);
|
||||
if (untrustedUuids.length) {
|
||||
window.reduxActions.conversations.conversationStoppedByMissingVerification({
|
||||
conversationId: ourConversation.id,
|
||||
untrustedUuids,
|
||||
});
|
||||
throw new Error(
|
||||
`Delete for everyone blocked because ${untrustedUuids.length} ` +
|
||||
'conversation(s) were untrusted. Failing this attempt.'
|
||||
);
|
||||
}
|
||||
|
||||
const recipientConversations = recipientIds
|
||||
.map(conversationId => {
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
if (!conversation) {
|
||||
log.error(`${logId}: conversation not found for ${conversationId}`);
|
||||
return undefined;
|
||||
}
|
||||
if (!isDirectConversation(conversation.attributes)) {
|
||||
log.error(`${logId}: conversation ${conversationId} is not direct`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isConversationAccepted(conversation.attributes)) {
|
||||
log.info(
|
||||
`${logId}: conversation ${conversation.idForLogging()} ` +
|
||||
'is not accepted; refusing to send'
|
||||
);
|
||||
updateMessageWithFailure(
|
||||
message,
|
||||
[new Error('Message request was not accepted')],
|
||||
log
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
if (isConversationUnregistered(conversation.attributes)) {
|
||||
log.info(
|
||||
`${logId}: conversation ${conversation.idForLogging()} ` +
|
||||
'is unregistered; refusing to send'
|
||||
);
|
||||
updateMessageWithFailure(
|
||||
message,
|
||||
[new Error('Contact no longer has a Signal account')],
|
||||
log
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
if (conversation.isBlocked()) {
|
||||
log.info(
|
||||
`${logId}: conversation ${conversation.idForLogging()} ` +
|
||||
'is blocked; refusing to send'
|
||||
);
|
||||
updateMessageWithFailure(
|
||||
message,
|
||||
[new Error('Contact is blocked')],
|
||||
log
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return conversation;
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
const hadSuccessfulSends = doesMessageHaveSuccessfulSends(message);
|
||||
let didSuccessfullySendOne = false;
|
||||
|
||||
// Special case - we have no one to send it to so just send the sync message.
|
||||
if (recipientConversations.length === 0) {
|
||||
didSuccessfullySendOne = true;
|
||||
}
|
||||
|
||||
const profileKey = await ourProfileKeyService.get();
|
||||
|
||||
await Promise.all(
|
||||
recipientConversations.map(conversation => {
|
||||
return conversation.queueJob(
|
||||
'conversationQueue/sendStoryDeleteForEveryone',
|
||||
async () => {
|
||||
log.info(
|
||||
`${logId}: Sending deleteStoryForEveryone with timestamp ${timestamp}`
|
||||
);
|
||||
|
||||
const sendOptions = await getSendOptions(conversation.attributes, {
|
||||
story: true,
|
||||
});
|
||||
|
||||
try {
|
||||
await handleMessageSend(
|
||||
messaging.sendMessageToIdentifier({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
identifier: conversation.getSendTarget()!,
|
||||
messageText: undefined,
|
||||
attachments: [],
|
||||
deletedForEveryoneTimestamp: targetTimestamp,
|
||||
timestamp,
|
||||
expireTimer: undefined,
|
||||
contentHint,
|
||||
groupId: undefined,
|
||||
profileKey: conversation.get('profileSharing')
|
||||
? profileKey
|
||||
: undefined,
|
||||
options: sendOptions,
|
||||
urgent: true,
|
||||
story: true,
|
||||
}),
|
||||
{
|
||||
messageIds: [storyId],
|
||||
sendType,
|
||||
}
|
||||
);
|
||||
|
||||
didSuccessfullySendOne = true;
|
||||
|
||||
await updateMessageWithSuccessfulSends(message, {
|
||||
successfulIdentifiers: [conversation.id],
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof SendMessageProtoError) {
|
||||
await updateMessageWithSuccessfulSends(message, error);
|
||||
}
|
||||
|
||||
const errors = maybeExpandErrors(error);
|
||||
await handleMultipleSendErrors({
|
||||
errors,
|
||||
isFinalAttempt,
|
||||
log,
|
||||
markFailed: () => updateMessageWithFailure(message, errors, log),
|
||||
timeRemaining,
|
||||
toThrow: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// Send sync message exactly once per job. If any of the sends are successful
|
||||
// and we didn't send the DOE itself before - it is a good time to send the
|
||||
// sync message.
|
||||
if (!hadSuccessfulSends && didSuccessfullySendOne) {
|
||||
log.info(`${logId}: Sending sync message`);
|
||||
const options = await getSendOptions(ourConversation.attributes, {
|
||||
syncMessage: true,
|
||||
});
|
||||
|
||||
const destinationUuid = ourConversation
|
||||
.getCheckedUuid('deleteStoryForEveryone')
|
||||
.toString();
|
||||
|
||||
// Sync message for other devices
|
||||
await handleMessageSend(
|
||||
messaging.sendSyncMessage({
|
||||
destination: undefined,
|
||||
destinationUuid,
|
||||
storyMessageRecipients: updatedStoryRecipients,
|
||||
expirationStartTimestamp: null,
|
||||
isUpdate: true,
|
||||
options,
|
||||
timestamp: message.get('timestamp'),
|
||||
urgent: false,
|
||||
}),
|
||||
{ messageIds: [storyId], sendType }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function doesMessageHaveSuccessfulSends(message: MessageModel): boolean {
|
||||
const map = message.get('deletedForEveryoneSendStatus') ?? {};
|
||||
|
||||
return Object.values(map).some(value => value === true);
|
||||
}
|
||||
|
||||
async function updateMessageWithSuccessfulSends(
|
||||
message: MessageModel,
|
||||
result?: CallbackResultType | SendMessageProtoError
|
||||
): Promise<void> {
|
||||
if (!result) {
|
||||
message.set({
|
||||
deletedForEveryoneSendStatus: {},
|
||||
deletedForEveryoneFailed: undefined,
|
||||
});
|
||||
await window.Signal.Data.saveMessage(message.attributes, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const deletedForEveryoneSendStatus = {
|
||||
...message.get('deletedForEveryoneSendStatus'),
|
||||
};
|
||||
|
||||
result.successfulIdentifiers?.forEach(identifier => {
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
deletedForEveryoneSendStatus[conversation.id] = true;
|
||||
});
|
||||
|
||||
message.set({
|
||||
deletedForEveryoneSendStatus,
|
||||
deletedForEveryoneFailed: undefined,
|
||||
});
|
||||
await window.Signal.Data.saveMessage(message.attributes, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
}
|
||||
|
||||
async function updateMessageWithFailure(
|
||||
message: MessageModel,
|
||||
errors: ReadonlyArray<unknown>,
|
||||
log: LoggerType
|
||||
): Promise<void> {
|
||||
log.error(
|
||||
'updateMessageWithFailure: Setting this set of errors',
|
||||
errors.map(Errors.toLogFormat)
|
||||
);
|
||||
|
||||
message.set({ deletedForEveryoneFailed: true });
|
||||
await window.Signal.Data.saveMessage(message.attributes, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
}
|
|
@ -13,7 +13,6 @@ import type {
|
|||
} from '../conversationJobQueue';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
import type { SenderKeyInfoType } from '../../model-types.d';
|
||||
import type {
|
||||
SendState,
|
||||
SendStateByConversationId,
|
||||
|
@ -25,6 +24,7 @@ import {
|
|||
} from '../../messages/MessageSendState';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import * as Errors from '../../types/errors';
|
||||
import type { StoryMessageRecipientsType } from '../../types/Stories';
|
||||
import dataInterface from '../../sql/Client';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { getMessagesById } from '../../messages/getMessagesById';
|
||||
|
@ -35,9 +35,9 @@ import {
|
|||
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||
import { handleMultipleSendErrors } from './handleMultipleSendErrors';
|
||||
import { isGroupV2, isMe } from '../../util/whatTypeOfConversation';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
||||
import { sendContentMessageToGroup } from '../../util/sendToGroup';
|
||||
import { distributionListToSendTarget } from '../../util/distributionListToSendTarget';
|
||||
import { SendMessageChallengeError } from '../../textsecure/Errors';
|
||||
|
||||
export async function sendStory(
|
||||
|
@ -283,8 +283,6 @@ export async function sendStory(
|
|||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
const recipientsSet = new Set(pendingSendRecipientIds);
|
||||
|
||||
const sendOptions = await getSendOptionsForRecipients(
|
||||
pendingSendRecipientIds,
|
||||
{ story: true }
|
||||
|
@ -303,28 +301,11 @@ export async function sendStory(
|
|||
isGroupV2(conversation.attributes) ||
|
||||
Boolean(distributionList?.allowsReplies);
|
||||
|
||||
let inMemorySenderKeyInfo = distributionList?.senderKeyInfo;
|
||||
|
||||
const sendTarget = distributionList
|
||||
? {
|
||||
getGroupId: () => undefined,
|
||||
getMembers: () =>
|
||||
pendingSendRecipientIds
|
||||
.map(uuid => window.ConversationController.get(uuid))
|
||||
.filter(isNotNil),
|
||||
hasMember: (uuid: UUIDStringType) => recipientsSet.has(uuid),
|
||||
idForLogging: () => `dl(${receiverId})`,
|
||||
isGroupV2: () => true,
|
||||
isValid: () => true,
|
||||
getSenderKeyInfo: () => inMemorySenderKeyInfo,
|
||||
saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) => {
|
||||
inMemorySenderKeyInfo = senderKeyInfo;
|
||||
await dataInterface.modifyStoryDistribution({
|
||||
...distributionList,
|
||||
senderKeyInfo,
|
||||
});
|
||||
},
|
||||
}
|
||||
? distributionListToSendTarget(
|
||||
distributionList,
|
||||
pendingSendRecipientIds
|
||||
)
|
||||
: conversation.toSenderKeyTarget();
|
||||
|
||||
const contentMessage = new Proto.Content();
|
||||
|
@ -530,11 +511,7 @@ export async function sendStory(
|
|||
});
|
||||
|
||||
// Build up the sync message's storyMessageRecipients and send it
|
||||
const storyMessageRecipients: Array<{
|
||||
destinationUuid: string;
|
||||
distributionListIds: Array<string>;
|
||||
isAllowedToReply: boolean;
|
||||
}> = [];
|
||||
const storyMessageRecipients: StoryMessageRecipientsType = [];
|
||||
recipientsByUuid.forEach((distributionListIds, destinationUuid) => {
|
||||
storyMessageRecipients.push({
|
||||
destinationUuid,
|
||||
|
|
1
ts/model-types.d.ts
vendored
1
ts/model-types.d.ts
vendored
|
@ -153,6 +153,7 @@ export type MessageAttributesType = {
|
|||
storyDistributionListId?: string;
|
||||
storyId?: string;
|
||||
storyReplyContext?: StoryReplyContextType;
|
||||
storyRecipientsVersion?: number;
|
||||
supportedVersionAtReceive?: unknown;
|
||||
synced?: boolean;
|
||||
unidentifiedDeliveryReceived?: boolean;
|
||||
|
|
|
@ -109,6 +109,7 @@ export function getStoryDataFromMessageAttributes(
|
|||
'source',
|
||||
'sourceUuid',
|
||||
'storyDistributionListId',
|
||||
'storyRecipientsVersion',
|
||||
'timestamp',
|
||||
'type',
|
||||
]),
|
||||
|
|
|
@ -77,6 +77,7 @@ export type StoryDataType = {
|
|||
| 'storyDistributionListId'
|
||||
| 'timestamp'
|
||||
| 'type'
|
||||
| 'storyRecipientsVersion'
|
||||
> & {
|
||||
// don't want the fields to be optional as in MessageAttributesType
|
||||
expireTimer: DurationInSeconds | undefined;
|
||||
|
|
|
@ -1431,11 +1431,7 @@ export default class MessageSender {
|
|||
urgent: boolean;
|
||||
options?: SendOptionsType;
|
||||
storyMessage?: Proto.StoryMessage;
|
||||
storyMessageRecipients?: Array<{
|
||||
destinationUuid: string;
|
||||
distributionListIds: Array<string>;
|
||||
isAllowedToReply: boolean;
|
||||
}>;
|
||||
storyMessageRecipients?: ReadonlyArray<Proto.SyncMessage.Sent.IStoryMessageRecipient>;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
|
@ -1461,17 +1457,7 @@ export default class MessageSender {
|
|||
sentMessage.storyMessage = storyMessage;
|
||||
}
|
||||
if (storyMessageRecipients) {
|
||||
sentMessage.storyMessageRecipients = storyMessageRecipients.map(
|
||||
recipient => {
|
||||
const storyMessageRecipient =
|
||||
new Proto.SyncMessage.Sent.StoryMessageRecipient();
|
||||
storyMessageRecipient.destinationUuid = recipient.destinationUuid;
|
||||
storyMessageRecipient.distributionListIds =
|
||||
recipient.distributionListIds;
|
||||
storyMessageRecipient.isAllowedToReply = recipient.isAllowedToReply;
|
||||
return storyMessageRecipient;
|
||||
}
|
||||
);
|
||||
sentMessage.storyMessageRecipients = storyMessageRecipients.slice();
|
||||
}
|
||||
|
||||
if (isUpdate) {
|
||||
|
|
|
@ -166,3 +166,9 @@ export enum ResolvedSendStatus {
|
|||
Sending = 'Sending',
|
||||
Sent = 'Sent',
|
||||
}
|
||||
|
||||
export type StoryMessageRecipientsType = Array<{
|
||||
destinationUuid: string;
|
||||
distributionListIds: Array<string>;
|
||||
isAllowedToReply: boolean;
|
||||
}>;
|
||||
|
|
|
@ -16,11 +16,21 @@ export enum UUIDKind {
|
|||
|
||||
export const UUID_BYTE_SIZE = 16;
|
||||
|
||||
export const isValidUuid = (value: unknown): value is UUIDStringType =>
|
||||
typeof value === 'string' &&
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(
|
||||
value
|
||||
);
|
||||
const UUID_REGEXP =
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
|
||||
export const isValidUuid = (value: unknown): value is UUIDStringType => {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Zero UUID is a valid uuid.
|
||||
if (value === '00000000-0000-0000-0000-000000000000') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return UUID_REGEXP.test(value);
|
||||
};
|
||||
|
||||
export class UUID {
|
||||
constructor(protected readonly value: string) {
|
||||
|
|
|
@ -2,12 +2,25 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
||||
import type { StoryDataType } from '../state/ducks/stories';
|
||||
import * as Errors from '../types/errors';
|
||||
import type { StoryMessageRecipientsType } from '../types/Stories';
|
||||
import * as log from '../logging/log';
|
||||
import { DAY } from './durations';
|
||||
import { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents';
|
||||
import { getSendOptions } from './getSendOptions';
|
||||
import {
|
||||
conversationJobQueue,
|
||||
conversationQueueJobEnum,
|
||||
} from '../jobs/conversationJobQueue';
|
||||
import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
|
||||
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||
import { isGroupV2 } from './whatTypeOfConversation';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { strictAssert } from './assert';
|
||||
import { repeat, zipObject } from './iterables';
|
||||
import { isOlderThan } from './timestamp';
|
||||
|
||||
export async function deleteStoryForEveryone(
|
||||
stories: ReadonlyArray<StoryDataType>,
|
||||
|
@ -17,8 +30,31 @@ export async function deleteStoryForEveryone(
|
|||
return;
|
||||
}
|
||||
|
||||
// Group stories are deleted as regular messages.
|
||||
const sourceConversation = window.ConversationController.get(
|
||||
story.conversationId
|
||||
);
|
||||
if (sourceConversation && isGroupV2(sourceConversation.attributes)) {
|
||||
sendDeleteForEveryoneMessage(sourceConversation.attributes, {
|
||||
deleteForEveryoneDuration: DAY,
|
||||
id: story.messageId,
|
||||
timestamp: story.timestamp,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const logId = `deleteStoryForEveryone(${story.messageId})`;
|
||||
const message = await getMessageById(story.messageId);
|
||||
if (!message) {
|
||||
throw new Error('Story not found');
|
||||
}
|
||||
|
||||
if (isOlderThan(story.timestamp, DAY)) {
|
||||
throw new Error('Cannot send DOE for a story older than one day');
|
||||
}
|
||||
|
||||
const conversationIds = new Set(Object.keys(story.sendStateByConversationId));
|
||||
const updatedStoryRecipients = new Map<
|
||||
const newStoryRecipients = new Map<
|
||||
string,
|
||||
{
|
||||
distributionListIds: Set<string>;
|
||||
|
@ -32,6 +68,30 @@ export async function deleteStoryForEveryone(
|
|||
// Remove ourselves from the DOE.
|
||||
conversationIds.delete(ourConversation.id);
|
||||
|
||||
// `updatedStoryRecipients` is used to build `storyMessageRecipients` for
|
||||
// a sync message. Put all affected destinationUuids early on so that if
|
||||
// there are no other distribution lists for them - we'd still include an
|
||||
// empty list.
|
||||
Object.entries(story.sendStateByConversationId).forEach(
|
||||
([recipientId, sendState]) => {
|
||||
if (recipientId === ourConversation.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const destinationUuid =
|
||||
window.ConversationController.get(recipientId)?.get('uuid');
|
||||
|
||||
if (!destinationUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
newStoryRecipients.set(destinationUuid, {
|
||||
distributionListIds: new Set(),
|
||||
isAllowedToReply: sendState.isAllowedToReplyToStory !== false,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Find stories that were sent to other distribution lists so that we don't
|
||||
// send a DOE request to the members of those lists.
|
||||
stories.forEach(item => {
|
||||
|
@ -62,120 +122,95 @@ export async function deleteStoryForEveryone(
|
|||
return;
|
||||
}
|
||||
|
||||
const distributionListIds =
|
||||
updatedStoryRecipients.get(destinationUuid)?.distributionListIds ||
|
||||
new Set();
|
||||
|
||||
// These are the remaining distribution list ids that the user has
|
||||
// access to.
|
||||
updatedStoryRecipients.set(destinationUuid, {
|
||||
distributionListIds: item.storyDistributionListId
|
||||
? new Set([...distributionListIds, item.storyDistributionListId])
|
||||
: distributionListIds,
|
||||
isAllowedToReply:
|
||||
sendStateByConversationId[conversationId].isAllowedToReplyToStory !==
|
||||
false,
|
||||
});
|
||||
|
||||
// Remove this conversationId so we don't send the DOE to those that
|
||||
// still have access.
|
||||
conversationIds.delete(conversationId);
|
||||
|
||||
// Build remaining distribution list ids that the user still has
|
||||
// access to.
|
||||
if (item.storyDistributionListId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build complete list of new story recipients (not counting ones that
|
||||
// are in the deleted story).
|
||||
let recipient = newStoryRecipients.get(destinationUuid);
|
||||
if (!recipient) {
|
||||
const isAllowedToReply =
|
||||
sendStateByConversationId[conversationId].isAllowedToReplyToStory;
|
||||
recipient = {
|
||||
distributionListIds: new Set(),
|
||||
isAllowedToReply: isAllowedToReply !== false,
|
||||
};
|
||||
|
||||
newStoryRecipients.set(destinationUuid, recipient);
|
||||
}
|
||||
|
||||
recipient.distributionListIds.add(item.storyDistributionListId);
|
||||
});
|
||||
});
|
||||
|
||||
// Include the sync message with the updated storyMessageRecipients list
|
||||
const sender = window.textsecure.messaging;
|
||||
strictAssert(sender, 'messaging has to be initialized');
|
||||
|
||||
const newStoryMessageRecipients: StoryMessageRecipientsType = [];
|
||||
|
||||
newStoryRecipients.forEach((recipientData, destinationUuid) => {
|
||||
newStoryMessageRecipients.push({
|
||||
destinationUuid,
|
||||
distributionListIds: Array.from(recipientData.distributionListIds),
|
||||
isAllowedToReply: recipientData.isAllowedToReply,
|
||||
});
|
||||
});
|
||||
|
||||
const destinationUuid = ourConversation
|
||||
.getCheckedUuid('deleteStoryForEveryone')
|
||||
.toString();
|
||||
|
||||
log.info(`${logId}: sending DOE to ${conversationIds.size} conversations`);
|
||||
|
||||
message.set({
|
||||
deletedForEveryoneSendStatus: zipObject(conversationIds, repeat(false)),
|
||||
});
|
||||
|
||||
// Send the DOE
|
||||
conversationIds.forEach(cid => {
|
||||
// Don't DOE yourself!
|
||||
if (cid === ourConversation.id) {
|
||||
return;
|
||||
}
|
||||
log.info(`${logId}: enqueing DeleteStoryForEveryone`);
|
||||
|
||||
const conversation = window.ConversationController.get(cid);
|
||||
try {
|
||||
const jobData: ConversationQueueJobData = {
|
||||
type: conversationQueueJobEnum.enum.DeleteStoryForEveryone,
|
||||
conversationId: ourConversation.id,
|
||||
storyId: story.messageId,
|
||||
targetTimestamp: story.timestamp,
|
||||
updatedStoryRecipients: newStoryMessageRecipients,
|
||||
};
|
||||
await conversationJobQueue.add(jobData, async jobToInsert => {
|
||||
log.info(`${logId}: Deleting message with job ${jobToInsert.id}`);
|
||||
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendDeleteForEveryoneMessage(conversation.attributes, {
|
||||
deleteForEveryoneDuration: DAY,
|
||||
id: story.messageId,
|
||||
timestamp: story.timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
// If it's the last story sent to a distribution list we don't have to send
|
||||
// the sync message, but to be consistent let's build up the updated
|
||||
// storyMessageRecipients and send the sync message.
|
||||
if (!updatedStoryRecipients.size) {
|
||||
Object.entries(story.sendStateByConversationId).forEach(
|
||||
([recipientId, sendState]) => {
|
||||
if (recipientId === ourConversation.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const destinationUuid =
|
||||
window.ConversationController.get(recipientId)?.get('uuid');
|
||||
|
||||
if (!destinationUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
updatedStoryRecipients.set(destinationUuid, {
|
||||
distributionListIds: new Set(),
|
||||
isAllowedToReply: sendState.isAllowedToReplyToStory !== false,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Send the sync message with the updated storyMessageRecipients list
|
||||
const sender = window.textsecure.messaging;
|
||||
if (sender) {
|
||||
const options = await getSendOptions(ourConversation.attributes, {
|
||||
syncMessage: true,
|
||||
});
|
||||
|
||||
const storyMessageRecipients: Array<{
|
||||
destinationUuid: string;
|
||||
distributionListIds: Array<string>;
|
||||
isAllowedToReply: boolean;
|
||||
}> = [];
|
||||
|
||||
updatedStoryRecipients.forEach((recipientData, destinationUuid) => {
|
||||
storyMessageRecipients.push({
|
||||
destinationUuid,
|
||||
distributionListIds: Array.from(recipientData.distributionListIds),
|
||||
isAllowedToReply: recipientData.isAllowedToReply,
|
||||
await window.Signal.Data.saveMessage(message.attributes, {
|
||||
jobToInsert,
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
});
|
||||
|
||||
const destinationUuid = ourConversation.get('uuid');
|
||||
|
||||
if (!destinationUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync message for other devices
|
||||
sender.sendSyncMessage({
|
||||
destination: undefined,
|
||||
destinationUuid,
|
||||
storyMessageRecipients,
|
||||
expirationStartTimestamp: null,
|
||||
isUpdate: true,
|
||||
options,
|
||||
timestamp: story.timestamp,
|
||||
urgent: false,
|
||||
});
|
||||
|
||||
// Sync message for Desktop
|
||||
const ev = new StoryRecipientUpdateEvent(
|
||||
{
|
||||
destinationUuid,
|
||||
timestamp: story.timestamp,
|
||||
storyMessageRecipients,
|
||||
},
|
||||
noop
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`${logId}: Failed to queue delete for everyone`,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
onStoryRecipientUpdate(ev);
|
||||
throw error;
|
||||
}
|
||||
|
||||
log.info(`${logId}: emulating sync message event`);
|
||||
|
||||
// Emulate message for Desktop (this will call deleteForEveryone())
|
||||
const ev = new StoryRecipientUpdateEvent(
|
||||
{
|
||||
destinationUuid,
|
||||
timestamp: story.timestamp,
|
||||
storyMessageRecipients: newStoryMessageRecipients,
|
||||
},
|
||||
noop
|
||||
);
|
||||
onStoryRecipientUpdate(ev);
|
||||
}
|
||||
|
|
38
ts/util/distributionListToSendTarget.ts
Normal file
38
ts/util/distributionListToSendTarget.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import type { SenderKeyInfoType } from '../model-types.d';
|
||||
import dataInterface from '../sql/Client';
|
||||
import type { StoryDistributionType } from '../sql/Interface';
|
||||
import type { SenderKeyTargetType } from './sendToGroup';
|
||||
import { isNotNil } from './isNotNil';
|
||||
|
||||
export function distributionListToSendTarget(
|
||||
distributionList: StoryDistributionType,
|
||||
pendingSendRecipientIds: ReadonlyArray<string>
|
||||
): SenderKeyTargetType {
|
||||
let inMemorySenderKeyInfo = distributionList?.senderKeyInfo;
|
||||
|
||||
const recipientsSet = new Set(pendingSendRecipientIds);
|
||||
|
||||
return {
|
||||
getGroupId: () => undefined,
|
||||
getMembers: () =>
|
||||
pendingSendRecipientIds
|
||||
.map(uuid => window.ConversationController.get(uuid))
|
||||
.filter(isNotNil),
|
||||
hasMember: (uuid: UUIDStringType) => recipientsSet.has(uuid),
|
||||
idForLogging: () => `dl(${distributionList.id})`,
|
||||
isGroupV2: () => true,
|
||||
isValid: () => true,
|
||||
getSenderKeyInfo: () => inMemorySenderKeyInfo,
|
||||
saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) => {
|
||||
inMemorySenderKeyInfo = senderKeyInfo;
|
||||
await dataInterface.modifyStoryDistribution({
|
||||
...distributionList,
|
||||
senderKeyInfo,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -8,10 +8,7 @@ import * as log from '../logging/log';
|
|||
import { Deletes } from '../messageModifiers/Deletes';
|
||||
import { SendStatus } from '../messages/MessageSendState';
|
||||
import { deleteForEveryone } from './deleteForEveryone';
|
||||
import {
|
||||
getConversationIdForLogging,
|
||||
getMessageIdForLogging,
|
||||
} from './idForLogging';
|
||||
import { getConversationIdForLogging } from './idForLogging';
|
||||
import { isStory } from '../state/selectors/message';
|
||||
import { normalizeUuid } from './normalizeUuid';
|
||||
import { queueUpdateMessage } from './messageBatcher';
|
||||
|
@ -25,8 +22,10 @@ export async function onStoryRecipientUpdate(
|
|||
|
||||
const conversation = window.ConversationController.get(destinationUuid);
|
||||
|
||||
const logId = `onStoryRecipientUpdate(${destinationUuid}, ${timestamp})`;
|
||||
|
||||
if (!conversation) {
|
||||
log.info(`onStoryRecipientUpdate no conversation for ${destinationUuid}`);
|
||||
log.info(`${logId}: no conversation`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,20 +36,16 @@ export async function onStoryRecipientUpdate(
|
|||
);
|
||||
|
||||
if (!targetConversation) {
|
||||
log.info('onStoryRecipientUpdate !targetConversation', {
|
||||
destinationUuid,
|
||||
timestamp,
|
||||
});
|
||||
|
||||
log.info(`${logId}: no targetConversation`);
|
||||
return;
|
||||
}
|
||||
|
||||
targetConversation.queueJob('onStoryRecipientUpdate', async () => {
|
||||
log.info('onStoryRecipientUpdate updating', timestamp);
|
||||
targetConversation.queueJob(logId, async () => {
|
||||
log.info(`${logId}: updating`);
|
||||
|
||||
// Build up some maps for fast/easy lookups
|
||||
const isAllowedToReply = new Map<string, boolean>();
|
||||
const conversationIdToDistributionListIds = new Map<string, Set<string>>();
|
||||
const distributionListIdToConversationIds = new Map<string, Set<string>>();
|
||||
data.storyMessageRecipients.forEach(item => {
|
||||
const convo = window.ConversationController.get(item.destinationUuid);
|
||||
|
||||
|
@ -58,14 +53,16 @@ export async function onStoryRecipientUpdate(
|
|||
return;
|
||||
}
|
||||
|
||||
conversationIdToDistributionListIds.set(
|
||||
convo.id,
|
||||
new Set(
|
||||
item.distributionListIds.map(uuid =>
|
||||
normalizeUuid(uuid, 'onStoryRecipientUpdate.distributionListId')
|
||||
)
|
||||
)
|
||||
);
|
||||
for (const rawUuid of item.distributionListIds) {
|
||||
const uuid = normalizeUuid(rawUuid, `${logId}.distributionListId`);
|
||||
|
||||
const existing = distributionListIdToConversationIds.get(uuid);
|
||||
if (existing === undefined) {
|
||||
distributionListIdToConversationIds.set(uuid, new Set([convo.id]));
|
||||
} else {
|
||||
existing.add(convo.id);
|
||||
}
|
||||
}
|
||||
isAllowedToReply.set(convo.id, item.isAllowedToReply !== false);
|
||||
});
|
||||
|
||||
|
@ -87,55 +84,60 @@ export async function onStoryRecipientUpdate(
|
|||
return false;
|
||||
}
|
||||
|
||||
const newConversationIds =
|
||||
distributionListIdToConversationIds.get(storyDistributionListId) ??
|
||||
new Set();
|
||||
|
||||
const nextSendStateByConversationId = {
|
||||
...sendStateByConversationId,
|
||||
};
|
||||
|
||||
conversationIdToDistributionListIds.forEach(
|
||||
(distributionListIds, conversationId) => {
|
||||
const hasDistributionListId = distributionListIds.has(
|
||||
storyDistributionListId
|
||||
);
|
||||
// Find conversation ids present in the local send state, but missing
|
||||
// in the remote state, and remove them from the local state.
|
||||
for (const oldId of Object.keys(sendStateByConversationId)) {
|
||||
if (!newConversationIds.has(oldId)) {
|
||||
const recipient = window.ConversationController.get(oldId);
|
||||
|
||||
const recipient = window.ConversationController.get(conversationId);
|
||||
const conversationIdForLogging = recipient
|
||||
const recipientLogId = recipient
|
||||
? getConversationIdForLogging(recipient.attributes)
|
||||
: conversationId;
|
||||
: oldId;
|
||||
|
||||
if (
|
||||
hasDistributionListId &&
|
||||
!sendStateByConversationId[conversationId]
|
||||
) {
|
||||
log.info('onStoryRecipientUpdate adding', {
|
||||
conversationId: conversationIdForLogging,
|
||||
messageId: getMessageIdForLogging(item),
|
||||
storyDistributionListId,
|
||||
});
|
||||
nextSendStateByConversationId[conversationId] = {
|
||||
isAllowedToReplyToStory: Boolean(
|
||||
isAllowedToReply.get(conversationId)
|
||||
),
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: now,
|
||||
};
|
||||
} else if (
|
||||
sendStateByConversationId[conversationId] &&
|
||||
!hasDistributionListId
|
||||
) {
|
||||
log.info('onStoryRecipientUpdate removing', {
|
||||
conversationId: conversationIdForLogging,
|
||||
messageId: getMessageIdForLogging(item),
|
||||
storyDistributionListId,
|
||||
});
|
||||
delete nextSendStateByConversationId[conversationId];
|
||||
}
|
||||
log.info(`${logId}: removing`, {
|
||||
recipient: recipientLogId,
|
||||
messageId: item.id,
|
||||
storyDistributionListId,
|
||||
});
|
||||
delete nextSendStateByConversationId[oldId];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Find conversation ids present in the remote send state, but missing in
|
||||
// the local send state, and add them to the local state.
|
||||
for (const newId of newConversationIds) {
|
||||
if (sendStateByConversationId[newId] === undefined) {
|
||||
const recipient = window.ConversationController.get(newId);
|
||||
|
||||
const recipientLogId = recipient
|
||||
? getConversationIdForLogging(recipient.attributes)
|
||||
: newId;
|
||||
|
||||
log.info(`${logId}: adding`, {
|
||||
recipient: recipientLogId,
|
||||
messageId: item.id,
|
||||
storyDistributionListId,
|
||||
});
|
||||
nextSendStateByConversationId[newId] = {
|
||||
isAllowedToReplyToStory: Boolean(isAllowedToReply.get(newId)),
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (isEqual(sendStateByConversationId, nextSendStateByConversationId)) {
|
||||
log.info(
|
||||
'onStoryRecipientUpdate: sendStateByConversationId does not need update'
|
||||
);
|
||||
log.info(`${logId}: sendStateByConversationId does not need update`, {
|
||||
messageId: item.id,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -150,8 +152,8 @@ export async function onStoryRecipientUpdate(
|
|||
(sendStateConversationIds.size === 1 &&
|
||||
sendStateConversationIds.has(ourConversationId))
|
||||
) {
|
||||
log.info('onStoryRecipientUpdate DOE', {
|
||||
messageId: getMessageIdForLogging(item),
|
||||
log.info(`${logId} DOE`, {
|
||||
messageId: item.id,
|
||||
storyDistributionListId,
|
||||
});
|
||||
const delAttributes: DeleteAttributesType = {
|
||||
|
|
|
@ -40,8 +40,7 @@ export async function sendDeleteForEveryoneMessage(
|
|||
if (!message) {
|
||||
throw new Error('sendDeleteForEveryoneMessage: Cannot find message!');
|
||||
}
|
||||
const messageModel = window.MessageController.register(messageId, message);
|
||||
const idForLogging = getMessageIdForLogging(messageModel.attributes);
|
||||
const idForLogging = getMessageIdForLogging(message.attributes);
|
||||
|
||||
const timestamp = Date.now();
|
||||
const maxDuration = deleteForEveryoneDuration || THREE_HOURS;
|
||||
|
@ -49,7 +48,7 @@ export async function sendDeleteForEveryoneMessage(
|
|||
throw new Error(`Cannot send DOE for a message older than ${maxDuration}`);
|
||||
}
|
||||
|
||||
messageModel.set({
|
||||
message.set({
|
||||
deletedForEveryoneSendStatus: zipObject(
|
||||
getRecipientConversationIds(conversationAttributes),
|
||||
repeat(false)
|
||||
|
@ -79,7 +78,7 @@ export async function sendDeleteForEveryoneMessage(
|
|||
`sendDeleteForEveryoneMessage: Deleting message ${idForLogging} ` +
|
||||
`in conversation ${conversationIdForLogging} with job ${jobToInsert.id}`
|
||||
);
|
||||
await window.Signal.Data.saveMessage(messageModel.attributes, {
|
||||
await window.Signal.Data.saveMessage(message.attributes, {
|
||||
jobToInsert,
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
});
|
||||
|
@ -97,5 +96,5 @@ export async function sendDeleteForEveryoneMessage(
|
|||
serverTimestamp: Date.now(),
|
||||
fromId: window.ConversationController.getOurConversationIdOrThrow(),
|
||||
});
|
||||
await deleteForEveryone(messageModel, deleteModel);
|
||||
await deleteForEveryone(message, deleteModel);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { areAllErrorsUnregistered } from '../jobs/helpers/areAllErrorsUnregister
|
|||
|
||||
export async function wrapWithSyncMessageSend({
|
||||
conversation,
|
||||
logId,
|
||||
logId: parentLogId,
|
||||
messageIds,
|
||||
send,
|
||||
sendType,
|
||||
|
@ -28,11 +28,10 @@ export async function wrapWithSyncMessageSend({
|
|||
sendType: SendTypesType;
|
||||
timestamp: number;
|
||||
}): Promise<void> {
|
||||
const logId = `wrapWithSyncMessageSend(${parentLogId}, ${timestamp})`;
|
||||
const sender = window.textsecure.messaging;
|
||||
if (!sender) {
|
||||
throw new Error(
|
||||
`wrapWithSyncMessageSend/${logId}: textsecure.messaging is not available!`
|
||||
);
|
||||
throw new Error(`${logId}: textsecure.messaging is not available!`);
|
||||
}
|
||||
|
||||
let response: CallbackResultType | undefined;
|
||||
|
@ -52,17 +51,13 @@ export async function wrapWithSyncMessageSend({
|
|||
if (thrown instanceof Error) {
|
||||
error = thrown;
|
||||
} else {
|
||||
log.error(
|
||||
`wrapWithSyncMessageSend/${logId}: Thrown value was not an Error, returning early`
|
||||
);
|
||||
log.error(`${logId}: Thrown value was not an Error, returning early`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response && !error) {
|
||||
throw new Error(
|
||||
`wrapWithSyncMessageSend/${logId}: message send didn't return result or error!`
|
||||
);
|
||||
throw new Error(`${logId}: message send didn't return result or error!`);
|
||||
}
|
||||
|
||||
const dataMessage =
|
||||
|
@ -71,11 +66,9 @@ export async function wrapWithSyncMessageSend({
|
|||
|
||||
if (didSuccessfullySendOne) {
|
||||
if (!dataMessage) {
|
||||
log.error(
|
||||
`wrapWithSyncMessageSend/${logId}: dataMessage was not returned by send!`
|
||||
);
|
||||
log.error(`${logId}: dataMessage was not returned by send!`);
|
||||
} else {
|
||||
log.info(`wrapWithSyncMessageSend/${logId}: Sending sync message...`);
|
||||
log.info(`${logId}: Sending sync message... `);
|
||||
const ourConversation =
|
||||
window.ConversationController.getOurConversationOrThrow();
|
||||
const options = await getSendOptions(ourConversation.attributes, {
|
||||
|
@ -99,7 +92,8 @@ export async function wrapWithSyncMessageSend({
|
|||
if (error instanceof Error) {
|
||||
if (areAllErrorsUnregistered(conversation.attributes, error)) {
|
||||
log.info(
|
||||
`wrapWithSyncMessageSend/${logId}: Group send failures were all UnregisteredUserError, returning succcessfully.`
|
||||
`${logId}: Group send failures were all UnregisteredUserError, ` +
|
||||
'returning succcessfully.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue