import { isArrayBuffer, isUndefined, omit } from 'lodash'; import { createAbsolutePathGetter, createDeleter, createReader, createWriterForNew, getPath, } from '../attachments/attachments'; import { autoOrientJPEG, captureDimensionsAndScreenshot, deleteData, loadData, } from './attachments/migrations'; // tslint:disable: prefer-object-spread // FIXME audric // upgrade: exports._mapAttachments(autoOrientJPEG), // upgrade: exports._mapAttachments(replaceUnicodeOrderOverrides), // upgrade: _mapAttachments(migrateDataToFileSystem), // upgrade: ._mapQuotedAttachments(migrateDataToFileSystem), // upgrade: initializeAttachmentMetadata, // upgrade: initializeAttachmentMetadata, // upgrade: _mapAttachments(captureDimensionsAndScreenshot), // upgrade: _mapAttachments(replaceUnicodeV2), // upgrade: _mapPreviewAttachments(migrateDataToFileSystem), export const deleteExternalMessageFiles = async (message: { attachments: any; quote: any; contact: any; preview: any; }) => { const { attachments, quote, contact, preview } = message; if (attachments && attachments.length) { await Promise.all(attachments.map(deleteData)); } if (quote && quote.attachments && quote.attachments.length) { await Promise.all( quote.attachments.map(async (attachment: { thumbnail: any }) => { const { thumbnail } = attachment; // To prevent spoofing, we copy the original image from the quoted message. // If so, it will have a 'copied' field. We don't want to delete it if it has // that field set to true. if (thumbnail && thumbnail.path && !thumbnail.copied) { await deleteOnDisk(thumbnail.path); } }) ); } if (contact && contact.length) { await Promise.all( contact.map(async (item: { avatar: any }) => { const { avatar } = item; if (avatar && avatar.avatar && avatar.avatar.path) { await deleteOnDisk(avatar.avatar.path); } }) ); } if (preview && preview.length) { await Promise.all( preview.map(async (item: { image: any }) => { const { image } = item; if (image && image.path) { await deleteOnDisk(image.path); } }) ); } }; let attachmentsPath: string | undefined; let internalReadAttachmentData: ((relativePath: string) => Promise) | undefined; let internalGetAbsoluteAttachmentPath: ((relativePath: string) => string) | undefined; let internalDeleteOnDisk: ((relativePath: string) => Promise) | undefined; let internalWriteNewAttachmentData: ((arrayBuffer: ArrayBuffer) => Promise) | undefined; // userDataPath must be app.getPath('userData'); export function initializeAttachmentLogic(userDataPath: string) { if (attachmentsPath) { throw new Error('attachmentsPath already initialized'); } if (!userDataPath || userDataPath.length <= 10) { throw new Error('userDataPath cannot have length <= 10'); } attachmentsPath = getPath(userDataPath); internalReadAttachmentData = createReader(attachmentsPath); internalGetAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath); internalDeleteOnDisk = createDeleter(attachmentsPath); internalWriteNewAttachmentData = createWriterForNew(attachmentsPath); } export const getAttachmentPath = () => { if (!attachmentsPath) { throw new Error('attachmentsPath not init'); } return attachmentsPath; }; export const loadAttachmentData = loadData(); export const loadPreviewData = async (preview: any) => { if (!preview || !preview.length) { return []; } return Promise.all( preview.map(async (item: any) => { if (!item.image) { return item; } return { ...item, image: await loadAttachmentData(item.image), }; }) ); }; export const loadQuoteData = async (quote: any) => { if (!quote) { return null; } return { ...quote, attachments: await Promise.all( (quote.attachments || []).map(async (attachment: any) => { const { thumbnail } = attachment; if (!thumbnail || !thumbnail.path) { return attachment; } return { ...attachment, thumbnail: await loadAttachmentData(thumbnail), }; }) ), }; }; export const processNewAttachment = async (attachment: { contentType: string; data: ArrayBuffer; digest?: string; path?: string; isRaw?: boolean; }) => { const rotatedData = await autoOrientJPEG(attachment); const rotatedAttachment = { ...attachment, contentType: rotatedData.contentType, data: rotatedData.data, digest: attachment.digest as string | undefined, }; if (rotatedData.shouldDeleteDigest) { delete rotatedAttachment.digest; } const onDiskAttachmentPath = await migrateDataToFileSystem(rotatedAttachment.data); const attachmentWithoutData = omit({ ...attachment, path: onDiskAttachmentPath }, 'data'); const finalAttachment = await captureDimensionsAndScreenshot(attachmentWithoutData); return finalAttachment; }; export const readAttachmentData = async (relativePath: string): Promise => { if (!internalReadAttachmentData) { throw new Error('attachment logic not initialized'); } return internalReadAttachmentData(relativePath); }; export const getAbsoluteAttachmentPath = (relativePath?: string): string => { if (!internalGetAbsoluteAttachmentPath) { throw new Error('attachment logic not initialized'); } return internalGetAbsoluteAttachmentPath(relativePath || ''); }; export const deleteOnDisk = async (relativePath: string): Promise => { if (!internalDeleteOnDisk) { throw new Error('attachment logic not initialized'); } return internalDeleteOnDisk(relativePath); }; export const writeNewAttachmentData = async (arrayBuffer: ArrayBuffer): Promise => { if (!internalWriteNewAttachmentData) { throw new Error('attachment logic not initialized'); } return internalWriteNewAttachmentData(arrayBuffer); }; // type Context :: { // writeNewAttachmentData :: ArrayBuffer -> Promise (IO Path) // } // // migrateDataToFileSystem :: Attachment -> // Context -> // Promise Attachment export const migrateDataToFileSystem = async (data?: ArrayBuffer) => { const hasDataField = !isUndefined(data); if (!hasDataField) { throw new Error('attachment has no data in migrateDataToFileSystem'); } const isValidData = isArrayBuffer(data); if (!isValidData) { throw new TypeError(`Expected ${data} to be an array buffer got: ${typeof data}`); } const path = await writeNewAttachmentData(data); return path; }; export async function deleteExternalFilesOfConversation(conversation: { avatar: any; profileAvatar: any; }) { if (!conversation) { return; } const { avatar, profileAvatar } = conversation; if (avatar && avatar.path) { await deleteOnDisk(avatar.path); } if (profileAvatar && profileAvatar.path) { await deleteOnDisk(profileAvatar.path); } }