2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
2021-07-19 19:26:06 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
import { DataWriter } from '../sql/Client';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { ConversationType } from '../state/ducks/conversations';
|
2022-01-26 21:58:00 +00:00
|
|
|
import * as Errors from '../types/errors';
|
|
|
|
import * as log from '../logging/log';
|
2021-07-21 20:45:41 +00:00
|
|
|
import { computeHash } from '../Crypto';
|
2021-07-19 19:26:06 +00:00
|
|
|
import { encryptProfileData } from '../util/encryptProfileData';
|
2021-07-21 20:45:41 +00:00
|
|
|
import { getProfile } from '../util/getProfile';
|
2022-01-14 21:34:52 +00:00
|
|
|
import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
|
2022-01-26 21:58:00 +00:00
|
|
|
import { strictAssert } from '../util/assert';
|
|
|
|
import { isWhitespace } from '../util/whitespaceStringUtil';
|
2024-02-06 02:29:45 +00:00
|
|
|
import { imagePathToBytes } from '../util/imagePathToBytes';
|
2024-07-11 19:44:09 +00:00
|
|
|
import { getLocalAvatarUrl } from '../util/avatarUtils';
|
2024-02-09 21:14:32 +00:00
|
|
|
import type {
|
|
|
|
AvatarUpdateOptionsType,
|
|
|
|
AvatarUpdateType,
|
|
|
|
} from '../types/Avatar';
|
2022-06-13 21:39:35 +00:00
|
|
|
import MessageSender from '../textsecure/SendMessage';
|
2021-07-19 19:26:06 +00:00
|
|
|
|
|
|
|
export async function writeProfile(
|
|
|
|
conversation: ConversationType,
|
2024-02-09 21:14:32 +00:00
|
|
|
options: AvatarUpdateOptionsType
|
2021-07-19 19:26:06 +00:00
|
|
|
): Promise<void> {
|
2024-01-02 19:36:49 +00:00
|
|
|
const { server } = window.textsecure;
|
|
|
|
if (!server) {
|
|
|
|
throw new Error('server is not available!');
|
2022-06-13 21:39:35 +00:00
|
|
|
}
|
|
|
|
|
2021-07-19 19:26:06 +00:00
|
|
|
// Before we write anything we request the user's profile so that we can
|
|
|
|
// have an up-to-date paymentAddress to be able to include it when we write
|
|
|
|
const model = window.ConversationController.get(conversation.id);
|
|
|
|
if (!model) {
|
|
|
|
return;
|
|
|
|
}
|
2023-08-16 20:54:39 +00:00
|
|
|
await getProfile(model.getServiceId(), model.get('e164'));
|
2021-07-19 19:26:06 +00:00
|
|
|
|
|
|
|
// Encrypt the profile data, update profile, and if needed upload the avatar
|
|
|
|
const {
|
|
|
|
aboutEmoji,
|
|
|
|
aboutText,
|
|
|
|
avatarHash,
|
2024-07-11 19:44:09 +00:00
|
|
|
rawAvatarPath,
|
2021-07-19 19:26:06 +00:00
|
|
|
familyName,
|
|
|
|
firstName,
|
|
|
|
} = conversation;
|
|
|
|
|
2022-01-26 21:58:00 +00:00
|
|
|
strictAssert(
|
|
|
|
!isWhitespace(String(conversation.firstName)),
|
|
|
|
'writeProfile: Cannot set an empty profile name'
|
|
|
|
);
|
|
|
|
|
2024-02-06 02:29:45 +00:00
|
|
|
let avatarUpdate: AvatarUpdateType;
|
|
|
|
if (options.keepAvatar) {
|
2024-07-11 19:44:09 +00:00
|
|
|
const profileAvatarUrl = getLocalAvatarUrl(model.attributes);
|
2024-02-06 02:29:45 +00:00
|
|
|
|
|
|
|
let avatarBuffer: Uint8Array | undefined;
|
2024-07-11 19:44:09 +00:00
|
|
|
if (profileAvatarUrl) {
|
2024-02-06 02:29:45 +00:00
|
|
|
try {
|
2024-07-11 19:44:09 +00:00
|
|
|
avatarBuffer = await imagePathToBytes(profileAvatarUrl);
|
2024-02-06 02:29:45 +00:00
|
|
|
} catch (error) {
|
|
|
|
log.warn('writeProfile: local avatar not found, dropping remote');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
avatarUpdate = {
|
|
|
|
oldAvatar: avatarBuffer,
|
|
|
|
newAvatar: avatarBuffer,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
avatarUpdate = options.avatarUpdate;
|
|
|
|
}
|
|
|
|
|
2021-07-19 19:26:06 +00:00
|
|
|
const [profileData, encryptedAvatarData] = await encryptProfileData(
|
|
|
|
conversation,
|
2024-02-06 02:29:45 +00:00
|
|
|
avatarUpdate
|
2021-07-19 19:26:06 +00:00
|
|
|
);
|
2024-01-02 19:36:49 +00:00
|
|
|
const avatarRequestHeaders = await server.putProfile(profileData);
|
2021-07-19 19:26:06 +00:00
|
|
|
|
|
|
|
// Upload the avatar if provided
|
|
|
|
// delete existing files on disk if avatar has been removed
|
|
|
|
// update the account's avatar path and hash if it's a new avatar
|
2024-02-06 02:29:45 +00:00
|
|
|
const { newAvatar } = avatarUpdate;
|
2022-03-16 00:14:20 +00:00
|
|
|
let maybeProfileAvatarUpdate: {
|
|
|
|
profileAvatar?:
|
|
|
|
| {
|
|
|
|
hash: string;
|
|
|
|
path: string;
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
} = {};
|
|
|
|
if (profileData.sameAvatar) {
|
|
|
|
log.info('writeProfile: not updating avatar');
|
|
|
|
} else if (avatarRequestHeaders && encryptedAvatarData && newAvatar) {
|
|
|
|
log.info('writeProfile: uploading new avatar');
|
2024-01-02 19:36:49 +00:00
|
|
|
const avatarUrl = await server.uploadAvatar(
|
2021-07-19 19:26:06 +00:00
|
|
|
avatarRequestHeaders,
|
|
|
|
encryptedAvatarData
|
|
|
|
);
|
|
|
|
|
2022-03-16 00:14:20 +00:00
|
|
|
const hash = await computeHash(newAvatar);
|
2021-07-19 19:26:06 +00:00
|
|
|
|
|
|
|
if (hash !== avatarHash) {
|
2022-03-16 00:14:20 +00:00
|
|
|
log.info('writeProfile: removing old avatar and saving the new one');
|
2024-07-11 19:44:09 +00:00
|
|
|
const [local] = await Promise.all([
|
2022-03-16 00:14:20 +00:00
|
|
|
window.Signal.Migrations.writeNewAttachmentData(newAvatar),
|
2024-07-11 19:44:09 +00:00
|
|
|
rawAvatarPath
|
|
|
|
? window.Signal.Migrations.deleteAttachmentData(rawAvatarPath)
|
2021-07-19 19:26:06 +00:00
|
|
|
: undefined,
|
|
|
|
]);
|
2022-03-16 00:14:20 +00:00
|
|
|
maybeProfileAvatarUpdate = {
|
2024-07-11 19:44:09 +00:00
|
|
|
profileAvatar: { hash, ...local },
|
2021-07-19 19:26:06 +00:00
|
|
|
};
|
|
|
|
}
|
2022-03-16 00:14:20 +00:00
|
|
|
|
|
|
|
await window.storage.put('avatarUrl', avatarUrl);
|
2024-07-11 19:44:09 +00:00
|
|
|
} else if (rawAvatarPath) {
|
2022-03-16 00:14:20 +00:00
|
|
|
log.info('writeProfile: removing avatar');
|
|
|
|
await Promise.all([
|
2024-07-11 19:44:09 +00:00
|
|
|
window.Signal.Migrations.deleteAttachmentData(rawAvatarPath),
|
2022-03-16 00:14:20 +00:00
|
|
|
window.storage.put('avatarUrl', undefined),
|
|
|
|
]);
|
2021-07-19 19:26:06 +00:00
|
|
|
|
2022-03-16 00:14:20 +00:00
|
|
|
maybeProfileAvatarUpdate = { profileAvatar: undefined };
|
|
|
|
}
|
2021-07-21 20:45:41 +00:00
|
|
|
|
2021-07-19 19:26:06 +00:00
|
|
|
// Update backbone, update DB, run storage service upload
|
|
|
|
model.set({
|
|
|
|
about: aboutText,
|
|
|
|
aboutEmoji,
|
|
|
|
profileName: firstName,
|
|
|
|
profileFamilyName: familyName,
|
2022-03-16 00:14:20 +00:00
|
|
|
...maybeProfileAvatarUpdate,
|
2021-07-19 19:26:06 +00:00
|
|
|
});
|
|
|
|
|
2024-07-22 18:16:33 +00:00
|
|
|
await DataWriter.updateConversation(model.attributes);
|
2021-07-19 19:26:06 +00:00
|
|
|
model.captureChange('writeProfile');
|
2021-07-21 20:45:41 +00:00
|
|
|
|
2022-01-14 21:34:52 +00:00
|
|
|
try {
|
|
|
|
await singleProtoJobQueue.add(
|
2022-06-13 21:39:35 +00:00
|
|
|
MessageSender.getFetchLocalProfileSyncMessage()
|
2022-01-14 21:34:52 +00:00
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
log.error(
|
|
|
|
'writeProfile: Failed to queue sync message',
|
|
|
|
Errors.toLogFormat(error)
|
|
|
|
);
|
|
|
|
}
|
2021-07-19 19:26:06 +00:00
|
|
|
}
|