Optimize profile avatar uploads and sync urls

This commit is contained in:
Fedor Indutny 2022-03-15 17:14:20 -07:00 committed by GitHub
parent 703bb8a3a3
commit 36ce4f27a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 147 additions and 77 deletions

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { noop } from 'lodash'; import { noop } from 'lodash';
import * as log from '../logging/log'; import * as log from '../logging/log';
@ -46,10 +46,6 @@ export const AvatarPreview = ({
onClick, onClick,
style = {}, style = {},
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const startingAvatarPathRef = useRef<undefined | string>(
avatarValue ? undefined : avatarPath
);
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>(); const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>();
// Loads the initial avatarPath if one is provided, but only if we're in editable mode. // Loads the initial avatarPath if one is provided, but only if we're in editable mode.
@ -60,8 +56,7 @@ export const AvatarPreview = ({
return; return;
} }
const startingAvatarPath = startingAvatarPathRef.current; if (!avatarPath) {
if (!startingAvatarPath) {
return noop; return noop;
} }
@ -69,14 +64,12 @@ export const AvatarPreview = ({
(async () => { (async () => {
try { try {
const buffer = await imagePathToBytes(startingAvatarPath); const buffer = await imagePathToBytes(avatarPath);
if (shouldCancel) { if (shouldCancel) {
return; return;
} }
setAvatarPreview(buffer); setAvatarPreview(buffer);
if (onAvatarLoaded) { onAvatarLoaded?.(buffer);
onAvatarLoaded(buffer);
}
} catch (err) { } catch (err) {
if (shouldCancel) { if (shouldCancel) {
return; return;
@ -92,7 +85,7 @@ export const AvatarPreview = ({
return () => { return () => {
shouldCancel = true; shouldCancel = true;
}; };
}, [onAvatarLoaded, isEditable]); }, [avatarPath, onAvatarLoaded, isEditable]);
// Ensures that when avatarValue changes we generate new URLs // Ensures that when avatarValue changes we generate new URLs
useEffect(() => { useEffect(() => {

View file

@ -25,7 +25,7 @@ const stories = storiesOf('Components/ProfileEditor', module);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
aboutEmoji: overrideProps.aboutEmoji, aboutEmoji: overrideProps.aboutEmoji,
aboutText: text('about', overrideProps.aboutText || ''), aboutText: text('about', overrideProps.aboutText || ''),
avatarPath: overrideProps.avatarPath, profileAvatarPath: overrideProps.profileAvatarPath,
clearUsernameSave: action('clearUsernameSave'), clearUsernameSave: action('clearUsernameSave'),
conversationId: '123', conversationId: '123',
color: overrideProps.color || getRandomColor(), color: overrideProps.color || getRandomColor(),
@ -64,7 +64,7 @@ stories.add('Full Set', () => {
{...createProps({ {...createProps({
aboutEmoji: '🙏', aboutEmoji: '🙏',
aboutText: 'Live. Laugh. Love', aboutText: 'Live. Laugh. Love',
avatarPath: '/fixtures/kitten-3-64-64.jpg', profileAvatarPath: '/fixtures/kitten-3-64-64.jpg',
onSetSkinTone: setSkinTone, onSetSkinTone: setSkinTone,
familyName: getLastName(), familyName: getLastName(),
skinTone, skinTone,

View file

@ -9,6 +9,7 @@ import type { AvatarColorType } from '../types/Colors';
import { AvatarColors } from '../types/Colors'; import { AvatarColors } from '../types/Colors';
import type { import type {
AvatarDataType, AvatarDataType,
AvatarUpdateType,
DeleteAvatarFromDiskActionType, DeleteAvatarFromDiskActionType,
ReplaceAvatarActionType, ReplaceAvatarActionType,
SaveAvatarToDiskActionType, SaveAvatarToDiskActionType,
@ -58,14 +59,14 @@ type PropsExternalType = {
onEditStateChanged: (editState: EditState) => unknown; onEditStateChanged: (editState: EditState) => unknown;
onProfileChanged: ( onProfileChanged: (
profileData: ProfileDataType, profileData: ProfileDataType,
avatarBuffer?: Uint8Array avatar: AvatarUpdateType
) => unknown; ) => unknown;
}; };
export type PropsDataType = { export type PropsDataType = {
aboutEmoji?: string; aboutEmoji?: string;
aboutText?: string; aboutText?: string;
avatarPath?: string; profileAvatarPath?: string;
color?: AvatarColorType; color?: AvatarColorType;
conversationId: string; conversationId: string;
familyName?: string; familyName?: string;
@ -211,7 +212,7 @@ function mapSaveStateToEditState({
export const ProfileEditor = ({ export const ProfileEditor = ({
aboutEmoji, aboutEmoji,
aboutText, aboutText,
avatarPath, profileAvatarPath,
clearUsernameSave, clearUsernameSave,
color, color,
conversationId, conversationId,
@ -254,9 +255,16 @@ export const ProfileEditor = ({
UsernameEditState.Editing UsernameEditState.Editing
); );
const [startingAvatarPath, setStartingAvatarPath] =
useState(profileAvatarPath);
const [oldAvatarBuffer, setOldAvatarBuffer] = useState<
Uint8Array | undefined
>(undefined);
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>( const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
undefined undefined
); );
const [isLoadingAvatar, setIsLoadingAvatar] = useState(true);
const [stagedProfile, setStagedProfile] = useState<ProfileDataType>({ const [stagedProfile, setStagedProfile] = useState<ProfileDataType>({
aboutEmoji, aboutEmoji,
aboutText, aboutText,
@ -285,6 +293,9 @@ export const ProfileEditor = ({
// To make AvatarEditor re-render less often // To make AvatarEditor re-render less often
const handleAvatarChanged = useCallback( const handleAvatarChanged = useCallback(
(avatar: Uint8Array | undefined) => { (avatar: Uint8Array | undefined) => {
// Do not display stale avatar from disk anymore.
setStartingAvatarPath(undefined);
setAvatarBuffer(avatar); setAvatarBuffer(avatar);
setEditState(EditState.None); setEditState(EditState.None);
onProfileChanged( onProfileChanged(
@ -295,10 +306,11 @@ export const ProfileEditor = ({
? trim(stagedProfile.familyName) ? trim(stagedProfile.familyName)
: undefined, : undefined,
}, },
avatar { oldAvatar: oldAvatarBuffer, newAvatar: avatar }
); );
setOldAvatarBuffer(avatar);
}, },
[onProfileChanged, stagedProfile] [onProfileChanged, stagedProfile, oldAvatarBuffer]
); );
const getFullNameText = () => { const getFullNameText = () => {
@ -405,9 +417,14 @@ export const ProfileEditor = ({
}; };
// To make AvatarEditor re-render less often // To make AvatarEditor re-render less often
const handleAvatarLoaded = useCallback(avatar => { const handleAvatarLoaded = useCallback(
setAvatarBuffer(avatar); avatar => {
}, []); setAvatarBuffer(avatar);
setOldAvatarBuffer(avatar);
setIsLoadingAvatar(false);
},
[setAvatarBuffer, setOldAvatarBuffer, setIsLoadingAvatar]
);
let content: JSX.Element; let content: JSX.Element;
@ -415,7 +432,7 @@ export const ProfileEditor = ({
content = ( content = (
<AvatarEditor <AvatarEditor
avatarColor={color || AvatarColors[0]} avatarColor={color || AvatarColors[0]}
avatarPath={avatarPath} avatarPath={startingAvatarPath}
avatarValue={avatarBuffer} avatarValue={avatarBuffer}
conversationId={conversationId} conversationId={conversationId}
conversationTitle={getFullNameText()} conversationTitle={getFullNameText()}
@ -430,6 +447,7 @@ export const ProfileEditor = ({
); );
} else if (editState === EditState.ProfileName) { } else if (editState === EditState.ProfileName) {
const shouldDisableSave = const shouldDisableSave =
isLoadingAvatar ||
!stagedProfile.firstName || !stagedProfile.firstName ||
(stagedProfile.firstName === fullName.firstName && (stagedProfile.firstName === fullName.firstName &&
stagedProfile.familyName === fullName.familyName) || stagedProfile.familyName === fullName.familyName) ||
@ -502,7 +520,10 @@ export const ProfileEditor = ({
familyName: stagedProfile.familyName, familyName: stagedProfile.familyName,
}); });
onProfileChanged(stagedProfile, avatarBuffer); onProfileChanged(stagedProfile, {
oldAvatar: oldAvatarBuffer,
newAvatar: avatarBuffer,
});
handleBack(); handleBack();
}} }}
> >
@ -513,8 +534,9 @@ export const ProfileEditor = ({
); );
} else if (editState === EditState.Bio) { } else if (editState === EditState.Bio) {
const shouldDisableSave = const shouldDisableSave =
stagedProfile.aboutText === fullBio.aboutText && isLoadingAvatar ||
stagedProfile.aboutEmoji === fullBio.aboutEmoji; (stagedProfile.aboutText === fullBio.aboutText &&
stagedProfile.aboutEmoji === fullBio.aboutEmoji);
content = ( content = (
<> <>
@ -613,7 +635,10 @@ export const ProfileEditor = ({
aboutText: stagedProfile.aboutText, aboutText: stagedProfile.aboutText,
}); });
onProfileChanged(stagedProfile, avatarBuffer); onProfileChanged(stagedProfile, {
oldAvatar: oldAvatarBuffer,
newAvatar: avatarBuffer,
});
handleBack(); handleBack();
}} }}
> >
@ -689,7 +714,7 @@ export const ProfileEditor = ({
<> <>
<AvatarPreview <AvatarPreview
avatarColor={color} avatarColor={color}
avatarPath={avatarPath} avatarPath={startingAvatarPath}
avatarValue={avatarBuffer} avatarValue={avatarBuffer}
conversationTitle={getFullNameText()} conversationTitle={getFullNameText()}
i18n={i18n} i18n={i18n}

View file

@ -7,6 +7,7 @@ import { ConfirmationDialog } from './ConfirmationDialog';
import type { PropsType as ProfileEditorPropsType } from './ProfileEditor'; import type { PropsType as ProfileEditorPropsType } from './ProfileEditor';
import { ProfileEditor, EditState } from './ProfileEditor'; import { ProfileEditor, EditState } from './ProfileEditor';
import type { ProfileDataType } from '../state/ducks/conversations'; import type { ProfileDataType } from '../state/ducks/conversations';
import type { AvatarUpdateType } from '../types/Avatar';
export type PropsDataType = { export type PropsDataType = {
hasError: boolean; hasError: boolean;
@ -15,7 +16,7 @@ export type PropsDataType = {
type PropsType = { type PropsType = {
myProfileChanged: ( myProfileChanged: (
profileData: ProfileDataType, profileData: ProfileDataType,
avatarBuffer?: Uint8Array avatar: AvatarUpdateType
) => unknown; ) => unknown;
toggleProfileEditor: () => unknown; toggleProfileEditor: () => unknown;
toggleProfileEditorHasError: () => unknown; toggleProfileEditorHasError: () => unknown;

View file

@ -1846,6 +1846,7 @@ export class ConversationModel extends window.Backbone
avatarPath: this.getAbsoluteAvatarPath(), avatarPath: this.getAbsoluteAvatarPath(),
avatarHash: this.getAvatarHash(), avatarHash: this.getAvatarHash(),
unblurredAvatarPath: this.getAbsoluteUnblurredAvatarPath(), unblurredAvatarPath: this.getAbsoluteUnblurredAvatarPath(),
profileAvatarPath: this.getAbsoluteProfileAvatarPath(),
color, color,
conversationColor: this.getConversationColor(), conversationColor: this.getConversationColor(),
customColor, customColor,
@ -5019,6 +5020,11 @@ export class ConversationModel extends window.Backbone
return avatarPath ? getAbsoluteAttachmentPath(avatarPath) : undefined; return avatarPath ? getAbsoluteAttachmentPath(avatarPath) : undefined;
} }
getAbsoluteProfileAvatarPath(): string | undefined {
const avatarPath = this.get('profileAvatar')?.path;
return avatarPath ? getAbsoluteAttachmentPath(avatarPath) : undefined;
}
getAbsoluteUnblurredAvatarPath(): string | undefined { getAbsoluteUnblurredAvatarPath(): string | undefined {
const unblurredAvatarPath = this.get('unblurredAvatarPath'); const unblurredAvatarPath = this.get('unblurredAvatarPath');
return unblurredAvatarPath return unblurredAvatarPath

View file

@ -178,7 +178,10 @@ export function toAccountRecord(
if (conversation.get('profileFamilyName')) { if (conversation.get('profileFamilyName')) {
accountRecord.familyName = conversation.get('profileFamilyName') || ''; accountRecord.familyName = conversation.get('profileFamilyName') || '';
} }
accountRecord.avatarUrl = window.storage.get('avatarUrl') || ''; const avatarUrl = window.storage.get('avatarUrl');
if (avatarUrl !== undefined) {
accountRecord.avatarUrl = avatarUrl;
}
accountRecord.noteToSelfArchived = Boolean(conversation.get('isArchived')); accountRecord.noteToSelfArchived = Boolean(conversation.get('isArchived'));
accountRecord.noteToSelfMarkedUnread = Boolean( accountRecord.noteToSelfMarkedUnread = Boolean(
conversation.get('markedUnread') conversation.get('markedUnread')
@ -895,7 +898,6 @@ export async function mergeAccountRecord(
): Promise<MergeResultType> { ): Promise<MergeResultType> {
let details = new Array<string>(); let details = new Array<string>();
const { const {
avatarUrl,
linkPreviews, linkPreviews,
notDiscoverableByPhoneNumber, notDiscoverableByPhoneNumber,
noteToSelfArchived, noteToSelfArchived,
@ -1146,10 +1148,9 @@ export async function mergeAccountRecord(
{ viaStorageServiceSync: true } { viaStorageServiceSync: true }
); );
if (avatarUrl) { const avatarUrl = dropNull(accountRecord.avatarUrl);
await conversation.setProfileAvatar(avatarUrl, profileKey); await conversation.setProfileAvatar(avatarUrl, profileKey);
window.storage.put('avatarUrl', avatarUrl); window.storage.put('avatarUrl', avatarUrl);
}
} }
const { hasConflict, details: extraDetails } = doesRecordHavePendingChanges( const { hasConflict, details: extraDetails } = doesRecordHavePendingChanges(

View file

@ -11,10 +11,11 @@ import { getProfile } from '../util/getProfile';
import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue'; import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { isWhitespace } from '../util/whitespaceStringUtil'; import { isWhitespace } from '../util/whitespaceStringUtil';
import type { AvatarUpdateType } from '../types/Avatar';
export async function writeProfile( export async function writeProfile(
conversation: ConversationType, conversation: ConversationType,
avatarBuffer?: Uint8Array avatar: AvatarUpdateType
): Promise<void> { ): Promise<void> {
// Before we write anything we request the user's profile so that we can // 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 // have an up-to-date paymentAddress to be able to include it when we write
@ -41,7 +42,7 @@ export async function writeProfile(
const [profileData, encryptedAvatarData] = await encryptProfileData( const [profileData, encryptedAvatarData] = await encryptProfileData(
conversation, conversation,
avatarBuffer avatar
); );
const avatarRequestHeaders = await window.textsecure.messaging.putProfile( const avatarRequestHeaders = await window.textsecure.messaging.putProfile(
profileData profileData
@ -50,37 +51,49 @@ export async function writeProfile(
// Upload the avatar if provided // Upload the avatar if provided
// delete existing files on disk if avatar has been removed // delete existing files on disk if avatar has been removed
// update the account's avatar path and hash if it's a new avatar // update the account's avatar path and hash if it's a new avatar
let profileAvatar: const { newAvatar } = avatar;
| { let maybeProfileAvatarUpdate: {
hash: string; profileAvatar?:
path: string; | {
} hash: string;
| undefined; path: string;
if (avatarRequestHeaders && encryptedAvatarData && avatarBuffer) { }
await window.textsecure.messaging.uploadAvatar( | undefined;
} = {};
if (profileData.sameAvatar) {
log.info('writeProfile: not updating avatar');
} else if (avatarRequestHeaders && encryptedAvatarData && newAvatar) {
log.info('writeProfile: uploading new avatar');
const avatarUrl = await window.textsecure.messaging.uploadAvatar(
avatarRequestHeaders, avatarRequestHeaders,
encryptedAvatarData encryptedAvatarData
); );
const hash = await computeHash(avatarBuffer); const hash = await computeHash(newAvatar);
if (hash !== avatarHash) { if (hash !== avatarHash) {
log.info('writeProfile: removing old avatar and saving the new one');
const [path] = await Promise.all([ const [path] = await Promise.all([
window.Signal.Migrations.writeNewAttachmentData(avatarBuffer), window.Signal.Migrations.writeNewAttachmentData(newAvatar),
avatarPath avatarPath
? window.Signal.Migrations.deleteAttachmentData(avatarPath) ? window.Signal.Migrations.deleteAttachmentData(avatarPath)
: undefined, : undefined,
]); ]);
profileAvatar = { maybeProfileAvatarUpdate = {
hash, profileAvatar: { hash, path },
path,
}; };
} }
} else if (avatarPath) {
await window.Signal.Migrations.deleteAttachmentData(avatarPath);
}
const profileAvatarData = profileAvatar ? { profileAvatar } : {}; 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 // Update backbone, update DB, run storage service upload
model.set({ model.set({
@ -88,7 +101,7 @@ export async function writeProfile(
aboutEmoji, aboutEmoji,
profileName: firstName, profileName: firstName,
profileFamilyName: familyName, profileFamilyName: familyName,
...profileAvatarData, ...maybeProfileAvatarUpdate,
}); });
dataInterface.updateConversation(model.attributes); dataInterface.updateConversation(model.attributes);

View file

@ -63,7 +63,7 @@ import {
getMe, getMe,
getUsernameSaveState, getUsernameSaveState,
} from '../selectors/conversations'; } from '../selectors/conversations';
import type { AvatarDataType } from '../../types/Avatar'; import type { AvatarDataType, AvatarUpdateType } from '../../types/Avatar';
import { getDefaultAvatars } from '../../types/Avatar'; import { getDefaultAvatars } from '../../types/Avatar';
import { getAvatarData } from '../../util/getAvatarData'; import { getAvatarData } from '../../util/getAvatarData';
import { isSameAvatarData } from '../../util/isSameAvatarData'; import { isSameAvatarData } from '../../util/isSameAvatarData';
@ -121,6 +121,7 @@ export type ConversationType = {
avatars?: Array<AvatarDataType>; avatars?: Array<AvatarDataType>;
avatarPath?: string; avatarPath?: string;
avatarHash?: string; avatarHash?: string;
profileAvatarPath?: string;
unblurredAvatarPath?: string; unblurredAvatarPath?: string;
areWeAdmin?: boolean; areWeAdmin?: boolean;
areWePending?: boolean; areWePending?: boolean;
@ -1095,7 +1096,7 @@ function saveUsername({
function myProfileChanged( function myProfileChanged(
profileData: ProfileDataType, profileData: ProfileDataType,
avatarBuffer?: Uint8Array avatar: AvatarUpdateType
): ThunkAction< ): ThunkAction<
void, void,
RootStateType, RootStateType,
@ -1111,7 +1112,7 @@ function myProfileChanged(
...conversation, ...conversation,
...profileData, ...profileData,
}, },
avatarBuffer avatar
); );
// writeProfile above updates the backbone model which in turn updates // writeProfile above updates the backbone model which in turn updates

View file

@ -17,7 +17,7 @@ function mapStateToProps(
): Omit<PropsDataType, 'onEditStateChange' | 'onProfileChanged'> & ): Omit<PropsDataType, 'onEditStateChange' | 'onProfileChanged'> &
ProfileEditorModalPropsType { ProfileEditorModalPropsType {
const { const {
avatarPath, profileAvatarPath,
avatars: userAvatarData = [], avatars: userAvatarData = [],
aboutText, aboutText,
aboutEmoji, aboutEmoji,
@ -34,7 +34,7 @@ function mapStateToProps(
return { return {
aboutEmoji, aboutEmoji,
aboutText, aboutText,
avatarPath, profileAvatarPath,
color, color,
conversationId, conversationId,
familyName, familyName,

View file

@ -10,13 +10,17 @@ import {
decryptProfileName, decryptProfileName,
decryptProfile, decryptProfile,
} from '../../Crypto'; } from '../../Crypto';
import type { ConversationType } from '../../state/ducks/conversations';
import { UUID } from '../../types/UUID'; import { UUID } from '../../types/UUID';
import { encryptProfileData } from '../../util/encryptProfileData'; import { encryptProfileData } from '../../util/encryptProfileData';
describe('encryptProfileData', () => { describe('encryptProfileData', () => {
it('encrypts and decrypts properly', async () => { let keyBuffer: Uint8Array;
const keyBuffer = getRandomBytes(32); let conversation: ConversationType;
const conversation = {
beforeEach(() => {
keyBuffer = getRandomBytes(32);
conversation = {
aboutEmoji: '🐢', aboutEmoji: '🐢',
aboutText: 'I like turtles', aboutText: 'I like turtles',
familyName: 'Kid', familyName: 'Kid',
@ -33,8 +37,13 @@ describe('encryptProfileData', () => {
title: '', title: '',
type: 'direct' as const, type: 'direct' as const,
}; };
});
const [encrypted] = await encryptProfileData(conversation); it('encrypts and decrypts properly', async () => {
const [encrypted] = await encryptProfileData(conversation, {
oldAvatar: undefined,
newAvatar: undefined,
});
assert.isDefined(encrypted.version); assert.isDefined(encrypted.version);
assert.isDefined(encrypted.name); assert.isDefined(encrypted.name);
@ -83,4 +92,22 @@ describe('encryptProfileData', () => {
assert.isDefined(encrypted.aboutEmoji); assert.isDefined(encrypted.aboutEmoji);
} }
}); });
it('sets sameAvatar to true when avatars are the same', async () => {
const [encrypted] = await encryptProfileData(conversation, {
oldAvatar: new Uint8Array([1, 2, 3]),
newAvatar: new Uint8Array([1, 2, 3]),
});
assert.isTrue(encrypted.sameAvatar);
});
it('sets sameAvatar to false when avatars are different', async () => {
const [encrypted] = await encryptProfileData(conversation, {
oldAvatar: new Uint8Array([1, 2, 3]),
newAvatar: new Uint8Array([4, 5, 6, 7]),
});
assert.isFalse(encrypted.sameAvatar);
});
}); });

View file

@ -701,6 +701,7 @@ export type ProfileRequestDataType = {
about: string | null; about: string | null;
aboutEmoji: string | null; aboutEmoji: string | null;
avatar: boolean; avatar: boolean;
sameAvatar: boolean;
commitment: string; commitment: string;
name: string; name: string;
paymentAddress: string | null; paymentAddress: string | null;

View file

@ -65,6 +65,11 @@ export type SaveAvatarToDiskActionType = (
conversationId?: string conversationId?: string
) => unknown; ) => unknown;
export type AvatarUpdateType = Readonly<{
oldAvatar: Uint8Array | undefined;
newAvatar: Uint8Array | undefined;
}>;
const groupIconColors = [ const groupIconColors = [
'A180', 'A180',
'A120', 'A120',

View file

@ -107,7 +107,7 @@ export type StorageAccessType = {
typingIndicators: boolean; typingIndicators: boolean;
sealedSenderIndicators: boolean; sealedSenderIndicators: boolean;
storageFetchComplete: boolean; storageFetchComplete: boolean;
avatarUrl: string; avatarUrl: string | undefined;
manifestVersion: number; manifestVersion: number;
storageCredentials: StorageServiceCredentials; storageCredentials: StorageServiceCredentials;
'storage-service-error-records': Array<UnknownRecord>; 'storage-service-error-records': Array<UnknownRecord>;

View file

@ -10,11 +10,12 @@ import {
encryptProfile, encryptProfile,
encryptProfileItemWithPadding, encryptProfileItemWithPadding,
} from '../Crypto'; } from '../Crypto';
import type { AvatarUpdateType } from '../types/Avatar';
import { deriveProfileKeyCommitment, deriveProfileKeyVersion } from './zkgroup'; import { deriveProfileKeyCommitment, deriveProfileKeyVersion } from './zkgroup';
export async function encryptProfileData( export async function encryptProfileData(
conversation: ConversationType, conversation: ConversationType,
avatarBuffer?: Uint8Array { oldAvatar, newAvatar }: AvatarUpdateType
): Promise<[ProfileRequestDataType, Uint8Array | undefined]> { ): Promise<[ProfileRequestDataType, Uint8Array | undefined]> {
const { const {
aboutEmoji, aboutEmoji,
@ -55,10 +56,12 @@ export async function encryptProfileData(
) )
: null; : null;
const encryptedAvatarData = avatarBuffer const encryptedAvatarData = newAvatar
? encryptProfile(avatarBuffer, keyBuffer) ? encryptProfile(newAvatar, keyBuffer)
: undefined; : undefined;
const sameAvatar = Bytes.areEqual(oldAvatar, newAvatar);
const profileData = { const profileData = {
version: deriveProfileKeyVersion(profileKey, uuid), version: deriveProfileKeyVersion(profileKey, uuid),
name: Bytes.toBase64(bytesName), name: Bytes.toBase64(bytesName),
@ -66,7 +69,8 @@ export async function encryptProfileData(
aboutEmoji: bytesAboutEmoji ? Bytes.toBase64(bytesAboutEmoji) : null, aboutEmoji: bytesAboutEmoji ? Bytes.toBase64(bytesAboutEmoji) : null,
badgeIds: (badges || []).map(({ id }) => id), badgeIds: (badges || []).map(({ id }) => id),
paymentAddress: window.storage.get('paymentAddress') || null, paymentAddress: window.storage.get('paymentAddress') || null,
avatar: Boolean(avatarBuffer), avatar: Boolean(newAvatar),
sameAvatar,
commitment: deriveProfileKeyCommitment(profileKey, uuid), commitment: deriveProfileKeyCommitment(profileKey, uuid),
}; };

View file

@ -7226,13 +7226,6 @@
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2021-05-05T23:11:22.692Z" "updated": "2021-05-05T23:11:22.692Z"
}, },
{
"rule": "React-useRef",
"path": "ts/components/AvatarPreview.tsx",
"line": " const startingAvatarPathRef = useRef<undefined | string>(",
"reasonCategory": "usageTrusted",
"updated": "2021-08-03T21:17:38.615Z"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/AvatarTextEditor.tsx", "path": "ts/components/AvatarTextEditor.tsx",
@ -8079,4 +8072,4 @@
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-09-17T21:02:59.414Z" "updated": "2021-09-17T21:02:59.414Z"
} }
] ]