Signal-Desktop/js/modules/types/conversation.js

168 lines
3.8 KiB
JavaScript

// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global window */
const { isFunction, isNumber } = require('lodash');
const {
arrayBufferToBase64,
base64ToArrayBuffer,
computeHash,
} = require('../../../ts/Crypto');
function buildAvatarUpdater({ field }) {
return async (conversation, data, options = {}) => {
if (!conversation) {
return conversation;
}
const avatar = conversation[field];
const {
deleteAttachmentData,
doesAttachmentExist,
writeNewAttachmentData,
} = options;
if (!isFunction(deleteAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
if (!isFunction(doesAttachmentExist)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
if (!isFunction(writeNewAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
);
}
const newHash = await computeHash(data);
if (!avatar || !avatar.hash) {
return {
...conversation,
[field]: {
hash: newHash,
path: await writeNewAttachmentData(data),
},
};
}
const { hash, path } = avatar;
const exists = await doesAttachmentExist(path);
if (!exists) {
window.log.warn(
`Conversation.buildAvatarUpdater: attachment ${path} did not exist`
);
}
if (exists && hash === newHash) {
return conversation;
}
await deleteAttachmentData(path);
return {
...conversation,
[field]: {
hash: newHash,
path: await writeNewAttachmentData(data),
},
};
};
}
const maybeUpdateAvatar = buildAvatarUpdater({ field: 'avatar' });
const maybeUpdateProfileAvatar = buildAvatarUpdater({
field: 'profileAvatar',
});
async function upgradeToVersion2(conversation, options) {
if (conversation.version >= 2) {
return conversation;
}
const { writeNewAttachmentData } = options;
if (!isFunction(writeNewAttachmentData)) {
throw new Error(
'Conversation.upgradeToVersion2: writeNewAttachmentData must be a function'
);
}
let { avatar, profileAvatar, profileKey } = conversation;
if (avatar && avatar.data) {
avatar = {
hash: await computeHash(avatar.data),
path: await writeNewAttachmentData(avatar.data),
};
}
if (profileAvatar && profileAvatar.data) {
profileAvatar = {
hash: await computeHash(profileAvatar.data),
path: await writeNewAttachmentData(profileAvatar.data),
};
}
if (profileKey && profileKey.byteLength) {
profileKey = arrayBufferToBase64(profileKey);
}
return {
...conversation,
version: 2,
avatar,
profileAvatar,
profileKey,
};
}
async function migrateConversation(conversation, options = {}) {
if (!conversation) {
return conversation;
}
if (!isNumber(conversation.version)) {
// eslint-disable-next-line no-param-reassign
conversation.version = 1;
}
return upgradeToVersion2(conversation, options);
}
async function deleteExternalFiles(conversation, options = {}) {
if (!conversation) {
return;
}
const { deleteAttachmentData } = options;
if (!isFunction(deleteAttachmentData)) {
throw new Error(
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
);
}
const { avatar, profileAvatar } = conversation;
if (avatar && avatar.path) {
await deleteAttachmentData(avatar.path);
}
if (profileAvatar && profileAvatar.path) {
await deleteAttachmentData(profileAvatar.path);
}
}
module.exports = {
arrayBufferToBase64,
base64ToArrayBuffer,
computeHash,
deleteExternalFiles,
maybeUpdateAvatar,
maybeUpdateProfileAvatar,
migrateConversation,
};