mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-24 10:28:03 +01:00
Normalize message attachments
This commit is contained in:
@@ -24,7 +24,11 @@ import {
|
||||
isImageTypeSupported,
|
||||
isVideoTypeSupported,
|
||||
} from '../util/GoogleChrome';
|
||||
import type { LocalizerType, WithRequiredProperties } from './Util';
|
||||
import type {
|
||||
LocalizerType,
|
||||
WithOptionalProperties,
|
||||
WithRequiredProperties,
|
||||
} from './Util';
|
||||
import { ThemeType } from './Util';
|
||||
import * as GoogleChrome from '../util/GoogleChrome';
|
||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||
@@ -56,14 +60,47 @@ export class AttachmentPermanentlyUndownloadableError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
type ScreenshotType = Omit<AttachmentType, 'size'> & {
|
||||
height: number;
|
||||
width: number;
|
||||
path: string;
|
||||
size?: number;
|
||||
export type ThumbnailType = EphemeralAttachmentFields & {
|
||||
size: number;
|
||||
contentType: MIME.MIMEType;
|
||||
path?: string;
|
||||
plaintextHash?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
version?: 1 | 2;
|
||||
localKey?: string; // AES + MAC
|
||||
};
|
||||
|
||||
export type AttachmentType = {
|
||||
export type ScreenshotType = WithOptionalProperties<ThumbnailType, 'size'>;
|
||||
export type BackupThumbnailType = WithOptionalProperties<ThumbnailType, 'size'>;
|
||||
|
||||
// These fields do not get saved to the DB.
|
||||
export type EphemeralAttachmentFields = {
|
||||
totalDownloaded?: number;
|
||||
data?: Uint8Array;
|
||||
/** Not included in protobuf, needs to be pulled from flags */
|
||||
isVoiceMessage?: boolean;
|
||||
/** For messages not already on disk, this will be a data url */
|
||||
url?: string;
|
||||
screenshotData?: Uint8Array;
|
||||
/** @deprecated Legacy field */
|
||||
screenshotPath?: string;
|
||||
|
||||
/** @deprecated Legacy field. Used only for downloading old attachment */
|
||||
id?: number;
|
||||
/** @deprecated Legacy field, used long ago for migrating attachments to disk. */
|
||||
schemaVersion?: number;
|
||||
/** @deprecated Legacy field, replaced by cdnKey */
|
||||
cdnId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adding a field to AttachmentType requires:
|
||||
* 1) adding a column to message_attachments
|
||||
* 2) updating MessageAttachmentDBReferenceType and MESSAGE_ATTACHMENT_COLUMNS
|
||||
* 3) saving data to the proper column
|
||||
*/
|
||||
export type AttachmentType = EphemeralAttachmentFields & {
|
||||
error?: boolean;
|
||||
blurHash?: string;
|
||||
caption?: string;
|
||||
@@ -73,36 +110,27 @@ export type AttachmentType = {
|
||||
fileName?: string;
|
||||
plaintextHash?: string;
|
||||
uploadTimestamp?: number;
|
||||
/** Not included in protobuf, needs to be pulled from flags */
|
||||
isVoiceMessage?: boolean;
|
||||
/** For messages not already on disk, this will be a data url */
|
||||
url?: string;
|
||||
size: number;
|
||||
pending?: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
path?: string;
|
||||
screenshot?: ScreenshotType;
|
||||
screenshotData?: Uint8Array;
|
||||
// Legacy Draft
|
||||
screenshotPath?: string;
|
||||
flags?: number;
|
||||
thumbnail?: ThumbnailType;
|
||||
isCorrupted?: boolean;
|
||||
cdnNumber?: number;
|
||||
cdnId?: string;
|
||||
cdnKey?: string;
|
||||
downloadPath?: string;
|
||||
key?: string;
|
||||
iv?: string;
|
||||
data?: Uint8Array;
|
||||
|
||||
textAttachment?: TextAttachmentType;
|
||||
wasTooBig?: boolean;
|
||||
|
||||
// If `true` backfill is unavailable
|
||||
backfillError?: boolean;
|
||||
|
||||
totalDownloaded?: number;
|
||||
incrementalMac?: string;
|
||||
chunkSize?: number;
|
||||
|
||||
@@ -115,25 +143,19 @@ export type AttachmentType = {
|
||||
// See app/attachment_channel.ts
|
||||
version?: 1 | 2;
|
||||
localKey?: string; // AES + MAC
|
||||
thumbnailFromBackup?: Pick<
|
||||
AttachmentType,
|
||||
'path' | 'version' | 'plaintextHash'
|
||||
>;
|
||||
thumbnailFromBackup?: BackupThumbnailType;
|
||||
|
||||
/** Legacy field. Used only for downloading old attachments */
|
||||
id?: number;
|
||||
|
||||
/** Legacy field, used long ago for migrating attachments to disk. */
|
||||
schemaVersion?: number;
|
||||
/** For quote attachments, if copied from the referenced attachment */
|
||||
copied?: boolean;
|
||||
} & (
|
||||
| {
|
||||
isReencryptableToSameDigest?: true;
|
||||
}
|
||||
| {
|
||||
isReencryptableToSameDigest: false;
|
||||
reencryptionInfo?: ReencryptionInfo;
|
||||
}
|
||||
);
|
||||
| {
|
||||
isReencryptableToSameDigest?: true;
|
||||
}
|
||||
| {
|
||||
isReencryptableToSameDigest: false;
|
||||
reencryptionInfo?: ReencryptionInfo;
|
||||
}
|
||||
);
|
||||
|
||||
export type LocalAttachmentV2Type = Readonly<{
|
||||
version: 2;
|
||||
@@ -259,13 +281,6 @@ export type AttachmentDraftType =
|
||||
size: number;
|
||||
};
|
||||
|
||||
export type ThumbnailType = AttachmentType & {
|
||||
// Only used when quote needed to make an in-memory thumbnail
|
||||
objectUrl?: string;
|
||||
// Whether the thumbnail has been copied from the original (quoted) message
|
||||
copied?: boolean;
|
||||
};
|
||||
|
||||
export enum AttachmentVariant {
|
||||
Default = 'Default',
|
||||
ThumbnailFromBackup = 'thumbnailFromBackup',
|
||||
@@ -1008,6 +1023,10 @@ export const isFile = (attachment: AttachmentType): boolean => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MIME.isLongMessage(contentType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
import type { AttachmentType } from './Attachment';
|
||||
import type { EmbeddedContactType } from './EmbeddedContact';
|
||||
import type { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
||||
|
||||
export function getMentionsRegex(): RegExp {
|
||||
return /\uFFFC/g;
|
||||
@@ -34,7 +33,6 @@ export type IncomingMessage = Readonly<
|
||||
source?: string;
|
||||
sourceDevice?: number;
|
||||
} & SharedMessageProperties &
|
||||
MessageSchemaVersion5 &
|
||||
MessageSchemaVersion6 &
|
||||
ExpirationTimerUpdate
|
||||
>;
|
||||
@@ -56,7 +54,6 @@ export type OutgoingMessage = Readonly<
|
||||
isViewOnce?: number;
|
||||
synced: boolean;
|
||||
} & SharedMessageProperties &
|
||||
MessageSchemaVersion5 &
|
||||
ExpirationTimerUpdate
|
||||
>;
|
||||
|
||||
@@ -64,7 +61,6 @@ export type VerifiedChangeMessage = Readonly<
|
||||
{
|
||||
type: 'verified-change';
|
||||
} & SharedMessageProperties &
|
||||
MessageSchemaVersion5 &
|
||||
ExpirationTimerUpdate
|
||||
>;
|
||||
|
||||
@@ -72,7 +68,6 @@ export type ProfileChangeNotificationMessage = Readonly<
|
||||
{
|
||||
type: 'profile-change';
|
||||
} & SharedMessageProperties &
|
||||
MessageSchemaVersion5 &
|
||||
ExpirationTimerUpdate
|
||||
>;
|
||||
|
||||
@@ -92,14 +87,6 @@ export type ExpirationTimerUpdate = Partial<
|
||||
}>
|
||||
>;
|
||||
|
||||
export type MessageSchemaVersion5 = Partial<
|
||||
Readonly<{
|
||||
hasAttachments: IndexableBoolean;
|
||||
hasVisualMediaAttachments: IndexablePresence;
|
||||
hasFileAttachments: IndexablePresence;
|
||||
}>
|
||||
>;
|
||||
|
||||
export type MessageSchemaVersion6 = Partial<
|
||||
Readonly<{
|
||||
contact: Array<EmbeddedContactType>;
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
} from './Attachment';
|
||||
import * as Errors from './errors';
|
||||
import * as SchemaVersion from './SchemaVersion';
|
||||
import { initializeAttachmentMetadata } from './message/initializeAttachmentMetadata';
|
||||
|
||||
import { LONG_MESSAGE } from './MIME';
|
||||
import type * as MIME from './MIME';
|
||||
@@ -140,6 +139,8 @@ export type ContextType = {
|
||||
// - Attachments: write bodyAttachment to disk
|
||||
// Version 14
|
||||
// - All attachments: ensure they are reencryptable to a known digest
|
||||
// Version 15
|
||||
// - A noop migration to cause attachments to be normalized when the message is saved
|
||||
|
||||
const INITIAL_SCHEMA_VERSION = 0;
|
||||
|
||||
@@ -488,12 +489,10 @@ const toVersion6 = _withSchemaVersion({
|
||||
schemaVersion: 6,
|
||||
upgrade: _mapContact(Contact.parseAndWriteAvatar(migrateDataToFileSystem)),
|
||||
});
|
||||
// IMPORTANT: We’ve updated our definition of `initializeAttachmentMetadata`, so
|
||||
// we need to run it again on existing items that have previously been incorrectly
|
||||
// classified:
|
||||
// NOOP: hasFileAttachments, etc. is now computed at message save time
|
||||
const toVersion7 = _withSchemaVersion({
|
||||
schemaVersion: 7,
|
||||
upgrade: initializeAttachmentMetadata,
|
||||
upgrade: noopUpgrade,
|
||||
});
|
||||
|
||||
const toVersion8 = _withSchemaVersion({
|
||||
@@ -655,6 +654,7 @@ const toVersion12 = _withSchemaVersion({
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
const toVersion13 = _withSchemaVersion({
|
||||
schemaVersion: 13,
|
||||
upgrade: migrateBodyAttachmentToDisk,
|
||||
|
||||
@@ -31,6 +31,7 @@ import { drop } from '../util/drop';
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment';
|
||||
import { AttachmentDisposition } from '../util/getLocalAttachmentUrl';
|
||||
import { getPlaintextHashForInMemoryAttachment } from '../AttachmentCrypto';
|
||||
|
||||
export type ActionSourceType =
|
||||
| 'startup'
|
||||
@@ -1094,7 +1095,6 @@ export async function copyStickerToAttachments(
|
||||
// Fall-back
|
||||
contentType: IMAGE_WEBP,
|
||||
};
|
||||
|
||||
const data = await window.Signal.Migrations.readAttachmentData(newSticker);
|
||||
|
||||
const sniffedMimeType = sniffImageMimeType(data);
|
||||
@@ -1106,6 +1106,8 @@ export async function copyStickerToAttachments(
|
||||
);
|
||||
}
|
||||
|
||||
newSticker.plaintextHash = getPlaintextHashForInMemoryAttachment(data);
|
||||
|
||||
return newSticker;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ export type JSONWithUnknownFields<Value> =
|
||||
export type WithRequiredProperties<T, P extends keyof T> = Omit<T, P> &
|
||||
Required<Pick<T, P>>;
|
||||
|
||||
export type WithOptionalProperties<T, P extends keyof T> = Omit<T, P> &
|
||||
Partial<Pick<T, P>>;
|
||||
|
||||
export function getTypingIndicatorSetting(): boolean {
|
||||
return window.storage.get('typingIndicators', false);
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2018 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as Attachment from '../Attachment';
|
||||
import * as IndexedDB from '../IndexedDB';
|
||||
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
|
||||
const hasAttachment =
|
||||
(predicate: (value: Attachment.AttachmentType) => boolean) =>
|
||||
(message: MessageAttributesType): IndexedDB.IndexablePresence =>
|
||||
IndexedDB.toIndexablePresence((message.attachments || []).some(predicate));
|
||||
|
||||
const hasFileAttachment = hasAttachment(Attachment.isFile);
|
||||
const hasVisualMediaAttachment = hasAttachment(Attachment.isVisualMedia);
|
||||
|
||||
export const initializeAttachmentMetadata = async (
|
||||
message: MessageAttributesType
|
||||
): Promise<MessageAttributesType> => {
|
||||
if (message.type === 'verified-change') {
|
||||
return message;
|
||||
}
|
||||
if (message.type === 'profile-change') {
|
||||
return message;
|
||||
}
|
||||
if (message.messageTimer || message.isViewOnce) {
|
||||
return message;
|
||||
}
|
||||
|
||||
const attachments = (message.attachments || []).filter(
|
||||
(attachment: Attachment.AttachmentType) =>
|
||||
attachment.contentType !== 'text/x-signal-plain'
|
||||
);
|
||||
const hasAttachments = IndexedDB.toIndexableBoolean(attachments.length > 0);
|
||||
|
||||
const hasFileAttachments = hasFileAttachment({ ...message, attachments });
|
||||
const hasVisualMediaAttachments = hasVisualMediaAttachment({
|
||||
...message,
|
||||
attachments,
|
||||
});
|
||||
|
||||
return {
|
||||
...message,
|
||||
hasAttachments,
|
||||
hasFileAttachments,
|
||||
hasVisualMediaAttachments,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user