Enable attachment backup uploading

This commit is contained in:
trevor-signal 2024-05-29 19:46:43 -04:00 committed by GitHub
parent 94a262b799
commit 4254356812
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2054 additions and 534 deletions

View file

@ -1001,12 +1001,18 @@ export function getAttachmentSignature(attachment: AttachmentType): string {
}
type RequiredPropertiesForDecryption = 'key' | 'digest';
type RequiredPropertiesForReencryption = 'key' | 'digest' | 'iv';
type DecryptableAttachment = WithRequiredProperties<
AttachmentType,
RequiredPropertiesForDecryption
>;
type ReencryptableAttachment = WithRequiredProperties<
AttachmentType,
RequiredPropertiesForReencryption
>;
export type AttachmentDownloadableFromTransitTier = WithRequiredProperties<
DecryptableAttachment,
'cdnKey' | 'cdnNumber'
@ -1024,15 +1030,25 @@ export type LocallySavedAttachment = WithRequiredProperties<
export type AttachmentReadyForBackup = WithRequiredProperties<
LocallySavedAttachment,
RequiredPropertiesForDecryption
RequiredPropertiesForReencryption
>;
function isDecryptable(
export function isDecryptable(
attachment: AttachmentType
): attachment is DecryptableAttachment {
return Boolean(attachment.key) && Boolean(attachment.digest);
}
export function isReencryptableToSameDigest(
attachment: AttachmentType
): attachment is ReencryptableAttachment {
return (
Boolean(attachment.key) &&
Boolean(attachment.digest) &&
Boolean(attachment.iv)
);
}
export function isDownloadableFromTransitTier(
attachment: AttachmentType
): attachment is AttachmentDownloadableFromTransitTier {

View file

@ -0,0 +1,102 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { z } from 'zod';
import {
type JobManagerJobType,
jobManagerJobSchema,
} from '../jobs/JobManager';
import { type MIMEType, MIMETypeSchema } from './MIME';
export type CoreAttachmentBackupJobType =
| StandardAttachmentBackupJobType
| ThumbnailAttachmentBackupJobType;
type StandardAttachmentBackupJobType = {
type: 'standard';
mediaName: string;
receivedAt: number;
data: {
path: string | null;
contentType: MIMEType;
keys: string;
digest: string;
iv: string;
transitCdnInfo?: {
cdnKey: string;
cdnNumber: number;
uploadTimestamp?: number;
};
size: number;
};
};
type ThumbnailAttachmentBackupJobType = {
type: 'thumbnail';
mediaName: string;
receivedAt: number;
data: {
fullsizePath: string | null;
contentType: MIMEType;
keys: string;
};
};
const standardBackupJobDataSchema = z.object({
type: z.literal('standard'),
data: z.object({
path: z.string(),
size: z.number(),
contentType: MIMETypeSchema,
keys: z.string(),
iv: z.string(),
digest: z.string(),
transitCdnInfo: z
.object({
cdnKey: z.string(),
cdnNumber: z.number(),
uploadTimestamp: z.number().optional(),
})
.optional(),
}),
});
const thumbnailBackupJobDataSchema = z.object({
type: z.literal('thumbnail'),
data: z.object({
fullsizePath: z.string(),
contentType: MIMETypeSchema,
keys: z.string(),
}),
});
export const attachmentBackupJobSchema = z
.object({
mediaName: z.string(),
receivedAt: z.number(),
})
.and(
z.discriminatedUnion('type', [
standardBackupJobDataSchema,
thumbnailBackupJobDataSchema,
])
)
.and(jobManagerJobSchema) satisfies z.ZodType<
AttachmentBackupJobType,
z.ZodTypeDef,
// With branded types, we need to specify that the input type of the schema is just a
// string
Omit<AttachmentBackupJobType, 'data'> & {
data: Omit<AttachmentBackupJobType['data'], 'contentType'> & {
contentType: string;
};
}
>;
export const thumbnailBackupJobRecordSchema = z.object({
mediaName: z.string(),
type: z.literal('standard'),
json: thumbnailBackupJobDataSchema.omit({ type: true }),
});
export type AttachmentBackupJobType = CoreAttachmentBackupJobType &
JobManagerJobType;

View file

@ -3,6 +3,10 @@
import { z } from 'zod';
import { MIMETypeSchema, type MIMEType } from './MIME';
import type { AttachmentType } from './Attachment';
import {
type JobManagerJobType,
jobManagerJobSchema,
} from '../jobs/JobManager';
export enum MediaTier {
STANDARD = 'standard',
@ -22,22 +26,21 @@ export type AttachmentDownloadJobTypeType = z.infer<
typeof attachmentDownloadTypeSchema
>;
export type AttachmentDownloadJobType = {
export type CoreAttachmentDownloadJobType = {
messageId: string;
receivedAt: number;
sentAt: number;
attachmentType: AttachmentDownloadJobTypeType;
attachment: AttachmentType;
attempts: number;
active: boolean;
retryAfter: number | null;
lastAttemptTimestamp: number | null;
digest: string;
contentType: MIMEType;
size: number;
};
export const attachmentDownloadJobSchema = z.object({
export type AttachmentDownloadJobType = CoreAttachmentDownloadJobType &
JobManagerJobType;
export const coreAttachmentDownloadJobSchema = z.object({
messageId: z.string(),
receivedAt: z.number(),
sentAt: z.number(),
@ -45,15 +48,15 @@ export const attachmentDownloadJobSchema = z.object({
attachment: z
.object({ size: z.number(), contentType: MIMETypeSchema })
.passthrough(),
attempts: z.number(),
active: z.boolean(),
retryAfter: z.number().nullable(),
lastAttemptTimestamp: z.number().nullable(),
digest: z.string(),
contentType: MIMETypeSchema,
size: z.number(),
messageIdForLogging: z.string().optional(),
}) satisfies z.ZodType<
});
export const attachmentDownloadJobSchema = coreAttachmentDownloadJobSchema.and(
jobManagerJobSchema
) satisfies z.ZodType<
Omit<AttachmentDownloadJobType, 'attachment' | 'contentType'> & {
contentType: string;
attachment: Record<string, unknown>;