diff --git a/background.html b/background.html
index b14e5ee3f..5e70bbd4d 100644
--- a/background.html
+++ b/background.html
@@ -29,6 +29,7 @@
+
diff --git a/package.json b/package.json
index 2904f7844..1a45a769a 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"p-retry": "^4.2.0",
"pify": "3.0.0",
"protobufjs": "^6.11.2",
+ "queue-promise": "^2.2.1",
"rc-slider": "^8.7.1",
"react": "^17.0.2",
"react-contexify": "5.0.0",
diff --git a/ts/components/conversation/media-gallery/LoadingIndicator.tsx b/ts/components/conversation/media-gallery/LoadingIndicator.tsx
deleted file mode 100644
index 349560a8e..000000000
--- a/ts/components/conversation/media-gallery/LoadingIndicator.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-
-export const LoadingIndicator = () => {
- return (
-
- );
-};
diff --git a/ts/node/sql.ts b/ts/node/sql.ts
index c197d9b97..22eda7ed2 100644
--- a/ts/node/sql.ts
+++ b/ts/node/sql.ts
@@ -139,7 +139,7 @@ function openAndMigrateDatabase(filePath: string, key: string) {
// First, we try to open the database without any cipher changes
try {
- db = new BetterSqlite3.default(filePath, openDbOptions);
+ db = new (BetterSqlite3 as any).default(filePath, openDbOptions);
keyDatabase(db, key);
switchToWAL(db);
@@ -159,7 +159,7 @@ function openAndMigrateDatabase(filePath: string, key: string) {
let db1;
try {
- db1 = new BetterSqlite3.default(filePath, openDbOptions);
+ db1 = new (BetterSqlite3 as any).default(filePath, openDbOptions);
keyDatabase(db1, key);
// https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0
@@ -177,7 +177,7 @@ function openAndMigrateDatabase(filePath: string, key: string) {
// migrate to the latest ciphers after we've modified the defaults.
let db2;
try {
- db2 = new BetterSqlite3.default(filePath, openDbOptions);
+ db2 = new (BetterSqlite3 as any).default(filePath, openDbOptions);
keyDatabase(db2, key);
db2.pragma('cipher_migrate');
diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts
index e76ccf46d..d7eafadac 100644
--- a/ts/receiver/configMessage.ts
+++ b/ts/receiver/configMessage.ts
@@ -14,15 +14,14 @@ import { configurationMessageReceived, trigger } from '../shims/events';
import { BlockedNumberController } from '../util';
import { removeFromCache } from './cache';
import { handleNewClosedGroup } from './closedGroups';
-import { updateProfileOneAtATime } from './dataMessage';
import { EnvelopePlus } from './types';
import { ConversationInteraction } from '../interactions';
import { getLastProfileUpdateTimestamp, setLastProfileUpdateTimestamp } from '../util/storage';
+import { appendFetchAvatarAndProfileJob, updateOurProfileSync } from './userProfileImageUpdates';
async function handleOurProfileUpdate(
sentAt: number | Long,
- configMessage: SignalService.ConfigurationMessage,
- ourPubkey: string
+ configMessage: SignalService.ConfigurationMessage
) {
const latestProfileUpdateTimestamp = getLastProfileUpdateTimestamp();
if (!latestProfileUpdateTimestamp || sentAt > latestProfileUpdateTimestamp) {
@@ -31,17 +30,11 @@ async function handleOurProfileUpdate(
);
const { profileKey, profilePicture, displayName } = configMessage;
- const ourConversation = getConversationController().get(ourPubkey);
- if (!ourConversation) {
- window?.log?.error('We need a convo with ourself at all times');
- return;
- }
-
const lokiProfile = {
displayName,
profilePicture,
};
- await updateProfileOneAtATime(ourConversation, lokiProfile, profileKey);
+ await updateOurProfileSync(lokiProfile, profileKey);
await setLastProfileUpdateTimestamp(_.toNumber(sentAt));
// do not trigger a signin by linking if the display name is empty
if (displayName) {
@@ -192,7 +185,7 @@ const handleContactFromConfig = async (
await BlockedNumberController.unblock(contactConvo.id);
}
- void updateProfileOneAtATime(contactConvo, profile, contactReceived.profileKey);
+ void appendFetchAvatarAndProfileJob(contactConvo, profile, contactReceived.profileKey);
} catch (e) {
window?.log?.warn('failed to handle a new closed group from configuration message');
}
@@ -213,7 +206,7 @@ export async function handleConfigurationMessage(
return removeFromCache(envelope);
}
- await handleOurProfileUpdate(envelope.timestamp, configurationMessage, ourPubkey);
+ await handleOurProfileUpdate(envelope.timestamp, configurationMessage);
await handleGroupsAndContactsFromConfigMessage(envelope, configurationMessage);
diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts
index 97c3734bf..7d22f68a6 100644
--- a/ts/receiver/dataMessage.ts
+++ b/ts/receiver/dataMessage.ts
@@ -5,7 +5,6 @@ import { getEnvelopeId } from './common';
import { PubKey } from '../session/types';
import { handleMessageJob, toRegularMessage } from './queuedJob';
-import { downloadAttachment } from './attachments';
import _ from 'lodash';
import { StringUtils, UserUtils } from '../session/utils';
import { getConversationController } from '../session/conversations';
@@ -15,104 +14,15 @@ import {
getMessageBySenderAndServerTimestamp,
} from '../../ts/data/data';
import { ConversationModel, ConversationTypeEnum } from '../models/conversation';
-import { allowOnlyOneAtATime } from '../session/utils/Promise';
-import { toHex } from '../session/utils/String';
import { toLogFormat } from '../types/attachments/Errors';
-import { processNewAttachment } from '../types/MessageAttachment';
-import { MIME } from '../types';
-import { autoScaleForIncomingAvatar } from '../util/attachmentsUtil';
+
import {
createSwarmMessageSentFromNotUs,
createSwarmMessageSentFromUs,
} from '../models/messageFactory';
import { MessageModel } from '../models/message';
import { isUsFromCache } from '../session/utils/User';
-import { decryptProfile } from '../util/crypto/profileEncrypter';
-import ByteBuffer from 'bytebuffer';
-
-export async function updateProfileOneAtATime(
- conversation: ConversationModel,
- profile: SignalService.DataMessage.ILokiProfile,
- profileKey?: Uint8Array | null // was any
-) {
- if (!conversation?.id) {
- window?.log?.warn('Cannot update profile with empty convoid');
- return;
- }
- const oneAtaTimeStr = `updateProfileOneAtATime:${conversation.id}`;
- return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
- return createOrUpdateProfile(conversation, profile, profileKey);
- });
-}
-
-/**
- * Creates a new profile from the profile provided. Creates the profile if it doesn't exist.
- */
-async function createOrUpdateProfile(
- conversation: ConversationModel,
- profile: SignalService.DataMessage.ILokiProfile,
- profileKey?: Uint8Array | null
-) {
- // Retain old values unless changed:
- const newProfile = conversation.get('profile') || {};
-
- newProfile.displayName = profile.displayName;
-
- if (profile.profilePicture && profileKey) {
- const prevPointer = conversation.get('avatarPointer');
- const needsUpdate = !prevPointer || !_.isEqual(prevPointer, profile.profilePicture);
-
- if (needsUpdate) {
- try {
- const downloaded = await downloadAttachment({
- url: profile.profilePicture,
- isRaw: true,
- });
-
- // null => use placeholder with color and first letter
- let path = null;
- if (profileKey) {
- // Convert profileKey to ArrayBuffer, if needed
- const encoding = typeof profileKey === 'string' ? 'base64' : null;
- try {
- const profileKeyArrayBuffer = dcodeIO.ByteBuffer.wrap(
- profileKey,
- encoding
- ).toArrayBuffer();
- const decryptedData = await decryptProfile(downloaded.data, profileKeyArrayBuffer);
-
- const scaledData = await autoScaleForIncomingAvatar(decryptedData);
- const upgraded = await processNewAttachment({
- data: await scaledData.blob.arrayBuffer(),
- contentType: MIME.IMAGE_UNKNOWN, // contentType is mostly used to generate previews and screenshot. We do not care for those in this case.
- });
- // Only update the convo if the download and decrypt is a success
- conversation.set('avatarPointer', profile.profilePicture);
- conversation.set('profileKey', toHex(profileKey));
- ({ path } = upgraded);
- } catch (e) {
- window?.log?.error(`Could not decrypt profile image: ${e}`);
- }
- }
- newProfile.avatar = path;
- } catch (e) {
- window.log.warn(
- `Failed to download attachment at ${profile.profilePicture}. Maybe it expired? ${e.message}`
- );
- // do not return here, we still want to update the display name even if the avatar failed to download
- }
- }
- } else if (profileKey) {
- newProfile.avatar = null;
- }
-
- const conv = await getConversationController().getOrCreateAndWait(
- conversation.id,
- ConversationTypeEnum.PRIVATE
- );
- await conv.setLokiProfile(newProfile);
- await conv.commit();
-}
+import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
function cleanAttachment(attachment: any) {
return {
@@ -303,7 +213,7 @@ export async function handleSwarmDataMessage(
cleanDataMessage.profileKey?.length
) {
// do not await this
- void updateProfileOneAtATime(
+ void appendFetchAvatarAndProfileJob(
senderConversationModel,
cleanDataMessage.profile,
cleanDataMessage.profileKey
diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts
index 3ce440558..7e3625d9e 100644
--- a/ts/receiver/queuedJob.ts
+++ b/ts/receiver/queuedJob.ts
@@ -8,13 +8,13 @@ import { ConversationModel, ConversationTypeEnum } from '../models/conversation'
import { MessageModel } from '../models/message';
import { getMessageById, getMessageCountByType, getMessagesBySentAt } from '../../ts/data/data';
-import { updateProfileOneAtATime } from './dataMessage';
import { SignalService } from '../protobuf';
import { UserUtils } from '../session/utils';
import { showMessageRequestBanner } from '../state/ducks/userConfig';
import { MessageDirection } from '../models/messageType';
import { LinkPreviews } from '../util/linkPreviews';
import { GoogleChrome } from '../util';
+import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
function contentTypeSupported(type: string): boolean {
const Chrome = GoogleChrome;
@@ -295,7 +295,7 @@ async function handleRegularMessage(
// the only profile we don't update with what is coming here is ours,
// as our profile is shared accross our devices with a ConfigurationMessage
if (type === 'incoming' && rawDataMessage.profile) {
- void updateProfileOneAtATime(
+ void appendFetchAvatarAndProfileJob(
sendingDeviceConversation,
rawDataMessage.profile,
rawDataMessage.profileKey
diff --git a/ts/receiver/userProfileImageUpdates.ts b/ts/receiver/userProfileImageUpdates.ts
new file mode 100644
index 000000000..fd624d454
--- /dev/null
+++ b/ts/receiver/userProfileImageUpdates.ts
@@ -0,0 +1,157 @@
+import Queue from 'queue-promise';
+import ByteBuffer from 'bytebuffer';
+import _ from 'lodash';
+
+import { downloadAttachment } from './attachments';
+
+import { allowOnlyOneAtATime, hasAlreadyOneAtaTimeMatching } from '../session/utils/Promise';
+import { toHex } from '../session/utils/String';
+import { processNewAttachment } from '../types/MessageAttachment';
+import { MIME } from '../types';
+import { autoScaleForIncomingAvatar } from '../util/attachmentsUtil';
+import { decryptProfile } from '../util/crypto/profileEncrypter';
+import { ConversationModel, ConversationTypeEnum } from '../models/conversation';
+import { SignalService } from '../protobuf';
+import { getConversationController } from '../session/conversations';
+import { UserUtils } from '../session/utils';
+
+const queue = new Queue({
+ concurrent: 1,
+ interval: 500,
+});
+
+queue.on('dequeue', () => {
+ // window.log.info('[profile-update] queue is dequeuing');
+});
+queue.on('resolve', () => {
+ // window.log.info('[profile-update] task resolved');
+});
+queue.on('reject', error => {
+ window.log.warn('[profile-update] task profile image update failed with', error);
+});
+queue.on('start', () => {
+ window.log.info('[profile-update] queue is starting');
+});
+queue.on('stop', () => {
+ window.log.info('[profile-update] queue is stopping');
+});
+queue.on('end', () => {
+ window.log.info('[profile-update] queue is ending');
+});
+
+export async function appendFetchAvatarAndProfileJob(
+ conversation: ConversationModel,
+ profile: SignalService.DataMessage.ILokiProfile,
+ profileKey?: Uint8Array | null // was any
+) {
+ if (!conversation?.id) {
+ window?.log?.warn('[profile-update] Cannot update profile with empty convoid');
+ return;
+ }
+ const oneAtaTimeStr = `appendFetchAvatarAndProfileJob:${conversation.id}`;
+
+ if (hasAlreadyOneAtaTimeMatching(oneAtaTimeStr)) {
+ window.log.info(
+ '[profile-update] not adding another task of "appendFetchAvatarAndProfileJob" as there is already one scheduled for the conversation: ',
+ conversation.id
+ );
+ return;
+ }
+ window.log.info(
+ '[profile-update] "appendFetchAvatarAndProfileJob" as there is already one scheduled for the conversation: ',
+ conversation.id
+ );
+ const task = allowOnlyOneAtATime(oneAtaTimeStr, async () => {
+ return createOrUpdateProfile(conversation, profile, profileKey);
+ });
+
+ queue.enqueue(async () => task);
+}
+
+/**
+ * This function should be used only when we have to do a sync update to our conversation with a new profile/avatar image or display name
+ * It tries to fetch the profile image, scale it, save it, and update the conversationModel
+ */
+export async function updateOurProfileSync(
+ profile: SignalService.DataMessage.ILokiProfile,
+ profileKey?: Uint8Array | null // was any
+) {
+ const ourConvo = getConversationController().get(UserUtils.getOurPubKeyStrFromCache());
+ if (!ourConvo?.id) {
+ window?.log?.warn('[profile-update] Cannot update our profile with empty convoid');
+ return;
+ }
+ const oneAtaTimeStr = `appendFetchAvatarAndProfileJob:${ourConvo.id}`;
+ return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
+ return createOrUpdateProfile(ourConvo, profile, profileKey);
+ });
+}
+
+/**
+ * Creates a new profile from the profile provided. Creates the profile if it doesn't exist.
+ */
+async function createOrUpdateProfile(
+ conversation: ConversationModel,
+ profile: SignalService.DataMessage.ILokiProfile,
+ profileKey?: Uint8Array | null
+) {
+ // Retain old values unless changed:
+ const newProfile = conversation.get('profile') || {};
+
+ newProfile.displayName = profile.displayName;
+
+ if (profile.profilePicture && profileKey) {
+ const prevPointer = conversation.get('avatarPointer');
+ const needsUpdate = !prevPointer || !_.isEqual(prevPointer, profile.profilePicture);
+
+ if (needsUpdate) {
+ try {
+ const downloaded = await downloadAttachment({
+ url: profile.profilePicture,
+ isRaw: true,
+ });
+
+ // null => use placeholder with color and first letter
+ let path = null;
+ if (profileKey) {
+ // Convert profileKey to ArrayBuffer, if needed
+ const encoding = typeof profileKey === 'string' ? 'base64' : undefined;
+ try {
+ const profileKeyArrayBuffer = ByteBuffer.wrap(profileKey, encoding).toArrayBuffer();
+ const decryptedData = await decryptProfile(downloaded.data, profileKeyArrayBuffer);
+ window.log.info(
+ `[profile-update] about to auto scale avatar for convo ${conversation.id}`
+ );
+
+ const scaledData = await autoScaleForIncomingAvatar(decryptedData);
+ const upgraded = await processNewAttachment({
+ data: await scaledData.blob.arrayBuffer(),
+ contentType: MIME.IMAGE_UNKNOWN, // contentType is mostly used to generate previews and screenshot. We do not care for those in this case.
+ });
+ // Only update the convo if the download and decrypt is a success
+ conversation.set('avatarPointer', profile.profilePicture);
+ conversation.set('profileKey', toHex(profileKey));
+ ({ path } = upgraded);
+ } catch (e) {
+ window?.log?.error(`[profile-update] Could not decrypt profile image: ${e}`);
+ }
+ }
+ newProfile.avatar = path;
+ } catch (e) {
+ window.log.warn(
+ `[profile-update] Failed to download attachment at ${profile.profilePicture}. Maybe it expired? ${e.message}`
+ );
+ // do not return here, we still want to update the display name even if the avatar failed to download
+ }
+ }
+ } else if (profileKey) {
+ newProfile.avatar = null;
+ }
+
+ const conv = await getConversationController().getOrCreateAndWait(
+ conversation.id,
+ ConversationTypeEnum.PRIVATE
+ );
+ await conv.setLokiProfile(newProfile);
+ await conv.commit();
+}
diff --git a/ts/session/apis/file_server_api/FileServerApiV2.ts b/ts/session/apis/file_server_api/FileServerApiV2.ts
index 3496d20ea..16536f610 100644
--- a/ts/session/apis/file_server_api/FileServerApiV2.ts
+++ b/ts/session/apis/file_server_api/FileServerApiV2.ts
@@ -143,9 +143,8 @@ export const buildUrl = (request: FileServerV2Request | OpenGroupV2Request): URL
};
/**
- * Upload a file to the file server v2
- * @param fileContent the data to send
- * @returns null or the fileID and complete URL to share this file
+ * Fetch the latest desktop release available on github from the fileserver.
+ * This call is onion routed and so do not expose our ip to github nor the file server.
*/
export const getLatestDesktopReleaseFileToFsV2 = async (): Promise => {
const queryParams = {
diff --git a/ts/session/utils/Promise.ts b/ts/session/utils/Promise.ts
index 49d0e765a..3b1bfbb49 100644
--- a/ts/session/utils/Promise.ts
+++ b/ts/session/utils/Promise.ts
@@ -15,7 +15,7 @@ export class TaskTimedOutError extends Error {
}
// one action resolves all
-const snodeGlobalLocks: Record> = {};
+const oneAtaTimeRecord: Record> = {};
export async function allowOnlyOneAtATime(
name: string,
@@ -23,16 +23,16 @@ export async function allowOnlyOneAtATime(
timeoutMs?: number
) {
// if currently not in progress
- if (snodeGlobalLocks[name] === undefined) {
+ if (oneAtaTimeRecord[name] === undefined) {
// set lock
- snodeGlobalLocks[name] = new Promise(async (resolve, reject) => {
+ oneAtaTimeRecord[name] = new Promise(async (resolve, reject) => {
// set up timeout feature
let timeoutTimer = null;
if (timeoutMs) {
timeoutTimer = setTimeout(() => {
- window?.log?.warn(`allowOnlyOneAtATime - TIMEDOUT after ${timeoutMs}s`);
+ window?.log?.warn(`allowOnlyOneAtATime - TIMEDOUT after ${timeoutMs}ms`);
// tslint:disable-next-line: no-dynamic-delete
- delete snodeGlobalLocks[name]; // clear lock
+ delete oneAtaTimeRecord[name]; // clear lock
reject();
}, timeoutMs);
}
@@ -55,7 +55,7 @@ export async function allowOnlyOneAtATime(
}
}
// tslint:disable-next-line: no-dynamic-delete
- delete snodeGlobalLocks[name]; // clear lock
+ delete oneAtaTimeRecord[name]; // clear lock
reject(e);
}
// clear timeout timer
@@ -66,12 +66,16 @@ export async function allowOnlyOneAtATime(
}
}
// tslint:disable-next-line: no-dynamic-delete
- delete snodeGlobalLocks[name]; // clear lock
+ delete oneAtaTimeRecord[name]; // clear lock
// release the kraken
resolve(innerRetVal);
});
}
- return snodeGlobalLocks[name];
+ return oneAtaTimeRecord[name];
+}
+
+export function hasAlreadyOneAtaTimeMatching(text: string): boolean {
+ return Boolean(oneAtaTimeRecord[text]);
}
/**
diff --git a/ts/test/session/unit/utils/Promise_test.ts b/ts/test/session/unit/utils/Promise_test.ts
index 7035d2b0e..7762dc9a0 100644
--- a/ts/test/session/unit/utils/Promise_test.ts
+++ b/ts/test/session/unit/utils/Promise_test.ts
@@ -7,6 +7,13 @@ import { PromiseUtils } from '../../../../session/utils';
// tslint:disable-next-line: no-require-imports no-var-requires
import chaiAsPromised from 'chai-as-promised';
+import {
+ allowOnlyOneAtATime,
+ hasAlreadyOneAtaTimeMatching,
+ sleepFor,
+} from '../../../../session/utils/Promise';
+import { TestUtils } from '../../../test-utils';
+
chai.use(chaiAsPromised as any);
chai.should();
@@ -34,6 +41,7 @@ describe('Promise Utils', () => {
pollSpy = sandbox.spy(PromiseUtils, 'poll');
waitForTaskSpy = sandbox.spy(PromiseUtils, 'waitForTask');
waitUntilSpy = sandbox.spy(PromiseUtils, 'waitUntil');
+ TestUtils.stubWindowLog();
});
afterEach(() => {
@@ -141,4 +149,68 @@ describe('Promise Utils', () => {
return promise.should.eventually.be.rejectedWith('Periodic check timeout');
});
});
+
+ describe('allowOnlyOneAtATime', () => {
+ it('start if not running', async () => {
+ const spy = sinon.spy(async () => {
+ return sleepFor(10);
+ });
+ await allowOnlyOneAtATime('testing', spy);
+ expect(spy.callCount).to.be.eq(1);
+ });
+
+ it('starts only once if already running', async () => {
+ const spy = sinon.spy(async () => {
+ return sleepFor(10);
+ });
+ void allowOnlyOneAtATime('testing', spy);
+
+ await allowOnlyOneAtATime('testing', spy);
+ expect(spy.callCount).to.be.eq(1);
+ });
+
+ it('throw if took longer than expected timeout', async () => {
+ const spy = sinon.spy(async () => {
+ return sleepFor(10);
+ });
+ try {
+ await allowOnlyOneAtATime('testing', spy, 5);
+ throw new Error('should not get here');
+ } catch (e) {
+ console.warn(e);
+ expect(e).to.be.be.eql(undefined, 'should be undefined');
+ }
+
+ expect(spy.callCount).to.be.eq(1);
+ });
+
+ it('does not throw if took less than expected timeout', async () => {
+ const spy = sinon.spy(async () => {
+ return sleepFor(10);
+ });
+ try {
+ await allowOnlyOneAtATime('testing', spy, 15);
+ throw new Error('should get here');
+ } catch (e) {
+ console.warn(e);
+ expect(e.message).to.be.be.eql('should get here');
+ }
+
+ expect(spy.callCount).to.be.eq(1);
+ });
+ });
+
+ describe('hasAlreadyOneAtaTimeMatching', () => {
+ it('returns true if already started', () => {
+ const spy = sinon.spy(async () => {
+ return sleepFor(10);
+ });
+ void allowOnlyOneAtATime('testing', spy);
+ expect(hasAlreadyOneAtaTimeMatching('testing')).to.be.eq(true, 'should be true');
+ });
+
+ it('returns false if not already started', () => {
+ expect(hasAlreadyOneAtaTimeMatching('testing2')).to.be.eq(false, 'should be false');
+ });
+ });
});
diff --git a/ts/types/attachments/migrations.ts b/ts/types/attachments/migrations.ts
index c533754ba..95407a551 100644
--- a/ts/types/attachments/migrations.ts
+++ b/ts/types/attachments/migrations.ts
@@ -20,6 +20,7 @@ import {
readAttachmentData,
writeNewAttachmentData,
} from '../MessageAttachment';
+import { perfEnd, perfStart } from '../../session/utils/Performance';
const DEFAULT_JPEG_QUALITY = 0.85;
@@ -30,10 +31,13 @@ const DEFAULT_JPEG_QUALITY = 0.85;
export const autoOrientJpegImage = async (
fileOrBlobOrURL: string | File | Blob
): Promise => {
+ perfStart(`autoOrientJpegImage`);
const loadedImage = await loadImage(fileOrBlobOrURL, { orientation: true, canvas: true });
-
- const canvas = loadedImage.image as HTMLCanvasElement;
- const dataURL = canvas.toDataURL(MIME.IMAGE_JPEG, DEFAULT_JPEG_QUALITY);
+ perfEnd(`autoOrientJpegImage`, `autoOrientJpegImage`);
+ const dataURL = (loadedImage.image as HTMLCanvasElement).toDataURL(
+ MIME.IMAGE_JPEG,
+ DEFAULT_JPEG_QUALITY
+ );
return dataURL;
};
diff --git a/ts/util/attachmentsUtil.ts b/ts/util/attachmentsUtil.ts
index e23567c1c..270c47044 100644
--- a/ts/util/attachmentsUtil.ts
+++ b/ts/util/attachmentsUtil.ts
@@ -11,6 +11,7 @@ import { THUMBNAIL_SIDE } from '../types/attachments/VisualAttachment';
import imageType from 'image-type';
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../session/constants';
+import { perfEnd, perfStart } from '../session/utils/Performance';
/**
* The logic for sending attachments is as follow:
@@ -64,6 +65,7 @@ export async function autoScaleForAvatar(
canvas: true,
};
+ perfStart(`loadimage-*${blob.size}`);
const canvas = await loadImage(blob, loadImgOpts);
-
+ perfEnd(`loadimage-*${blob.size}`, `loadimage-*${blob.size}`);
if (!canvas || !canvas.originalWidth || !canvas.originalHeight) {
throw new Error('failed to scale image');
}
diff --git a/ts/webworker/workers/util.worker.ts b/ts/webworker/workers/util.worker.ts
index a0632dfd5..4c80b7e85 100644
--- a/ts/webworker/workers/util.worker.ts
+++ b/ts/webworker/workers/util.worker.ts
@@ -167,15 +167,6 @@ async function deriveSymmetricKey(x25519PublicKey: Uint8Array, x25519PrivateKey:
return symmetricKey;
}
-async function generateEphemeralKeyPair() {
- const ran = (await getSodiumWorker()).randombytes_buf(32);
- const keys = generateKeyPair(ran);
- return keys;
- // Signal protocol prepends with "0x05"
- // keys.pubKey = keys.pubKey.slice(1);
- // return { pubKey: keys.public, privKey: keys.private };
-}
-
function assertArrayBufferView(val: any) {
if (!ArrayBuffer.isView(val)) {
throw new Error('val type not correct');
@@ -189,7 +180,11 @@ async function encryptForPubkey(pubkeyX25519str: string, payloadBytes: Uint8Arra
throw new Error('pubkeyX25519str type not correct');
}
assertArrayBufferView(payloadBytes);
- const ephemeral = await generateEphemeralKeyPair();
+ const ran = (await getSodiumWorker()).randombytes_buf(32);
+ const ephemeral = generateKeyPair(ran);
+ // Signal protocol prepends with "0x05"
+ // keys.pubKey = keys.pubKey.slice(1);
+ // return { pubKey: keys.public, privKey: keys.private };
const pubkeyX25519Buffer = fromHexToArray(pubkeyX25519str);
const symmetricKey = await deriveSymmetricKey(
pubkeyX25519Buffer,
diff --git a/yarn.lock b/yarn.lock
index 482594d42..cc8721f38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6080,6 +6080,11 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+queue-promise@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/queue-promise/-/queue-promise-2.2.1.tgz#8de03fb79ba458efcb5ebf76368ab029d2669752"
+ integrity sha512-C3eyRwLF9m6dPV4MtqMVFX+Xmc7keZ9Ievm3jJ/wWM5t3uVbFnGsJXwpYzZ4LaIEcX9bss/mdaKzyrO6xheRuA==
+
quick-lru@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"