// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import dataInterface from '../sql/Client'; import type { ConversationType } from '../state/ducks/conversations'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; import { computeHash } from '../Crypto'; import { encryptProfileData } from '../util/encryptProfileData'; import { getProfile } from '../util/getProfile'; import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue'; import { strictAssert } from '../util/assert'; import { isWhitespace } from '../util/whitespaceStringUtil'; import type { AvatarUpdateType } from '../types/Avatar'; import MessageSender from '../textsecure/SendMessage'; export async function writeProfile( conversation: ConversationType, avatar: AvatarUpdateType ): Promise { const { messaging } = window.textsecure; if (!messaging) { throw new Error('messaging is not available!'); } // 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; } await getProfile(model.get('uuid'), model.get('e164')); // Encrypt the profile data, update profile, and if needed upload the avatar const { aboutEmoji, aboutText, avatarHash, avatarPath, familyName, firstName, } = conversation; strictAssert( !isWhitespace(String(conversation.firstName)), 'writeProfile: Cannot set an empty profile name' ); const [profileData, encryptedAvatarData] = await encryptProfileData( conversation, avatar ); const avatarRequestHeaders = await messaging.putProfile(profileData); // 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 const { newAvatar } = avatar; 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'); const avatarUrl = await messaging.uploadAvatar( avatarRequestHeaders, encryptedAvatarData ); const hash = await computeHash(newAvatar); if (hash !== avatarHash) { log.info('writeProfile: removing old avatar and saving the new one'); const [path] = await Promise.all([ window.Signal.Migrations.writeNewAttachmentData(newAvatar), avatarPath ? window.Signal.Migrations.deleteAttachmentData(avatarPath) : undefined, ]); maybeProfileAvatarUpdate = { profileAvatar: { hash, path }, }; } await window.storage.put('avatarUrl', avatarUrl); } else if (avatarPath) { log.info('writeProfile: removing avatar'); await Promise.all([ window.Signal.Migrations.deleteAttachmentData(avatarPath), window.storage.put('avatarUrl', undefined), ]); maybeProfileAvatarUpdate = { profileAvatar: undefined }; } // Update backbone, update DB, run storage service upload model.set({ about: aboutText, aboutEmoji, profileName: firstName, profileFamilyName: familyName, ...maybeProfileAvatarUpdate, }); dataInterface.updateConversation(model.attributes); model.captureChange('writeProfile'); try { await singleProtoJobQueue.add( MessageSender.getFetchLocalProfileSyncMessage() ); } catch (error) { log.error( 'writeProfile: Failed to queue sync message', Errors.toLogFormat(error) ); } }