mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +00:00
Re-use standard attachments on edit
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import lodash from 'lodash';
|
||||
import PQueue from 'p-queue';
|
||||
import { ContentHint } from '@signalapp/libsignal-client';
|
||||
import Long from 'long';
|
||||
|
||||
import * as Errors from '../../types/errors.js';
|
||||
import { strictAssert } from '../../util/assert.js';
|
||||
@@ -33,6 +34,7 @@ import type {
|
||||
OutgoingStickerType,
|
||||
} from '../../textsecure/SendMessage.js';
|
||||
import type {
|
||||
AttachmentDownloadableFromTransitTier,
|
||||
AttachmentType,
|
||||
UploadedAttachmentType,
|
||||
} from '../../types/Attachment.js';
|
||||
@@ -74,6 +76,11 @@ import {
|
||||
} from '../../test-node/util/messageFailures.js';
|
||||
import { getMessageIdForLogging } from '../../util/idForLogging.js';
|
||||
import { send, sendSyncMessageOnly } from '../../messages/send.js';
|
||||
import type { SignalService } from '../../protobuf/index.js';
|
||||
import { uuidToBytes } from '../../util/uuidToBytes.js';
|
||||
import { fromBase64 } from '../../Bytes.js';
|
||||
import { MIMETypeToString } from '../../types/MIME.js';
|
||||
import { canReuseExistingTransitCdnPointerForEditedMessage } from '../../util/Attachment.js';
|
||||
|
||||
const { isNumber } = lodash;
|
||||
|
||||
@@ -220,7 +227,12 @@ export async function sendNormalMessage(
|
||||
sticker,
|
||||
storyMessage,
|
||||
storyContext,
|
||||
} = await getMessageSendData({ log, message, targetTimestamp });
|
||||
} = await getMessageSendData({
|
||||
log,
|
||||
message,
|
||||
targetTimestamp,
|
||||
isEditedMessageSend: editedMessageTimestamp != null,
|
||||
});
|
||||
|
||||
if (reaction) {
|
||||
strictAssert(
|
||||
@@ -584,12 +596,14 @@ async function getMessageSendData({
|
||||
log,
|
||||
message,
|
||||
targetTimestamp,
|
||||
isEditedMessageSend,
|
||||
}: Readonly<{
|
||||
log: LoggerType;
|
||||
message: MessageModel;
|
||||
targetTimestamp: number;
|
||||
isEditedMessageSend: boolean;
|
||||
}>): Promise<{
|
||||
attachments: Array<UploadedAttachmentType>;
|
||||
attachments: Array<SignalService.IAttachmentPointer>;
|
||||
body: undefined | string;
|
||||
contact?: Array<EmbeddedContactWithUploadedAvatar>;
|
||||
deletedForEveryoneTimestamp: undefined | number;
|
||||
@@ -652,15 +666,21 @@ async function getMessageSendData({
|
||||
storyMessage,
|
||||
] = await Promise.all([
|
||||
uploadQueue.addAll(
|
||||
preUploadAttachments.map(
|
||||
attachment => () =>
|
||||
uploadSingleAttachment({
|
||||
preUploadAttachments.map(attachment => async () => {
|
||||
if (isEditedMessageSend) {
|
||||
if (canReuseExistingTransitCdnPointerForEditedMessage(attachment)) {
|
||||
return convertAttachmentToPointer(attachment);
|
||||
}
|
||||
log.error('Unable to reuse attachment pointer for edited message');
|
||||
}
|
||||
|
||||
return uploadSingleAttachment({
|
||||
attachment,
|
||||
log,
|
||||
message,
|
||||
targetTimestamp,
|
||||
});
|
||||
})
|
||||
)
|
||||
),
|
||||
uploadQueue.add(async () =>
|
||||
maybeLongAttachment
|
||||
@@ -1258,3 +1278,47 @@ function didSendToEveryone({
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function convertAttachmentToPointer(
|
||||
attachment: AttachmentDownloadableFromTransitTier
|
||||
): SignalService.IAttachmentPointer {
|
||||
const {
|
||||
cdnKey,
|
||||
cdnNumber,
|
||||
clientUuid,
|
||||
key,
|
||||
size,
|
||||
digest,
|
||||
incrementalMac,
|
||||
chunkSize,
|
||||
uploadTimestamp,
|
||||
contentType,
|
||||
fileName,
|
||||
flags,
|
||||
width,
|
||||
height,
|
||||
caption,
|
||||
blurHash,
|
||||
} = attachment;
|
||||
|
||||
return {
|
||||
cdnKey,
|
||||
cdnNumber,
|
||||
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
|
||||
key: fromBase64(key),
|
||||
size,
|
||||
digest: fromBase64(digest),
|
||||
incrementalMac: incrementalMac ? fromBase64(incrementalMac) : undefined,
|
||||
chunkSize,
|
||||
uploadTimestamp: uploadTimestamp
|
||||
? Long.fromNumber(uploadTimestamp)
|
||||
: undefined,
|
||||
contentType: MIMETypeToString(contentType),
|
||||
fileName,
|
||||
flags,
|
||||
width,
|
||||
height,
|
||||
caption,
|
||||
blurHash,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export const singleProtoJobDataSchema = z.object({
|
||||
export type SingleProtoJobData = z.infer<typeof singleProtoJobDataSchema>;
|
||||
|
||||
export type MessageOptionsType = {
|
||||
attachments?: ReadonlyArray<UploadedAttachmentType>;
|
||||
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||
body?: string;
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||
@@ -215,7 +215,7 @@ export type MessageOptionsType = {
|
||||
storyContext?: StoryContextType;
|
||||
};
|
||||
export type GroupSendOptionsType = {
|
||||
attachments?: ReadonlyArray<UploadedAttachmentType>;
|
||||
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
@@ -235,7 +235,7 @@ export type GroupSendOptionsType = {
|
||||
};
|
||||
|
||||
class Message {
|
||||
attachments: ReadonlyArray<UploadedAttachmentType>;
|
||||
attachments: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||
|
||||
body?: string;
|
||||
|
||||
@@ -1211,7 +1211,7 @@ export class MessageSender {
|
||||
urgent,
|
||||
includePniSignatureMessage,
|
||||
}: Readonly<{
|
||||
attachments: ReadonlyArray<UploadedAttachmentType> | undefined;
|
||||
attachments: ReadonlyArray<Proto.IAttachmentPointer> | undefined;
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||
contentHint: number;
|
||||
|
||||
@@ -127,13 +127,11 @@ export type UploadedAttachmentType = Proto.IAttachmentPointer &
|
||||
Readonly<{
|
||||
// Required fields
|
||||
cdnKey: string;
|
||||
iv: Uint8Array;
|
||||
key: Uint8Array;
|
||||
size: number;
|
||||
digest: Uint8Array;
|
||||
contentType: string;
|
||||
plaintextHash: string;
|
||||
isReencryptableToSameDigest: true;
|
||||
}>;
|
||||
|
||||
export type AttachmentWithHydratedData = AttachmentType & {
|
||||
|
||||
@@ -60,6 +60,8 @@ const MIN_TIMELINE_IMAGE_HEIGHT = 50;
|
||||
const MAX_DISPLAYABLE_IMAGE_WIDTH = 8192;
|
||||
const MAX_DISPLAYABLE_IMAGE_HEIGHT = 8192;
|
||||
|
||||
const MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER = 3 * DAY;
|
||||
|
||||
// // Incoming message attachment fields
|
||||
// {
|
||||
// id: string
|
||||
@@ -1173,6 +1175,23 @@ export function partitionBodyAndNormalAttachments<
|
||||
};
|
||||
}
|
||||
|
||||
export function canReuseExistingTransitCdnPointerForEditedMessage(
|
||||
attachment: AttachmentType
|
||||
): attachment is AttachmentDownloadableFromTransitTier {
|
||||
// In practice, this should always return true, since the timeframe for editing a
|
||||
// message is less than MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER
|
||||
return (
|
||||
isValidDigest(attachment.digest) &&
|
||||
isValidAttachmentKey(attachment.key) &&
|
||||
attachment.cdnKey != null &&
|
||||
attachment.cdnNumber != null &&
|
||||
isMoreRecentThan(
|
||||
attachment.uploadTimestamp ?? 0,
|
||||
MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS: Set<MessageAttachmentType> =
|
||||
new Set(['attachment', 'sticker']);
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ export async function uploadAttachment(
|
||||
cdnNumber,
|
||||
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
|
||||
key: keys,
|
||||
iv: encrypted.iv,
|
||||
size: attachment.data.byteLength,
|
||||
digest: encrypted.digest,
|
||||
plaintextHash: encrypted.plaintextHash,
|
||||
@@ -69,7 +68,6 @@ export async function uploadAttachment(
|
||||
height,
|
||||
caption,
|
||||
blurHash,
|
||||
isReencryptableToSameDigest: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user