Enable attachment backup uploading
This commit is contained in:
parent
94a262b799
commit
4254356812
27 changed files with 2054 additions and 534 deletions
|
@ -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 {
|
||||
|
|
102
ts/types/AttachmentBackup.ts
Normal file
102
ts/types/AttachmentBackup.ts
Normal 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;
|
|
@ -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>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue