fix: set and get profile picture from libsession

This commit is contained in:
Audric Ackermann 2023-02-10 16:30:02 +11:00
parent 6bbb16b46d
commit f215535f75
9 changed files with 73 additions and 33 deletions

View File

@ -37,6 +37,7 @@ async function mergeConfigsWithIncomingUpdates(
const wrapperId = LibSessionUtil.kindToVariant(kind); const wrapperId = LibSessionUtil.kindToVariant(kind);
await GenericWrapperActions.merge(wrapperId, toMerge); await GenericWrapperActions.merge(wrapperId, toMerge);
const needsPush = await GenericWrapperActions.needsPush(wrapperId); const needsPush = await GenericWrapperActions.needsPush(wrapperId);
const needsDump = await GenericWrapperActions.needsDump(wrapperId); const needsDump = await GenericWrapperActions.needsDump(wrapperId);
const messageHashes = [incomingConfig.messageHash]; const messageHashes = [incomingConfig.messageHash];
@ -59,7 +60,6 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise<Inco
} }
const updatedProfilePicture = await UserConfigWrapperActions.getProfilePicture(); const updatedProfilePicture = await UserConfigWrapperActions.getProfilePicture();
debugger;
const picUpdate = !isEmpty(updatedProfilePicture.key) && !isEmpty(updatedProfilePicture.url); const picUpdate = !isEmpty(updatedProfilePicture.key) && !isEmpty(updatedProfilePicture.url);
await ProfileManager.updateOurProfileSync( await ProfileManager.updateOurProfileSync(
updatedUserName, updatedUserName,

View File

@ -42,14 +42,15 @@ async function updateProfileOfContact(
// avoid setting the display name to an invalid value // avoid setting the display name to an invalid value
if (existingDisplayName !== displayName && !isEmpty(displayName)) { if (existingDisplayName !== displayName && !isEmpty(displayName)) {
console.warn(
`updateProfileOfContact overriding old "${existingDisplayName}: with "${displayName}"`
);
conversation.set('displayNameInProfile', displayName || undefined); conversation.set('displayNameInProfile', displayName || undefined);
await conversation.commit(); await conversation.commit();
} }
// add an avatar download job only if needed // add an avatar download job only if needed
debugger;
const profileKeyHex = !profileKey || isEmpty(profileKey) ? null : toHex(profileKey); const profileKeyHex = !profileKey || isEmpty(profileKey) ? null : toHex(profileKey);
await AvatarDownload.addAvatarDownloadJobIfNeeded({ await AvatarDownload.addAvatarDownloadJobIfNeeded({
profileKeyHex, profileKeyHex,
profileUrl, profileUrl,

View File

@ -21,6 +21,10 @@ export type StartProcessingResult = 'job_in_progress' | 'job_deferred' | 'job_st
export type AddJobResult = 'job_deferred' | 'job_started'; export type AddJobResult = 'job_deferred' | 'job_started';
function jobToLogId<T extends TypeOfPersistedData>(jobRunner: JobRunnerType, job: PersistedJob<T>) {
return `id: "${job.persistedData.identifier}" (type: "${jobRunner}")`;
}
/** /**
* This class is used to plan jobs and make sure they are retried until the success. * This class is used to plan jobs and make sure they are retried until the success.
* By having a specific type, we can find the logic to be run by that type of job. * By having a specific type, we can find the logic to be run by that type of job.
@ -79,7 +83,7 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
if (this.jobsScheduled.find(j => j.persistedData.identifier === job.persistedData.identifier)) { if (this.jobsScheduled.find(j => j.persistedData.identifier === job.persistedData.identifier)) {
window.log.info( window.log.info(
`job runner has already a job with id:"${job.persistedData.identifier}" planned so not adding another one` `job runner (${this.jobRunnerType}) has already a job with id:"${job.persistedData.identifier}" planned so not adding another one`
); );
return 'identifier_exists'; return 'identifier_exists';
} }
@ -168,7 +172,7 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
private async writeJobsToDB() { private async writeJobsToDB() {
const serialized = this.getSerializedJobs(); const serialized = this.getSerializedJobs();
window.log.warn('writing to db', serialized); window.log.warn(`writing to db for "${this.jobRunnerType}": `, serialized);
await Data.createOrUpdateItem({ await Data.createOrUpdateItem({
id: this.getJobRunnerItemId(), id: this.getJobRunnerItemId(),
value: JSON.stringify(serialized), value: JSON.stringify(serialized),
@ -176,7 +180,6 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
} }
private async addJobUnchecked(job: PersistedJob<T>) { private async addJobUnchecked(job: PersistedJob<T>) {
console.warn('job', job);
this.jobsScheduled.push(cloneDeep(job)); this.jobsScheduled.push(cloneDeep(job));
this.sortJobsList(); this.sortJobsList();
await this.writeJobsToDB(); await this.writeJobsToDB();
@ -254,7 +257,12 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
private deleteJobsByIdentifier(identifiers: Array<string>) { private deleteJobsByIdentifier(identifiers: Array<string>) {
identifiers.forEach(identifier => { identifiers.forEach(identifier => {
const jobIndex = this.jobsScheduled.findIndex(f => f.persistedData.identifier === identifier); const jobIndex = this.jobsScheduled.findIndex(f => f.persistedData.identifier === identifier);
window.log.info('deleteJobsByIdentifier job', identifier, ' index', jobIndex); window.log.info(
`removing job ${jobToLogId(
this.jobRunnerType,
this.jobsScheduled[jobIndex]
)} at ${jobIndex}`
);
if (jobIndex >= 0) { if (jobIndex >= 0) {
this.jobsScheduled.splice(jobIndex, 1); this.jobsScheduled.splice(jobIndex, 1);
@ -290,21 +298,40 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
success = await timeout(this.currentJob.runJob(), this.currentJob.getJobTimeoutMs()); success = await timeout(this.currentJob.runJob(), this.currentJob.getJobTimeoutMs());
if (success !== RunJobResult.Success) { if (success !== RunJobResult.Success) {
throw new Error(`job ${nextJob.persistedData.identifier} failed`); throw new Error('return result was not "Success"');
} }
// here the job did not throw and didn't return false. Consider it OK then and remove it from the list of jobs to run. // here the job did not throw and didn't return false. Consider it OK then and remove it from the list of jobs to run.
this.deleteJobsByIdentifier([this.currentJob.persistedData.identifier]); this.deleteJobsByIdentifier([this.currentJob.persistedData.identifier]);
await this.writeJobsToDB(); await this.writeJobsToDB();
} catch (e) { } catch (e) {
window.log.warn(`JobRunner current ${this.jobRunnerType} failed with ${e.message}`); window.log.info(`${jobToLogId(this.jobRunnerType, nextJob)} failed with "${e.message}"`);
if ( if (
success === RunJobResult.PermanentFailure || success === RunJobResult.PermanentFailure ||
nextJob.persistedData.currentRetry >= nextJob.persistedData.maxAttempts - 1 nextJob.persistedData.currentRetry >= nextJob.persistedData.maxAttempts - 1
) { ) {
if (success === RunJobResult.PermanentFailure) {
window.log.info(
`${jobToLogId(this.jobRunnerType, nextJob)}:${
nextJob.persistedData.currentRetry
} permament failure for job`
);
} else {
window.log.info(
`Too many failures for ${jobToLogId(
this.jobRunnerType,
nextJob
)} out of nextJob.persistedData.maxAttempts`
);
}
// we cannot restart this job anymore. Remove the entry completely // we cannot restart this job anymore. Remove the entry completely
this.deleteJobsByIdentifier([nextJob.persistedData.identifier]); this.deleteJobsByIdentifier([nextJob.persistedData.identifier]);
} else { } else {
window.log.info(
`Rescheduling ${jobToLogId(this.jobRunnerType, nextJob)} in ${
nextJob.persistedData.delayBetweenRetries
}...`
);
nextJob.persistedData.currentRetry = nextJob.persistedData.currentRetry + 1; nextJob.persistedData.currentRetry = nextJob.persistedData.currentRetry + 1;
// that job can be restarted. Plan a retry later with the already defined retry // that job can be restarted. Plan a retry later with the already defined retry
nextJob.persistedData.nextAttemptTimestamp = nextJob.persistedData.nextAttemptTimestamp =

View File

@ -60,11 +60,11 @@ async function addAvatarDownloadJobIfNeeded({
profileKeyHex: string | null | undefined; profileKeyHex: string | null | undefined;
}) { }) {
if (profileKeyHex && shouldAddAvatarDownloadJob({ pubkey, profileUrl, profileKeyHex })) { if (profileKeyHex && shouldAddAvatarDownloadJob({ pubkey, profileUrl, profileKeyHex })) {
debugger;
const avatarDownloadJob = new AvatarDownloadJob({ const avatarDownloadJob = new AvatarDownloadJob({
conversationId: pubkey, conversationId: pubkey,
profileKeyHex, profileKeyHex,
profilePictureUrl: profileUrl || null, profilePictureUrl: profileUrl || null,
nextAttemptTimestamp: Date.now(),
}); });
window.log.debug( window.log.debug(
`addAvatarDownloadJobIfNeeded: adding job download for ${pubkey}:${profileUrl}:${profileKeyHex} ` `addAvatarDownloadJobIfNeeded: adding job download for ${pubkey}:${profileUrl}:${profileKeyHex} `
@ -130,8 +130,6 @@ class AvatarDownloadJob extends PersistedJob<AvatarDownloadPersistedData> {
return RunJobResult.PermanentFailure; return RunJobResult.PermanentFailure;
} }
debugger;
let conversation = getConversationController().get(convoId); let conversation = getConversationController().get(convoId);
if (!conversation) { if (!conversation) {
// return true so we do not retry this task. // return true so we do not retry this task.
@ -171,12 +169,26 @@ class AvatarDownloadJob extends PersistedJob<AvatarDownloadPersistedData> {
}); });
conversation = getConversationController().getOrThrow(convoId); conversation = getConversationController().getOrThrow(convoId);
if (!downloaded.data.byteLength) {
window.log.debug(`[profileupdate] downloaded data is empty for ${conversation.id}`);
return RunJobResult.RetryJobIfPossible; // so we retry this job
}
// null => use placeholder with color and first letter // null => use placeholder with color and first letter
let path = null; let path = null;
try { try {
const profileKeyArrayBuffer = fromHexToArray(this.persistedData.profileKeyHex); const profileKeyArrayBuffer = fromHexToArray(this.persistedData.profileKeyHex);
const decryptedData = await decryptProfile(downloaded.data, profileKeyArrayBuffer); let decryptedData: ArrayBuffer;
try {
decryptedData = await decryptProfile(downloaded.data, profileKeyArrayBuffer);
} catch (decryptError) {
window.log.info(
`[profileupdate] failed to decrypt downloaded data ${conversation.id} with provided profileKey`
);
// if we cannot decrypt the content, there is no need to keep retrying.
return RunJobResult.PermanentFailure;
}
window.log.info( window.log.info(
`[profileupdate] about to auto scale avatar for convo ${conversation.id}` `[profileupdate] about to auto scale avatar for convo ${conversation.id}`

View File

@ -1,4 +1,3 @@
import { from_string } from 'libsodium-wrappers-sumo';
import { compact, groupBy, isArray, isEmpty, isNumber, isString, uniq } from 'lodash'; import { compact, groupBy, isArray, isEmpty, isNumber, isString, uniq } from 'lodash';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { UserUtils } from '../..'; import { UserUtils } from '../..';
@ -12,6 +11,7 @@ import { getConversationController } from '../../../conversations';
import { SharedConfigMessage } from '../../../messages/outgoing/controlMessage/SharedConfigMessage'; import { SharedConfigMessage } from '../../../messages/outgoing/controlMessage/SharedConfigMessage';
import { MessageSender } from '../../../sending/MessageSender'; import { MessageSender } from '../../../sending/MessageSender';
import { LibSessionUtil, OutgoingConfResult } from '../../libsession/libsession_utils'; import { LibSessionUtil, OutgoingConfResult } from '../../libsession/libsession_utils';
import { fromHexToArray } from '../../String';
import { runners } from '../JobRunner'; import { runners } from '../JobRunner';
import { import {
AddJobCheckReturn, AddJobCheckReturn,
@ -116,10 +116,6 @@ function resultsToSuccessfulChange(
request.destination request.destination
) { ) {
// a message was posted. We need to add it to the tracked list of hashes // a message was posted. We need to add it to the tracked list of hashes
console.warn(
`messagePostedHashes for j:${j}; didDeleteOldConfigMessages:${didDeleteOldConfigMessages}: `,
messagePostedHashes
);
const updatedHashes: Array<string> = didDeleteOldConfigMessages const updatedHashes: Array<string> = didDeleteOldConfigMessages
? [messagePostedHashes] ? [messagePostedHashes]
: uniq(compact([...request.allOldHashes, messagePostedHashes])); : uniq(compact([...request.allOldHashes, messagePostedHashes]));
@ -132,7 +128,6 @@ function resultsToSuccessfulChange(
} }
} }
} catch (e) { } catch (e) {
console.warn('eeee', e);
throw e; throw e;
} }
@ -153,7 +148,6 @@ async function buildAndSaveDumpsToDB(changes: Array<SuccessfulChange>): Promise<
continue; continue;
} }
const dump = await GenericWrapperActions.dump(variant); const dump = await GenericWrapperActions.dump(variant);
console.warn('change.updatedHash', change.updatedHash);
await ConfigDumpData.saveConfigDump({ await ConfigDumpData.saveConfigDump({
data: dump, data: dump,
publicKey: change.publicKey, publicKey: change.publicKey,
@ -205,7 +199,8 @@ class ConfigurationSyncJob extends PersistedJob<ConfigurationSyncPersistedData>
await UserConfigWrapperActions.setName(name || ''); await UserConfigWrapperActions.setName(name || '');
if (profileKey && pointer) { if (profileKey && pointer) {
await UserConfigWrapperActions.setProfilePicture(pointer, from_string(profileKey)); const profileKeyArray = fromHexToArray(profileKey);
await UserConfigWrapperActions.setProfilePicture(pointer, profileKeyArray);
} else { } else {
await UserConfigWrapperActions.setProfilePicture('', new Uint8Array()); await UserConfigWrapperActions.setProfilePicture('', new Uint8Array());
} }
@ -235,9 +230,6 @@ class ConfigurationSyncJob extends PersistedJob<ConfigurationSyncPersistedData>
}) })
); );
console.warn(
`ConfigurationSyncJob sendToPubKeyNonDurably ${this.persistedData.identifier} returned: "${allResults}"`
);
// we do a sequence call here. If we do not have the right expected number of results, consider it // we do a sequence call here. If we do not have the right expected number of results, consider it
if (!isArray(allResults) || allResults.length !== singleDestChanges.length) { if (!isArray(allResults) || allResults.length !== singleDestChanges.length) {

View File

@ -160,7 +160,6 @@ async function pendingChangesForPubkey(pubkey: string): Promise<Array<OutgoingCo
const results: Array<OutgoingConfResult> = []; const results: Array<OutgoingConfResult> = [];
debugger;
for (let index = 0; index < dumps.length; index++) { for (let index = 0; index < dumps.length; index++) {
const dump = dumps[index]; const dump = dumps[index];
const variant = dump.variant; const variant = dump.variant;

View File

@ -5,7 +5,7 @@ import { from_hex, from_string } from 'libsodium-wrappers-sumo';
// tslint:disable: chai-vague-errors no-unused-expression no-http-string no-octal-literal whitespace no-require-imports variable-name // tslint:disable: chai-vague-errors no-unused-expression no-http-string no-octal-literal whitespace no-require-imports variable-name
import * as SessionUtilWrapper from 'session_util_wrapper'; import * as SessionUtilWrapper from 'session_util_wrapper';
describe('libsession_wrapper_contacts ', () => { describe('libsession_contacts', () => {
// Note: To run this test, you need to compile the libsession wrapper for node (and not for electron). // Note: To run this test, you need to compile the libsession wrapper for node (and not for electron).
// To do this, you can cd to the node_module/libsession_wrapper folder and do // To do this, you can cd to the node_module/libsession_wrapper folder and do
// yarn configure && yarn build // yarn configure && yarn build
@ -13,7 +13,7 @@ describe('libsession_wrapper_contacts ', () => {
// We have to disable it by filename as nodejs tries to load the module during the import step above, and fails as it is not compiled for nodejs but for electron. // We have to disable it by filename as nodejs tries to load the module during the import step above, and fails as it is not compiled for nodejs but for electron.
it('[config][contacts]', () => { it('libsession_contacts', () => {
const edSecretKey = from_hex( const edSecretKey = from_hex(
'0123456789abcdef0123456789abcdef000000000000000000000000000000004cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7' '0123456789abcdef0123456789abcdef000000000000000000000000000000004cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7'
); );
@ -186,7 +186,7 @@ describe('libsession_wrapper_contacts ', () => {
expect(nicknames2).to.be.deep.eq(['(N/A)', 'Nickname 3']); expect(nicknames2).to.be.deep.eq(['(N/A)', 'Nickname 3']);
}); });
it('[config][contacts][c]', () => { it('libsession_contacts_c', () => {
const edSecretKey = from_hex( const edSecretKey = from_hex(
'0123456789abcdef0123456789abcdef000000000000000000000000000000004cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7' '0123456789abcdef0123456789abcdef000000000000000000000000000000004cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7'
); );

View File

@ -7,7 +7,7 @@ import { concatUInt8Array } from '../../../../session/crypto';
// tslint:disable: chai-vague-errors no-unused-expression no-http-string no-octal-literal whitespace // tslint:disable: chai-vague-errors no-unused-expression no-http-string no-octal-literal whitespace
describe('libsession_wrapper', () => { describe('libsession_wrapper', () => {
it('[config][user_profile][c]', () => { it('libsession_user', () => {
// Note: To run this test, you need to compile the libsession wrapper for node (and not for electron). // Note: To run this test, you need to compile the libsession wrapper for node (and not for electron).
// To do this, you can cd to the node_module/libsession_wrapper folder and do // To do this, you can cd to the node_module/libsession_wrapper folder and do
// yarn configure && yarn build // yarn configure && yarn build
@ -46,7 +46,14 @@ describe('libsession_wrapper', () => {
expect(picResult.key).to.be.null; expect(picResult.key).to.be.null;
// Now let's go set a profile name and picture: // Now let's go set a profile name and picture:
conf.setProfilePicture('http://example.org/omg-pic-123.bmp', stringToUint8Array('secret')); conf.setProfilePicture(
'http://example.org/omg-pic-123.bmp',
new Uint8Array([115, 101, 99, 114, 101, 116]) // 'secret' in ascii
);
expect(conf.getProfilePicture().key).to.be.deep.eq(
new Uint8Array([115, 101, 99, 114, 101, 116]) // 'secret' in ascii
);
conf.setName('Kallie'); conf.setName('Kallie');
// Retrieve them just to make sure they set properly: // Retrieve them just to make sure they set properly:
@ -58,7 +65,9 @@ describe('libsession_wrapper', () => {
const picture = conf.getProfilePicture(); const picture = conf.getProfilePicture();
expect(picture.url).to.be.eq('http://example.org/omg-pic-123.bmp'); expect(picture.url).to.be.eq('http://example.org/omg-pic-123.bmp');
expect(picture.key).to.be.deep.eq(stringToUint8Array('secret')); expect(picture.key).to.be.deep.eq(
new Uint8Array([115, 101, 99, 114, 101, 116]) // 'secret' in ascii
);
// Since we've made changes, we should need to push new config to the swarm, *and* should need // Since we've made changes, we should need to push new config to the swarm, *and* should need
// to dump the updated state: // to dump the updated state:

View File

@ -95,8 +95,8 @@ onmessage = async (e: { data: [number, ConfigWrapperObjectTypes, string, ...any]
} }
const wrapper = await getCorrespondingWrapper(config); const wrapper = await getCorrespondingWrapper(config);
const fn = (wrapper as any)[action]; const fn = (wrapper as any)[action];
if (!fn) { if (!fn) {
throw new Error( throw new Error(
`Worker: job "${jobId}" did not find function "${action}" on config "${config}"` `Worker: job "${jobId}" did not find function "${action}" on config "${config}"`