Normalize message attachments

This commit is contained in:
trevor-signal 2025-05-22 21:09:54 -04:00 committed by GitHub
commit d6e81eee11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2540 additions and 807 deletions

View file

@ -37,7 +37,10 @@ import type {
CallLinkType,
DefunctCallLinkType,
} from '../types/CallLink';
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
import type {
AttachmentDownloadJobType,
AttachmentDownloadJobTypeType,
} from '../types/AttachmentDownload';
import type {
GroupSendEndorsementsData,
GroupSendMemberEndorsementRecord,
@ -46,6 +49,7 @@ import type { SyncTaskType } from '../util/syncTasks';
import type { AttachmentBackupJobType } from '../types/AttachmentBackup';
import type { GifType } from '../components/fun/panels/FunPanelGifs';
import type { NotificationProfileType } from '../types/NotificationProfile';
import { strictAssert } from '../util/assert';
export type ReadableDB = Database & { __readable_db: never };
export type WritableDB = ReadableDB & { __writable_db: never };
@ -217,7 +221,10 @@ export type StoredPreKeyType = PreKeyType & {
privateKey: string;
publicKey: string;
};
export type ServerSearchResultMessageType = MessageTypeUnhydrated & {
export type ServerSearchResultMessageType = MessageType &
ServerMessageSearchResultType;
export type ServerMessageSearchResultType = {
// If the FTS matches text in message.body, snippet will be populated
ftsSnippet: string | null;
@ -537,6 +544,136 @@ export enum AttachmentDownloadSource {
BACKFILL = 'backfill',
}
export const MESSAGE_ATTACHMENT_COLUMNS = [
'messageId',
'conversationId',
'sentAt',
'attachmentType',
'orderInMessage',
'editHistoryIndex',
'clientUuid',
'size',
'contentType',
'path',
'localKey',
'plaintextHash',
'caption',
'fileName',
'blurHash',
'height',
'width',
'digest',
'key',
'iv',
'flags',
'downloadPath',
'transitCdnKey',
'transitCdnNumber',
'transitCdnUploadTimestamp',
'backupMediaName',
'backupCdnNumber',
'incrementalMac',
'incrementalMacChunkSize',
'isReencryptableToSameDigest',
'reencryptionIv',
'reencryptionKey',
'reencryptionDigest',
'thumbnailPath',
'thumbnailSize',
'thumbnailContentType',
'thumbnailLocalKey',
'thumbnailVersion',
'screenshotPath',
'screenshotSize',
'screenshotContentType',
'screenshotLocalKey',
'screenshotVersion',
'backupThumbnailPath',
'backupThumbnailSize',
'backupThumbnailContentType',
'backupThumbnailLocalKey',
'backupThumbnailVersion',
'storyTextAttachmentJson',
'localBackupPath',
'isCorrupted',
'backfillError',
'error',
'wasTooBig',
'copiedFromQuotedAttachment',
'version',
'pending',
] as const satisfies Array<keyof MessageAttachmentDBType>;
export type MessageAttachmentDBType = {
messageId: string;
attachmentType: AttachmentDownloadJobTypeType;
orderInMessage: number;
editHistoryIndex: number | null;
conversationId: string;
sentAt: number;
clientUuid: string | null;
size: number;
contentType: string;
path: string | null;
plaintextHash: string | null;
downloadPath: string | null;
caption: string | null;
blurHash: string | null;
width: number | null;
height: number | null;
flags: number | null;
key: string | null;
iv: string | null;
digest: string | null;
fileName: string | null;
incrementalMac: string | null;
incrementalMacChunkSize: number | null;
localKey: string | null;
version: 1 | 2 | null;
transitCdnKey: string | null;
transitCdnNumber: number | null;
transitCdnUploadTimestamp: number | null;
backupMediaName: string | null;
backupCdnNumber: number | null;
thumbnailPath: string | null;
thumbnailSize: number | null;
thumbnailContentType: string | null;
thumbnailLocalKey: string | null;
thumbnailVersion: 1 | 2 | null;
screenshotPath: string | null;
screenshotSize: number | null;
screenshotContentType: string | null;
screenshotLocalKey: string | null;
screenshotVersion: 1 | 2 | null;
backupThumbnailPath: string | null;
backupThumbnailSize: number | null;
backupThumbnailContentType: string | null;
backupThumbnailLocalKey: string | null;
backupThumbnailVersion: 1 | 2 | null;
reencryptionIv: string | null;
reencryptionKey: string | null;
reencryptionDigest: string | null;
storyTextAttachmentJson: string | null;
localBackupPath: string | null;
isCorrupted: 1 | 0 | null;
backfillError: 1 | 0 | null;
error: 1 | 0 | null;
wasTooBig: 1 | 0 | null;
pending: 1 | 0 | null;
isReencryptableToSameDigest: 1 | 0 | null;
copiedFromQuotedAttachment: 1 | 0 | null;
};
// Test to make sure that MESSAGE_ATTACHMENT_COLUMNS &
// MessageAttachmentDBReferenceType remain in sync!
const testDBRefTypeMatchesColumnNames = true as unknown as [
keyof MessageAttachmentDBType,
] satisfies [(typeof MESSAGE_ATTACHMENT_COLUMNS)[number]];
strictAssert(
testDBRefTypeMatchesColumnNames,
'attachment_columns must match DB fields type'
);
type ReadableInterface = {
close: () => void;
@ -744,6 +881,9 @@ type ReadableInterface = {
getStatisticsForLogging(): Record<string, string>;
getSizeOfPendingBackupAttachmentDownloadJobs(): number;
getAttachmentReferencesForMessages: (
messageIds: Array<string>
) => Array<MessageAttachmentDBType>;
};
type WritableInterface = {
@ -866,6 +1006,7 @@ type WritableInterface = {
) => void;
_removeAllReactions: () => void;
_removeAllMessages: () => void;
_removeMessage: (id: string) => void;
incrementMessagesMigrationAttempts: (
messageIds: ReadonlyArray<string>
) => void;
@ -1072,16 +1213,16 @@ export type ServerReadableDirectInterface = ReadableInterface & {
getRecentStoryReplies(
storyId: string,
options?: GetRecentStoryRepliesOptionsType
): Array<MessageTypeUnhydrated>;
): Array<MessageType>;
getOlderMessagesByConversation: (
options: AdjacentMessagesByConversationOptionsType
) => Array<MessageTypeUnhydrated>;
) => Array<MessageType>;
getNewerMessagesByConversation: (
options: AdjacentMessagesByConversationOptionsType
) => Array<MessageTypeUnhydrated>;
) => Array<MessageType>;
getConversationRangeCenteredOnMessage: (
options: AdjacentMessagesByConversationOptionsType
) => GetConversationRangeCenteredOnMessageResultType<MessageTypeUnhydrated>;
) => GetConversationRangeCenteredOnMessageResultType<MessageType>;
getIdentityKeyById: (
id: IdentityKeyIdType
@ -1141,7 +1282,11 @@ export type ServerWritableDirectInterface = WritableInterface & {
) => string;
saveMessages: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
options: {
forceSave?: boolean;
ourAci: AciString;
_testOnlyAvoidNormalizingAttachments?: boolean;
}
) => Array<string>;
saveMessagesIndividually: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
@ -1241,6 +1386,7 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
_testOnlyAvoidNormalizingAttachments?: boolean;
}
) => string;
saveMessages: (
@ -1249,6 +1395,7 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
_testOnlyAvoidNormalizingAttachments?: boolean;
}
) => Array<string>;
saveMessagesIndividually: (
@ -1311,7 +1458,7 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
}
) => void;
removeOtherData: () => void;
cleanupOrphanedAttachments: () => void;
cleanupOrphanedAttachments: (options?: { _block: boolean }) => void;
ensureFilePermissions: () => void;
}>;