88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import { type AttachmentType, mightBeOnBackupTier } from '../types/Attachment';
|
|
import { downloadAttachment as doDownloadAttachment } from '../textsecure/downloadAttachment';
|
|
import { MediaTier } from '../types/AttachmentDownload';
|
|
import * as log from '../logging/log';
|
|
import { redactGenericText } from './privacy';
|
|
import { HTTPError } from '../textsecure/Errors';
|
|
import { toLogFormat } from '../types/errors';
|
|
|
|
export class AttachmentPermanentlyUndownloadableError extends Error {}
|
|
|
|
export async function downloadAttachment(
|
|
attachmentData: AttachmentType,
|
|
dependencies = { downloadAttachmentFromServer: doDownloadAttachment }
|
|
): Promise<AttachmentType> {
|
|
const redactedDigest = redactGenericText(attachmentData.digest ?? '');
|
|
const logId = `downloadAttachment(${redactedDigest})`;
|
|
|
|
const { server } = window.textsecure;
|
|
if (!server) {
|
|
throw new Error('window.textsecure.server is not available!');
|
|
}
|
|
|
|
let migratedAttachment: AttachmentType;
|
|
|
|
const { id: legacyId } = attachmentData;
|
|
if (legacyId === undefined) {
|
|
migratedAttachment = attachmentData;
|
|
} else {
|
|
migratedAttachment = {
|
|
...attachmentData,
|
|
cdnId: String(legacyId),
|
|
};
|
|
}
|
|
|
|
if (mightBeOnBackupTier(migratedAttachment)) {
|
|
try {
|
|
return await dependencies.downloadAttachmentFromServer(
|
|
server,
|
|
migratedAttachment,
|
|
{
|
|
mediaTier: MediaTier.BACKUP,
|
|
}
|
|
);
|
|
} catch (error) {
|
|
if (error instanceof HTTPError && error.code === 404) {
|
|
// This is an expected occurrence if restoring from a backup before the
|
|
// attachment has been moved to the backup tier
|
|
log.warn(`${logId}: attachment not found on backup CDN`);
|
|
} else {
|
|
// We also just log this error instead of throwing, since we want to still try to
|
|
// find it on the attachment tier.
|
|
log.error(
|
|
`${logId}: error when downloading from backup CDN`,
|
|
toLogFormat(error)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
return await dependencies.downloadAttachmentFromServer(
|
|
server,
|
|
migratedAttachment,
|
|
{
|
|
mediaTier: MediaTier.STANDARD,
|
|
}
|
|
);
|
|
} catch (error) {
|
|
if (mightBeOnBackupTier(migratedAttachment)) {
|
|
// We don't want to throw the AttachmentPermanentlyUndownloadableError because we
|
|
// may just need to wait for this attachment to end up on the backup tier
|
|
throw error;
|
|
}
|
|
// Attachments on the transit tier expire after 30 days, then start returning 404 or
|
|
// 403
|
|
if (
|
|
error instanceof HTTPError &&
|
|
(error.code === 404 || error.code === 403)
|
|
) {
|
|
throw new AttachmentPermanentlyUndownloadableError();
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|