Merge contacts when we discover split or duplicated contacts

This commit is contained in:
Scott Nonnenberg 2020-07-10 11:28:49 -07:00
parent 68e432188b
commit 901179440f
32 changed files with 1199 additions and 824 deletions

View file

@ -85,6 +85,12 @@
return `group(${groupId})`;
},
// This is one of the few times that we want to collapse our uuid/e164 pair down into
// just one bit of data. If we have a UUID, we'll send using it.
getSendTarget() {
return this.get('uuid') || this.get('e164');
},
handleMessageError(message, errors) {
this.trigger('messageError', message, errors);
},
@ -318,9 +324,8 @@
}
const groupId = !this.isPrivate() ? this.get('groupId') : null;
const maybeRecipientId = this.get('uuid') || this.get('e164');
const recipientId = this.isPrivate() ? maybeRecipientId : null;
const groupNumbers = this.getRecipients();
const recipientId = this.isPrivate() ? this.getSendTarget() : null;
const sendOptions = this.getSendOptions();
@ -395,10 +400,10 @@
async onNewMessage(message) {
// Clear typing indicator for a given contact if we receive a message from them
const identifier = message.get
? `${message.get('source')}.${message.get('sourceDevice')}`
: `${message.source}.${message.sourceDevice}`;
this.clearContactTypingTimer(identifier);
const deviceId = message.get
? `${message.get('conversationId')}.${message.get('sourceDevice')}`
: `${message.conversationId}.${message.sourceDevice}`;
this.clearContactTypingTimer(deviceId);
this.debouncedUpdateLastMessage();
},
@ -582,7 +587,13 @@
m => !m.hasErrors() && m.isIncoming()
);
const receiptSpecs = readMessages.map(m => ({
sender: m.get('source') || m.get('sourceUuid'),
senderE164: m.get('source'),
senderUuid: m.get('sourceUuid'),
senderId: ConversationController.get({
e164: m.get('source'),
uuid: m.get('sourceUuid'),
lowTrust: true,
}),
timestamp: m.get('sent_at'),
hasErrors: m.hasErrors(),
}));
@ -1167,14 +1178,12 @@
getRecipients() {
if (this.isPrivate()) {
return [this.get('uuid') || this.get('e164')];
return [this.getSendTarget()];
}
const me = ConversationController.getConversationId(
textsecure.storage.user.getUuid() || textsecure.storage.user.getNumber()
);
const me = ConversationController.getOurConversationId();
return _.without(this.get('members'), me).map(memberId => {
const c = ConversationController.get(memberId);
return c.get('uuid') || c.get('e164');
return c.getSendTarget();
});
},
@ -1331,11 +1340,7 @@
const reactionModel = Whisper.Reactions.add({
...outgoingReaction,
fromId:
this.ourNumber ||
this.ourUuid ||
textsecure.storage.user.getNumber() ||
textsecure.storage.user.getUuid(),
fromId: ConversationController.getOurConversationId(),
timestamp,
fromSync: true,
});
@ -1446,9 +1451,7 @@
async sendProfileKeyUpdate() {
const id = this.get('id');
const recipients = this.isPrivate()
? [this.get('uuid') || this.get('e164')]
: this.getRecipients();
const recipients = this.getRecipients();
if (!this.get('profileSharing')) {
window.log.error(
'Attempted to send profileKeyUpdate to conversation without profileSharing enabled',
@ -1477,7 +1480,7 @@
const { clearUnreadMetrics } = window.reduxActions.conversations;
clearUnreadMetrics(this.id);
const destination = this.get('uuid') || this.get('e164');
const destination = this.getSendTarget();
const expireTimer = this.get('expireTimer');
const recipients = this.getRecipients();
@ -1549,7 +1552,7 @@
).map(contact => {
const error = new Error('Network is not available');
error.name = 'SendMessageNetworkError';
error.identifier = contact.get('uuid') || contact.get('e164');
error.identifier = contact.get('id');
return error;
});
await message.saveErrors(errors);
@ -1658,8 +1661,8 @@
async handleMessageSendResult(failoverIdentifiers, unidentifiedDeliveries) {
await Promise.all(
(failoverIdentifiers || []).map(async number => {
const conversation = ConversationController.get(number);
(failoverIdentifiers || []).map(async identifier => {
const conversation = ConversationController.get(identifier);
if (
conversation &&
@ -1677,8 +1680,8 @@
);
await Promise.all(
(unidentifiedDeliveries || []).map(async number => {
const conversation = ConversationController.get(number);
(unidentifiedDeliveries || []).map(async identifier => {
const conversation = ConversationController.get(identifier);
if (
conversation &&
@ -1722,17 +1725,10 @@
getSendMetadata(options = {}) {
const { syncMessage, disableMeCheck } = options;
if (!this.ourNumber && !this.ourUuid) {
return null;
}
// START: this code has an Expiration date of ~2018/11/21
// We don't want to enable unidentified delivery for send unless it is
// also enabled for our own account.
const myId = ConversationController.ensureContactIds({
e164: this.ourNumber,
uuid: this.ourUuid,
});
const myId = ConversationController.getOurConversationId();
const me = ConversationController.get(myId);
if (
!disableMeCheck &&
@ -1903,10 +1899,7 @@
source,
});
source =
source ||
textsecure.storage.user.getNumber() ||
textsecure.storage.user.getUuid();
source = source || ConversationController.getOurConversationId();
// When we add a disappearing messages notification to the conversation, we want it
// to be above the message that initiated that change, hence the subtraction.
@ -1933,7 +1926,7 @@
});
if (this.isPrivate()) {
model.set({ destination: this.get('uuid') || this.get('e164') });
model.set({ destination: this.getSendTarget() });
}
if (model.isOutgoing()) {
model.set({ recipients: this.getRecipients() });
@ -1963,7 +1956,7 @@
const flags =
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
const dataMessage = await textsecure.messaging.getMessageProto(
this.get('uuid') || this.get('e164'),
this.getSendTarget(),
null,
[],
null,
@ -1980,7 +1973,7 @@
if (this.get('type') === 'private') {
promise = textsecure.messaging.sendExpirationTimerUpdateToIdentifier(
this.get('uuid') || this.get('e164'),
this.getSendTarget(),
expireTimer,
message.get('sent_at'),
profileKey,
@ -2180,7 +2173,12 @@
await m.markRead(options.readAt);
return {
sender: m.get('source') || m.get('sourceUuid'),
senderE164: m.get('source'),
senderUuid: m.get('sourceUuid'),
senderId: ConversationController.ensureContactIds({
e164: m.get('source'),
uuid: m.get('sourceUuid'),
}),
timestamp: m.get('sent_at'),
hasErrors: m.hasErrors(),
};
@ -2188,7 +2186,7 @@
);
// Some messages we're marking read are local notifications with no sender
read = _.filter(read, m => Boolean(m.sender));
read = _.filter(read, m => Boolean(m.senderId));
unreadMessages = unreadMessages.filter(m => Boolean(m.isIncoming()));
const unreadCount = unreadMessages.length - read.length;
@ -2206,8 +2204,10 @@
window.log.info(`Sending ${read.length} read syncs`);
// Because syncReadMessages sends to our other devices, and sendReadReceipts goes
// to a contact, we need accessKeys for both.
const { sendOptions } = ConversationController.prepareForSend(
this.ourUuid || this.ourNumber,
const {
sendOptions,
} = ConversationController.prepareForSend(
ConversationController.getOurConversationId(),
{ syncMessage: true }
);
await this.wrapSend(
@ -2222,12 +2222,12 @@
if (storage.get('read-receipt-setting') && this.getAccepted()) {
window.log.info(`Sending ${items.length} read receipts`);
const convoSendOptions = this.getSendOptions();
const receiptsBySender = _.groupBy(items, 'sender');
const receiptsBySender = _.groupBy(items, 'senderId');
await Promise.all(
_.map(receiptsBySender, async (receipts, identifier) => {
_.map(receiptsBySender, async (receipts, senderId) => {
const timestamps = _.map(receipts, 'timestamp');
const c = ConversationController.get(identifier);
const c = ConversationController.get(senderId);
await this.wrapSend(
textsecure.messaging.sendReadReceipts(
c.get('e164'),
@ -2249,28 +2249,33 @@
getProfiles() {
// request all conversation members' keys
let ids = [];
let conversations = [];
if (this.isPrivate()) {
ids = [this.get('uuid') || this.get('e164')];
conversations = [this];
} else {
ids = this.get('members')
.map(id => {
const c = ConversationController.get(id);
return c ? c.get('uuid') || c.get('e164') : null;
})
conversations = this.get('members')
.map(id => ConversationController.get(id))
.filter(Boolean);
}
return Promise.all(_.map(ids, this.getProfile));
return Promise.all(
_.map(conversations, conversation => {
this.getProfile(conversation.get('uuid'), conversation.get('e164'));
})
);
},
async getProfile(id) {
async getProfile(providedUuid, providedE164) {
if (!textsecure.messaging) {
throw new Error(
'Conversation.getProfile: textsecure.messaging not available'
);
}
const c = ConversationController.getOrCreate(id, 'private');
const id = ConversationController.ensureContactIds({
uuid: providedUuid,
e164: providedE164,
});
const c = ConversationController.get(id);
const {
generateProfileKeyCredentialRequest,
getClientZkProfileOperations,
@ -2294,6 +2299,7 @@
const profileKey = c.get('profileKey');
const uuid = c.get('uuid');
const identifier = c.getSendTarget();
const profileKeyVersionHex = c.get('profileKeyVersion');
const existingProfileKeyCredential = c.get('profileKeyCredential');
@ -2317,11 +2323,11 @@
const sendMetadata = c.getSendMetadata({ disableMeCheck: true }) || {};
const getInfo =
sendMetadata[c.get('e164')] || sendMetadata[c.get('uuid')] || {};
sendMetadata[c.get('uuid')] || sendMetadata[c.get('e164')] || {};
if (getInfo.accessKey) {
try {
profile = await textsecure.messaging.getProfile(id, {
profile = await textsecure.messaging.getProfile(identifier, {
accessKey: getInfo.accessKey,
profileKeyVersion: profileKeyVersionHex,
profileKeyCredentialRequest: profileKeyCredentialRequestHex,
@ -2332,7 +2338,7 @@
`Setting sealedSender to DISABLED for conversation ${c.idForLogging()}`
);
c.set({ sealedSender: SEALED_SENDER.DISABLED });
profile = await textsecure.messaging.getProfile(id, {
profile = await textsecure.messaging.getProfile(identifier, {
profileKeyVersion: profileKeyVersionHex,
profileKeyCredentialRequest: profileKeyCredentialRequestHex,
});
@ -2341,7 +2347,7 @@
}
}
} else {
profile = await textsecure.messaging.getProfile(id, {
profile = await textsecure.messaging.getProfile(identifier, {
profileKeyVersion: profileKeyVersionHex,
profileKeyCredentialRequest: profileKeyCredentialRequestHex,
});
@ -2349,14 +2355,14 @@
const identityKey = base64ToArrayBuffer(profile.identityKey);
const changed = await textsecure.storage.protocol.saveIdentity(
`${id}.1`,
`${identifier}.1`,
identityKey,
false
);
if (changed) {
// save identity will close all sessions except for .1, so we
// must close that one manually.
const address = new libsignal.SignalProtocolAddress(id, 1);
const address = new libsignal.SignalProtocolAddress(identifier, 1);
window.log.info('closing session for', address.toString());
const sessionCipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
@ -2421,7 +2427,7 @@
if (error.code !== 403 && error.code !== 404) {
window.log.warn(
'getProfile failure:',
id,
c.idForLogging(),
error && error.stack ? error.stack : error
);
} else {
@ -2435,7 +2441,7 @@
} catch (error) {
window.log.warn(
'getProfile decryption failure:',
id,
c.idForLogging(),
error && error.stack ? error.stack : error
);
await c.dropProfileKey();
@ -2780,10 +2786,9 @@
const conversationId = this.id;
const sender = await ConversationController.getOrCreateAndWait(
reaction ? reaction.get('fromId') : message.get('source'),
'private'
);
const sender = reaction
? ConversationController.get(reaction.get('fromId'))
: message.getContact();
const iconUrl = await sender.getNotificationIcon();
@ -2805,42 +2810,33 @@
},
notifyTyping(options = {}) {
const {
isTyping,
sender,
senderUuid,
senderId,
isMe,
senderDevice,
} = options;
const { isTyping, senderId, isMe, senderDevice } = options;
// We don't do anything with typing messages from our other devices
if (isMe) {
return;
}
const identifier = `${sender}.${senderDevice}`;
const deviceId = `${senderId}.${senderDevice}`;
this.contactTypingTimers = this.contactTypingTimers || {};
const record = this.contactTypingTimers[identifier];
const record = this.contactTypingTimers[deviceId];
if (record) {
clearTimeout(record.timer);
}
if (isTyping) {
this.contactTypingTimers[identifier] = this.contactTypingTimers[
identifier
this.contactTypingTimers[deviceId] = this.contactTypingTimers[
deviceId
] || {
timestamp: Date.now(),
sender,
senderId,
senderUuid,
senderDevice,
};
this.contactTypingTimers[identifier].timer = setTimeout(
this.clearContactTypingTimer.bind(this, identifier),
this.contactTypingTimers[deviceId].timer = setTimeout(
this.clearContactTypingTimer.bind(this, deviceId),
15 * 1000
);
if (!record) {
@ -2848,7 +2844,7 @@
this.trigger('change', this);
}
} else {
delete this.contactTypingTimers[identifier];
delete this.contactTypingTimers[deviceId];
if (record) {
// User was previously typing, and is no longer. State change!
this.trigger('change', this);
@ -2856,13 +2852,13 @@
}
},
clearContactTypingTimer(identifier) {
clearContactTypingTimer(deviceId) {
this.contactTypingTimers = this.contactTypingTimers || {};
const record = this.contactTypingTimers[identifier];
const record = this.contactTypingTimers[deviceId];
if (record) {
clearTimeout(record.timer);
delete this.contactTypingTimers[identifier];
delete this.contactTypingTimers[deviceId];
// User was previously typing, but timed out or we received message. State change!
this.trigger('change', this);
@ -2879,9 +2875,9 @@
* than just their id.
*/
initialize() {
this._byE164 = {};
this._byUuid = {};
this._byGroupId = {};
this._byE164 = Object.create(null);
this._byUuid = Object.create(null);
this._byGroupId = Object.create(null);
this.on('idUpdated', (model, idProp, oldValue) => {
if (oldValue) {
if (idProp === 'e164') {
@ -2908,9 +2904,9 @@
reset(...args) {
Backbone.Collection.prototype.reset.apply(this, args);
this._byE164 = {};
this._byUuid = {};
this._byGroupId = {};
this._byE164 = Object.create(null);
this._byUuid = Object.create(null);
this._byGroupId = Object.create(null);
},
add(...models) {
@ -2918,12 +2914,22 @@
[].concat(res).forEach(model => {
const e164 = model.get('e164');
if (e164) {
this._byE164[e164] = model;
const existing = this._byE164[e164];
// Prefer the contact with both e164 and uuid
if (!existing || (existing && !existing.get('uuid'))) {
this._byE164[e164] = model;
}
}
const uuid = model.get('uuid');
if (uuid) {
this._byUuid[uuid] = model;
const existing = this._byUuid[uuid];
// Prefer the contact with both e164 and uuid
if (!existing || (existing && !existing.get('e164'))) {
this._byUuid[uuid] = model;
}
}
const groupId = model.get('groupId');

View file

@ -193,30 +193,20 @@
// Other top-level prop-generation
getPropsForSearchResult() {
const sourceE164 = this.getSource();
const sourceUuid = this.getSourceUuid();
const fromContact = this.findAndFormatContact(sourceE164 || sourceUuid);
const ourId = ConversationController.getOurConversationId();
const sourceId = this.getContactId();
const fromContact = this.findAndFormatContact(sourceId);
if (
(sourceE164 && sourceE164 === this.OUR_NUMBER) ||
(sourceUuid && sourceUuid === this.OUR_UUID)
) {
if (ourId === sourceId) {
fromContact.isMe = true;
}
const convo = this.getConversation();
let to = convo ? this.findAndFormatContact(convo.get('id')) : {};
const to = convo ? this.findAndFormatContact(convo.get('id')) : {};
if (convo && convo.isMe()) {
if (to && convo && convo.isMe()) {
to.isMe = true;
} else if (
(sourceE164 && convo && sourceE164 === convo.get('e164')) ||
(sourceUuid && convo && sourceUuid === convo.get('uuid'))
) {
to = {
isMe: true,
};
}
return {
@ -237,10 +227,10 @@
const unidentifiedLookup = (
this.get('unidentifiedDeliveries') || []
).reduce((accumulator, uuidOrE164) => {
).reduce((accumulator, identifier) => {
// eslint-disable-next-line no-param-reassign
accumulator[
ConversationController.getConversationId(uuidOrE164)
ConversationController.getConversationId(identifier)
] = true;
return accumulator;
}, Object.create(null));
@ -249,7 +239,7 @@
// Older messages don't have the recipients included on the message, so we fall
// back to the conversation's current recipients
const conversationIds = this.isIncoming()
? [this.getContact().get('id')]
? [this.getContactId()]
: _.union(
(this.get('sent_to') || []).map(id =>
ConversationController.getConversationId(id)
@ -379,11 +369,11 @@
getPropsForUnsupportedMessage() {
const requiredVersion = this.get('requiredProtocolVersion');
const canProcessNow = this.CURRENT_PROTOCOL_VERSION >= requiredVersion;
const phoneNumber = this.getSource();
const sourceId = this.getContactId();
return {
canProcessNow,
contact: this.findAndFormatContact(phoneNumber),
contact: this.findAndFormatContact(sourceId),
};
},
getPropsForTimerNotification() {
@ -396,8 +386,14 @@
const timespan = Whisper.ExpirationTimerOptions.getName(expireTimer || 0);
const disabled = !expireTimer;
const sourceId = ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
});
const ourId = ConversationController.getOurConversationId();
const basicProps = {
...this.findAndFormatContact(source),
...this.findAndFormatContact(sourceId),
type: 'fromOther',
timespan,
disabled,
@ -408,7 +404,7 @@
...basicProps,
type: 'fromSync',
};
} else if (source === this.OUR_NUMBER || sourceUuid === this.OUR_UUID) {
} else if (sourceId && sourceId === ourId) {
return {
...basicProps,
type: 'fromMe',
@ -430,12 +426,12 @@
getPropsForVerificationNotification() {
const type = this.get('verified') ? 'markVerified' : 'markNotVerified';
const isLocal = this.get('local');
const phoneNumber = this.get('verifiedChanged');
const identifier = this.get('verifiedChanged');
return {
type,
isLocal,
contact: this.findAndFormatContact(phoneNumber),
contact: this.findAndFormatContact(identifier),
};
},
getPropsForGroupNotification() {
@ -460,7 +456,7 @@
Array.isArray(groupUpdate.joined)
? groupUpdate.joined
: [groupUpdate.joined],
phoneNumber => this.findAndFormatContact(phoneNumber)
identifier => this.findAndFormatContact(identifier)
),
});
}
@ -477,7 +473,7 @@
Array.isArray(groupUpdate.left)
? groupUpdate.left
: [groupUpdate.left],
phoneNumber => this.findAndFormatContact(phoneNumber)
identifier => this.findAndFormatContact(identifier)
),
});
}
@ -495,9 +491,8 @@
});
}
const sourceE164 = this.getSource();
const sourceUuid = this.getSourceUuid();
const from = this.findAndFormatContact(sourceE164 || sourceUuid);
const sourceId = this.getContactId();
const from = this.findAndFormatContact(sourceId);
return {
from,
@ -537,10 +532,9 @@
.map(attachment => this.getPropsForAttachment(attachment));
},
getPropsForMessage() {
const sourceE164 = this.getSource();
const sourceUuid = this.getSourceUuid();
const contact = this.findAndFormatContact(sourceE164 || sourceUuid);
const contactModel = this.findContact(sourceE164 || sourceUuid);
const sourceId = this.getContactId();
const contact = this.findAndFormatContact(sourceId);
const contactModel = this.findContact(sourceId);
const authorColor = contactModel ? contactModel.getColor() : null;
const authorAvatarPath = contactModel
@ -774,7 +768,13 @@
referencedMessageNotFound,
} = quote;
const contact =
author && ConversationController.get(author || authorUuid);
(author || authorUuid) &&
ConversationController.get(
ConversationController.ensureContactIds({
e164: author,
uuid: authorUuid,
})
);
const authorColor = contact ? contact.getColor() : 'grey';
const authorPhoneNumber = format(author, {
@ -810,17 +810,18 @@
const e164 = conversation.get('e164');
const uuid = conversation.get('uuid');
const conversationId = conversation.get('id');
const readBy = this.get('read_by') || [];
if (includesAny(readBy, identifier, e164, uuid)) {
if (includesAny(readBy, conversationId, e164, uuid)) {
return 'read';
}
const deliveredTo = this.get('delivered_to') || [];
if (includesAny(deliveredTo, identifier, e164, uuid)) {
if (includesAny(deliveredTo, conversationId, e164, uuid)) {
return 'delivered';
}
const sentTo = this.get('sent_to') || [];
if (includesAny(sentTo, identifier, e164, uuid)) {
if (includesAny(sentTo, conversationId, e164, uuid)) {
return 'sent';
}
@ -1220,19 +1221,22 @@
return this.OUR_UUID;
},
getContact() {
getContactId() {
const source = this.getSource();
const sourceUuid = this.getSourceUuid();
if (!source && !sourceUuid) {
return null;
return ConversationController.getOurConversationId();
}
const contactId = ConversationController.ensureContactIds({
return ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
});
return ConversationController.get(contactId, 'private');
},
getContact() {
const id = this.getContactId();
return ConversationController.get(id);
},
isOutgoing() {
return this.get('type') === 'outgoing';
@ -1395,7 +1399,7 @@
let recipients = _.intersection(intendedRecipients, currentRecipients);
recipients = _.without(recipients, successfulRecipients).map(id => {
const c = ConversationController.get(id);
return c.get('uuid') || c.get('e164');
return c.getSendTarget();
});
if (!recipients.length) {
@ -1699,7 +1703,7 @@
try {
this.set({
// These are the same as a normal send()
sent_to: [conv.get('uuid') || conv.get('e164')],
sent_to: [conv.getSendTarget()],
sent: true,
expirationStartTimestamp: Date.now(),
});
@ -1709,8 +1713,8 @@
unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null,
// These are unique to a Note to Self message - immediately read/delivered
delivered_to: [this.OUR_UUID || this.OUR_NUMBER],
read_by: [this.OUR_UUID || this.OUR_NUMBER],
delivered_to: [ConversationController.getOurConversationId()],
read_by: [ConversationController.getOurConversationId()],
});
} catch (result) {
const errors = (result && result.errors) || [
@ -2004,20 +2008,20 @@
return message;
}
const { attachments, id, author } = quote;
const { attachments, id, author, authorUuid } = quote;
const firstAttachment = attachments[0];
const authorConversationId = ConversationController.ensureContactIds({
e164: author,
uuid: authorUuid,
});
const collection = await window.Signal.Data.getMessagesBySentAt(id, {
MessageCollection: Whisper.MessageCollection,
});
const found = collection.find(item => {
const messageAuthor = item.getContact();
const messageAuthorId = item.getContactId();
return (
messageAuthor &&
ConversationController.getConversationId(author) ===
messageAuthor.get('id')
);
return authorConversationId === messageAuthorId;
});
if (!found) {
@ -2119,10 +2123,7 @@
const source = message.get('source');
const sourceUuid = message.get('sourceUuid');
const type = message.get('type');
let conversationId = message.get('conversationId');
if (initialMessage.group) {
conversationId = initialMessage.group.id;
}
const conversationId = message.get('conversationId');
const GROUP_TYPES = textsecure.protobuf.GroupContext.Type;
const conversation = ConversationController.get(conversationId);
@ -2392,10 +2393,13 @@
if (conversation.get('left')) {
window.log.warn('re-added to a left group');
attributes.left = false;
conversation.set({ addedBy: message.getContact().get('id') });
conversation.set({ addedBy: message.getContactId() });
}
} else if (dataMessage.group.type === GROUP_TYPES.QUIT) {
const sender = ConversationController.get(source || sourceUuid);
const sender = ConversationController.ensureContactIds({
e164: source,
uuid: sourceUuid,
});
const inGroup = Boolean(
sender &&
(conversation.get('members') || []).includes(sender.id)
@ -2453,6 +2457,7 @@
message.set({
expirationTimerUpdate: {
source,
sourceUuid,
expireTimer: dataMessage.expireTimer,
},
});
@ -2567,9 +2572,7 @@
e164: source,
uuid: sourceUuid,
});
ConversationController.get(localId, 'private').setProfileKey(
profileKey
);
ConversationController.get(localId).setProfileKey(profileKey);
}
}