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 lodash from 'lodash';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { ContentHint } from '@signalapp/libsignal-client';
|
import { ContentHint } from '@signalapp/libsignal-client';
|
||||||
|
import Long from 'long';
|
||||||
|
|
||||||
import * as Errors from '../../types/errors.js';
|
import * as Errors from '../../types/errors.js';
|
||||||
import { strictAssert } from '../../util/assert.js';
|
import { strictAssert } from '../../util/assert.js';
|
||||||
@@ -33,6 +34,7 @@ import type {
|
|||||||
OutgoingStickerType,
|
OutgoingStickerType,
|
||||||
} from '../../textsecure/SendMessage.js';
|
} from '../../textsecure/SendMessage.js';
|
||||||
import type {
|
import type {
|
||||||
|
AttachmentDownloadableFromTransitTier,
|
||||||
AttachmentType,
|
AttachmentType,
|
||||||
UploadedAttachmentType,
|
UploadedAttachmentType,
|
||||||
} from '../../types/Attachment.js';
|
} from '../../types/Attachment.js';
|
||||||
@@ -74,6 +76,11 @@ import {
|
|||||||
} from '../../test-node/util/messageFailures.js';
|
} from '../../test-node/util/messageFailures.js';
|
||||||
import { getMessageIdForLogging } from '../../util/idForLogging.js';
|
import { getMessageIdForLogging } from '../../util/idForLogging.js';
|
||||||
import { send, sendSyncMessageOnly } from '../../messages/send.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;
|
const { isNumber } = lodash;
|
||||||
|
|
||||||
@@ -220,7 +227,12 @@ export async function sendNormalMessage(
|
|||||||
sticker,
|
sticker,
|
||||||
storyMessage,
|
storyMessage,
|
||||||
storyContext,
|
storyContext,
|
||||||
} = await getMessageSendData({ log, message, targetTimestamp });
|
} = await getMessageSendData({
|
||||||
|
log,
|
||||||
|
message,
|
||||||
|
targetTimestamp,
|
||||||
|
isEditedMessageSend: editedMessageTimestamp != null,
|
||||||
|
});
|
||||||
|
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
strictAssert(
|
strictAssert(
|
||||||
@@ -584,12 +596,14 @@ async function getMessageSendData({
|
|||||||
log,
|
log,
|
||||||
message,
|
message,
|
||||||
targetTimestamp,
|
targetTimestamp,
|
||||||
|
isEditedMessageSend,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
log: LoggerType;
|
log: LoggerType;
|
||||||
message: MessageModel;
|
message: MessageModel;
|
||||||
targetTimestamp: number;
|
targetTimestamp: number;
|
||||||
|
isEditedMessageSend: boolean;
|
||||||
}>): Promise<{
|
}>): Promise<{
|
||||||
attachments: Array<UploadedAttachmentType>;
|
attachments: Array<SignalService.IAttachmentPointer>;
|
||||||
body: undefined | string;
|
body: undefined | string;
|
||||||
contact?: Array<EmbeddedContactWithUploadedAvatar>;
|
contact?: Array<EmbeddedContactWithUploadedAvatar>;
|
||||||
deletedForEveryoneTimestamp: undefined | number;
|
deletedForEveryoneTimestamp: undefined | number;
|
||||||
@@ -652,15 +666,21 @@ async function getMessageSendData({
|
|||||||
storyMessage,
|
storyMessage,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
uploadQueue.addAll(
|
uploadQueue.addAll(
|
||||||
preUploadAttachments.map(
|
preUploadAttachments.map(attachment => async () => {
|
||||||
attachment => () =>
|
if (isEditedMessageSend) {
|
||||||
uploadSingleAttachment({
|
if (canReuseExistingTransitCdnPointerForEditedMessage(attachment)) {
|
||||||
attachment,
|
return convertAttachmentToPointer(attachment);
|
||||||
log,
|
}
|
||||||
message,
|
log.error('Unable to reuse attachment pointer for edited message');
|
||||||
targetTimestamp,
|
}
|
||||||
})
|
|
||||||
)
|
return uploadSingleAttachment({
|
||||||
|
attachment,
|
||||||
|
log,
|
||||||
|
message,
|
||||||
|
targetTimestamp,
|
||||||
|
});
|
||||||
|
})
|
||||||
),
|
),
|
||||||
uploadQueue.add(async () =>
|
uploadQueue.add(async () =>
|
||||||
maybeLongAttachment
|
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 SingleProtoJobData = z.infer<typeof singleProtoJobDataSchema>;
|
||||||
|
|
||||||
export type MessageOptionsType = {
|
export type MessageOptionsType = {
|
||||||
attachments?: ReadonlyArray<UploadedAttachmentType>;
|
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||||
body?: string;
|
body?: string;
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||||
@@ -215,7 +215,7 @@ export type MessageOptionsType = {
|
|||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
};
|
};
|
||||||
export type GroupSendOptionsType = {
|
export type GroupSendOptionsType = {
|
||||||
attachments?: ReadonlyArray<UploadedAttachmentType>;
|
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||||
deletedForEveryoneTimestamp?: number;
|
deletedForEveryoneTimestamp?: number;
|
||||||
@@ -235,7 +235,7 @@ export type GroupSendOptionsType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
attachments: ReadonlyArray<UploadedAttachmentType>;
|
attachments: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||||
|
|
||||||
body?: string;
|
body?: string;
|
||||||
|
|
||||||
@@ -1211,7 +1211,7 @@ export class MessageSender {
|
|||||||
urgent,
|
urgent,
|
||||||
includePniSignatureMessage,
|
includePniSignatureMessage,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
attachments: ReadonlyArray<UploadedAttachmentType> | undefined;
|
attachments: ReadonlyArray<Proto.IAttachmentPointer> | undefined;
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
|
|||||||
@@ -127,13 +127,11 @@ export type UploadedAttachmentType = Proto.IAttachmentPointer &
|
|||||||
Readonly<{
|
Readonly<{
|
||||||
// Required fields
|
// Required fields
|
||||||
cdnKey: string;
|
cdnKey: string;
|
||||||
iv: Uint8Array;
|
|
||||||
key: Uint8Array;
|
key: Uint8Array;
|
||||||
size: number;
|
size: number;
|
||||||
digest: Uint8Array;
|
digest: Uint8Array;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
plaintextHash: string;
|
plaintextHash: string;
|
||||||
isReencryptableToSameDigest: true;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type AttachmentWithHydratedData = AttachmentType & {
|
export type AttachmentWithHydratedData = AttachmentType & {
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ const MIN_TIMELINE_IMAGE_HEIGHT = 50;
|
|||||||
const MAX_DISPLAYABLE_IMAGE_WIDTH = 8192;
|
const MAX_DISPLAYABLE_IMAGE_WIDTH = 8192;
|
||||||
const MAX_DISPLAYABLE_IMAGE_HEIGHT = 8192;
|
const MAX_DISPLAYABLE_IMAGE_HEIGHT = 8192;
|
||||||
|
|
||||||
|
const MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER = 3 * DAY;
|
||||||
|
|
||||||
// // Incoming message attachment fields
|
// // Incoming message attachment fields
|
||||||
// {
|
// {
|
||||||
// id: string
|
// 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> =
|
const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS: Set<MessageAttachmentType> =
|
||||||
new Set(['attachment', 'sticker']);
|
new Set(['attachment', 'sticker']);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export async function uploadAttachment(
|
|||||||
cdnNumber,
|
cdnNumber,
|
||||||
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
|
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
|
||||||
key: keys,
|
key: keys,
|
||||||
iv: encrypted.iv,
|
|
||||||
size: attachment.data.byteLength,
|
size: attachment.data.byteLength,
|
||||||
digest: encrypted.digest,
|
digest: encrypted.digest,
|
||||||
plaintextHash: encrypted.plaintextHash,
|
plaintextHash: encrypted.plaintextHash,
|
||||||
@@ -69,7 +68,6 @@ export async function uploadAttachment(
|
|||||||
height,
|
height,
|
||||||
caption,
|
caption,
|
||||||
blurHash,
|
blurHash,
|
||||||
isReencryptableToSameDigest: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user