Merge contacts when we discover split or duplicated contacts
This commit is contained in:
parent
68e432188b
commit
901179440f
32 changed files with 1199 additions and 824 deletions
127
js/background.js
127
js/background.js
|
@ -25,14 +25,24 @@
|
|||
wait: 500,
|
||||
maxSize: 500,
|
||||
processBatch: async items => {
|
||||
const bySource = _.groupBy(items, item => item.source || item.sourceUuid);
|
||||
const sources = Object.keys(bySource);
|
||||
const byConversationId = _.groupBy(items, item =>
|
||||
ConversationController.ensureContactIds({
|
||||
e164: item.source,
|
||||
uuid: item.sourceUuid,
|
||||
})
|
||||
);
|
||||
const ids = Object.keys(byConversationId);
|
||||
|
||||
for (let i = 0, max = sources.length; i < max; i += 1) {
|
||||
const source = sources[i];
|
||||
const timestamps = bySource[source].map(item => item.timestamp);
|
||||
for (let i = 0, max = ids.length; i < max; i += 1) {
|
||||
const conversationId = ids[i];
|
||||
const timestamps = byConversationId[conversationId].map(
|
||||
item => item.timestamp
|
||||
);
|
||||
|
||||
const c = ConversationController.get(conversationId);
|
||||
const uuid = c.get('uuid');
|
||||
const e164 = c.get('e164');
|
||||
|
||||
const c = ConversationController.get(source);
|
||||
c.queueJob(async () => {
|
||||
try {
|
||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||
|
@ -41,15 +51,15 @@
|
|||
// eslint-disable-next-line no-await-in-loop
|
||||
await wrap(
|
||||
textsecure.messaging.sendDeliveryReceipt(
|
||||
c.get('e164'),
|
||||
c.get('uuid'),
|
||||
e164,
|
||||
uuid,
|
||||
timestamps,
|
||||
sendOptions
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
`Failed to send delivery receipt to ${source} for timestamps ${timestamps}:`,
|
||||
`Failed to send delivery receipt to ${e164}/${uuid} for timestamps ${timestamps}:`,
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
|
@ -577,6 +587,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
window.Signal.conversationControllerStart();
|
||||
try {
|
||||
await Promise.all([
|
||||
ConversationController.load(),
|
||||
|
@ -584,6 +595,7 @@
|
|||
Signal.Emojis.load(),
|
||||
textsecure.storage.protocol.hydrateCaches(),
|
||||
]);
|
||||
await ConversationController.checkForConflicts();
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'background.js: ConversationController failed to load:',
|
||||
|
@ -1759,6 +1771,7 @@
|
|||
|
||||
const deviceId = textsecure.storage.user.getDeviceId();
|
||||
|
||||
// If we didn't capture a UUID on registration, go get it from the server
|
||||
if (!textsecure.storage.user.getUuid()) {
|
||||
const server = WebAPI.connect({
|
||||
username: OLD_USERNAME,
|
||||
|
@ -1954,6 +1967,7 @@
|
|||
const senderId = ConversationController.ensureContactIds({
|
||||
e164: sender,
|
||||
uuid: senderUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
const conversation = ConversationController.get(groupId || senderId);
|
||||
const ourId = ConversationController.getOurConversationId();
|
||||
|
@ -2047,6 +2061,7 @@
|
|||
const detailsId = ConversationController.ensureContactIds({
|
||||
e164: details.number,
|
||||
uuid: details.uuid,
|
||||
highTrust: true,
|
||||
});
|
||||
const conversation = ConversationController.get(detailsId);
|
||||
let activeAt = conversation.get('active_at');
|
||||
|
@ -2236,12 +2251,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const sourceE164 = textsecure.storage.user.getNumber();
|
||||
const sourceUuid = textsecure.storage.user.getUuid();
|
||||
const receivedAt = Date.now();
|
||||
await conversation.updateExpirationTimer(
|
||||
expireTimer,
|
||||
sourceE164 || sourceUuid,
|
||||
ConversationController.getOurConversationId(),
|
||||
receivedAt,
|
||||
{
|
||||
fromSync: true,
|
||||
|
@ -2256,10 +2269,16 @@
|
|||
});
|
||||
|
||||
// Matches event data from `libtextsecure` `MessageReceiver::handleSentMessage`:
|
||||
const getDescriptorForSent = ({ message, destination }) =>
|
||||
const getDescriptorForSent = ({ message, destination, destinationUuid }) =>
|
||||
message.group
|
||||
? getGroupDescriptor(message.group)
|
||||
: { type: Message.PRIVATE, id: destination };
|
||||
: {
|
||||
type: Message.PRIVATE,
|
||||
id: ConversationController.ensureContactIds({
|
||||
e164: destination,
|
||||
uuid: destinationUuid,
|
||||
}),
|
||||
};
|
||||
|
||||
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
|
||||
const getDescriptorForReceived = ({ message, source, sourceUuid }) =>
|
||||
|
@ -2270,6 +2289,7 @@
|
|||
id: ConversationController.ensureContactIds({
|
||||
e164: source,
|
||||
uuid: sourceUuid,
|
||||
highTrust: true,
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -2280,13 +2300,12 @@
|
|||
messageDescriptor,
|
||||
}) {
|
||||
const profileKey = data.message.profileKey.toString('base64');
|
||||
const sender = await ConversationController.getOrCreateAndWait(
|
||||
messageDescriptor.id,
|
||||
'private'
|
||||
);
|
||||
const sender = await ConversationController.get(messageDescriptor.id);
|
||||
|
||||
// Will do the save for us
|
||||
await sender.setProfileKey(profileKey);
|
||||
if (sender) {
|
||||
// Will do the save for us
|
||||
await sender.setProfileKey(profileKey);
|
||||
}
|
||||
|
||||
return confirm();
|
||||
}
|
||||
|
@ -2357,9 +2376,12 @@
|
|||
}
|
||||
|
||||
async function onProfileKeyUpdate({ data, confirm }) {
|
||||
const conversation = ConversationController.get(
|
||||
data.source || data.sourceUuid
|
||||
);
|
||||
const conversationId = ConversationController.ensureContactIds({
|
||||
e164: data.source,
|
||||
uuid: data.sourceUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
const conversation = ConversationController.get(conversationId);
|
||||
|
||||
if (!conversation) {
|
||||
window.log.error(
|
||||
|
@ -2397,11 +2419,8 @@
|
|||
messageDescriptor,
|
||||
}) {
|
||||
// First set profileSharing = true for the conversation we sent to
|
||||
const { id, type } = messageDescriptor;
|
||||
const conversation = await ConversationController.getOrCreateAndWait(
|
||||
id,
|
||||
type
|
||||
);
|
||||
const { id } = messageDescriptor;
|
||||
const conversation = await ConversationController.get(id);
|
||||
|
||||
conversation.enableProfileSharing();
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
|
@ -2417,7 +2436,7 @@
|
|||
return confirm();
|
||||
}
|
||||
|
||||
function createSentMessage(data) {
|
||||
function createSentMessage(data, descriptor) {
|
||||
const now = Date.now();
|
||||
let sentTo = [];
|
||||
|
||||
|
@ -2430,6 +2449,11 @@
|
|||
data.unidentifiedDeliveries = unidentified.map(item => item.destination);
|
||||
}
|
||||
|
||||
const isGroup = descriptor.type === Message.GROUP;
|
||||
const conversationId = isGroup
|
||||
? ConversationController.ensureGroup(descriptor.id)
|
||||
: descriptor.id;
|
||||
|
||||
return new Whisper.Message({
|
||||
source: textsecure.storage.user.getNumber(),
|
||||
sourceUuid: textsecure.storage.user.getUuid(),
|
||||
|
@ -2438,7 +2462,7 @@
|
|||
serverTimestamp: data.serverTimestamp,
|
||||
sent_to: sentTo,
|
||||
received_at: now,
|
||||
conversationId: data.destination,
|
||||
conversationId,
|
||||
type: 'outgoing',
|
||||
sent: true,
|
||||
unidentifiedDeliveries: data.unidentifiedDeliveries || [],
|
||||
|
@ -2468,7 +2492,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
const message = createSentMessage(data);
|
||||
const message = createSentMessage(data, messageDescriptor);
|
||||
|
||||
if (data.message.reaction) {
|
||||
const { reaction } = data.message;
|
||||
|
@ -2502,12 +2526,7 @@
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
ConversationController.getOrCreate(
|
||||
messageDescriptor.id,
|
||||
messageDescriptor.type
|
||||
);
|
||||
// Don't wait for handleDataMessage, as it has its own per-conversation queueing
|
||||
|
||||
message.handleDataMessage(data.message, event.confirm, {
|
||||
data,
|
||||
});
|
||||
|
@ -2520,12 +2539,10 @@
|
|||
const fromContactId = ConversationController.ensureContactIds({
|
||||
e164: data.source,
|
||||
uuid: data.sourceUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
|
||||
// Determine if this message is in a group
|
||||
const isGroup = descriptor.type === Message.GROUP;
|
||||
|
||||
// Determine the conversationId this message belongs to
|
||||
const conversationId = isGroup
|
||||
? ConversationController.ensureGroup(descriptor.id, {
|
||||
addedBy: fromContactId,
|
||||
|
@ -2651,10 +2668,7 @@
|
|||
});
|
||||
|
||||
const conversationId = message.get('conversationId');
|
||||
const conversation = ConversationController.getOrCreate(
|
||||
conversationId,
|
||||
'private'
|
||||
);
|
||||
const conversation = ConversationController.get(conversationId);
|
||||
|
||||
// This matches the queueing behavior used in Message.handleDataMessage
|
||||
conversation.queueJob(async () => {
|
||||
|
@ -2791,9 +2805,13 @@
|
|||
|
||||
function onReadReceipt(ev) {
|
||||
const readAt = ev.timestamp;
|
||||
const { timestamp } = ev.read;
|
||||
const reader = ConversationController.getConversationId(ev.read.reader);
|
||||
window.log.info('read receipt', reader, timestamp);
|
||||
const { timestamp, source, sourceUuid } = ev.read;
|
||||
const reader = ConversationController.ensureContactIds({
|
||||
e164: source,
|
||||
uuid: sourceUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
window.log.info('read receipt', timestamp, source, sourceUuid, reader);
|
||||
|
||||
ev.confirm();
|
||||
|
||||
|
@ -2879,7 +2897,11 @@
|
|||
ev.viaContactSync ? 'via contact sync' : ''
|
||||
);
|
||||
|
||||
const verifiedId = ConversationController.ensureContactIds({ e164, uuid });
|
||||
const verifiedId = ConversationController.ensureContactIds({
|
||||
e164,
|
||||
uuid,
|
||||
highTrust: true,
|
||||
});
|
||||
const contact = await ConversationController.get(verifiedId, 'private');
|
||||
const options = {
|
||||
viaSyncMessage: true,
|
||||
|
@ -2899,20 +2921,23 @@
|
|||
function onDeliveryReceipt(ev) {
|
||||
const { deliveryReceipt } = ev;
|
||||
const { sourceUuid, source } = deliveryReceipt;
|
||||
const identifier = source || sourceUuid;
|
||||
|
||||
window.log.info(
|
||||
'delivery receipt from',
|
||||
`${identifier}.${deliveryReceipt.sourceDevice}`,
|
||||
`${source} ${sourceUuid} ${deliveryReceipt.sourceDevice}`,
|
||||
deliveryReceipt.timestamp
|
||||
);
|
||||
|
||||
ev.confirm();
|
||||
|
||||
const deliveredTo = ConversationController.getConversationId(identifier);
|
||||
const deliveredTo = ConversationController.ensureContactIds({
|
||||
e164: source,
|
||||
uuid: sourceUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
|
||||
if (!deliveredTo) {
|
||||
window.log.info('no conversation for identifier', identifier);
|
||||
window.log.info('no conversation for', source, sourceUuid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,353 +0,0 @@
|
|||
/* global _, Whisper, Backbone, storage, textsecure */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
||||
|
||||
const conversations = new Whisper.ConversationCollection();
|
||||
const inboxCollection = new (Backbone.Collection.extend({
|
||||
initialize() {
|
||||
this.listenTo(conversations, 'add change:active_at', this.addActive);
|
||||
this.listenTo(conversations, 'reset', () => this.reset([]));
|
||||
|
||||
this.on(
|
||||
'add remove change:unreadCount',
|
||||
_.debounce(this.updateUnreadCount.bind(this), 1000)
|
||||
);
|
||||
},
|
||||
addActive(model) {
|
||||
if (model.get('active_at')) {
|
||||
this.add(model);
|
||||
} else {
|
||||
this.remove(model);
|
||||
}
|
||||
},
|
||||
updateUnreadCount() {
|
||||
const newUnreadCount = _.reduce(
|
||||
this.map(m => m.get('unreadCount')),
|
||||
(item, memo) => item + memo,
|
||||
0
|
||||
);
|
||||
storage.put('unreadCount', newUnreadCount);
|
||||
|
||||
if (newUnreadCount > 0) {
|
||||
window.setBadgeCount(newUnreadCount);
|
||||
window.document.title = `${window.getTitle()} (${newUnreadCount})`;
|
||||
} else {
|
||||
window.setBadgeCount(0);
|
||||
window.document.title = window.getTitle();
|
||||
}
|
||||
window.updateTrayIcon(newUnreadCount);
|
||||
},
|
||||
}))();
|
||||
|
||||
window.getInboxCollection = () => inboxCollection;
|
||||
window.getConversations = () => conversations;
|
||||
|
||||
window.ConversationController = {
|
||||
get(id) {
|
||||
if (!this._initialFetchComplete) {
|
||||
throw new Error(
|
||||
'ConversationController.get() needs complete initial fetch'
|
||||
);
|
||||
}
|
||||
|
||||
return conversations.get(id);
|
||||
},
|
||||
// Needed for some model setup which happens during the initial fetch() call below
|
||||
getUnsafe(id) {
|
||||
return conversations.get(id);
|
||||
},
|
||||
dangerouslyCreateAndAdd(attributes) {
|
||||
return conversations.add(attributes);
|
||||
},
|
||||
getOrCreate(identifier, type, additionalInitialProps = {}) {
|
||||
if (typeof identifier !== 'string') {
|
||||
throw new TypeError("'id' must be a string");
|
||||
}
|
||||
|
||||
if (type !== 'private' && type !== 'group') {
|
||||
throw new TypeError(
|
||||
`'type' must be 'private' or 'group'; got: '${type}'`
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._initialFetchComplete) {
|
||||
throw new Error(
|
||||
'ConversationController.get() needs complete initial fetch'
|
||||
);
|
||||
}
|
||||
|
||||
let conversation = conversations.get(identifier);
|
||||
if (conversation) {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
const id = window.getGuid();
|
||||
|
||||
if (type === 'group') {
|
||||
conversation = conversations.add({
|
||||
id,
|
||||
uuid: null,
|
||||
e164: null,
|
||||
groupId: identifier,
|
||||
type,
|
||||
version: 2,
|
||||
...additionalInitialProps,
|
||||
});
|
||||
} else if (window.isValidGuid(identifier)) {
|
||||
conversation = conversations.add({
|
||||
id,
|
||||
uuid: identifier,
|
||||
e164: null,
|
||||
groupId: null,
|
||||
type,
|
||||
version: 2,
|
||||
...additionalInitialProps,
|
||||
});
|
||||
} else {
|
||||
conversation = conversations.add({
|
||||
id,
|
||||
uuid: null,
|
||||
e164: identifier,
|
||||
groupId: null,
|
||||
type,
|
||||
version: 2,
|
||||
...additionalInitialProps,
|
||||
});
|
||||
}
|
||||
|
||||
const create = async () => {
|
||||
if (!conversation.isValid()) {
|
||||
const validationError = conversation.validationError || {};
|
||||
window.log.error(
|
||||
'Contact is not valid. Not saving, but adding to collection:',
|
||||
conversation.idForLogging(),
|
||||
validationError.stack
|
||||
);
|
||||
|
||||
return conversation;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.Signal.Data.saveConversation(conversation.attributes, {
|
||||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'Conversation save failed! ',
|
||||
identifier,
|
||||
type,
|
||||
'Error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return conversation;
|
||||
};
|
||||
|
||||
conversation.initialPromise = create();
|
||||
|
||||
return conversation;
|
||||
},
|
||||
getOrCreateAndWait(id, type, additionalInitialProps = {}) {
|
||||
return this._initialPromise.then(() => {
|
||||
const conversation = this.getOrCreate(id, type, additionalInitialProps);
|
||||
|
||||
if (conversation) {
|
||||
return conversation.initialPromise.then(() => conversation);
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error('getOrCreateAndWait: did not get conversation')
|
||||
);
|
||||
});
|
||||
},
|
||||
getConversationId(address) {
|
||||
if (!address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [id] = textsecure.utils.unencodeNumber(address);
|
||||
const conv = this.get(id);
|
||||
|
||||
if (conv) {
|
||||
return conv.get('id');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getOurConversationId() {
|
||||
const e164 = textsecure.storage.user.getNumber();
|
||||
const uuid = textsecure.storage.user.getUuid();
|
||||
return this.ensureContactIds({ e164, uuid });
|
||||
},
|
||||
/**
|
||||
* Given a UUID and/or an E164, resolves to a string representing the local
|
||||
* database of the given contact. If a conversation is found it is updated
|
||||
* to have the given UUID and E164. If a conversation is not found, this
|
||||
* function creates a conversation with the given UUID and E164. If the
|
||||
* conversation * is found in the local database it is updated.
|
||||
*
|
||||
* This function also additionally checks for mismatched e164/uuid pairs out
|
||||
* of abundance of caution.
|
||||
*/
|
||||
ensureContactIds({ e164, uuid }) {
|
||||
// Check for at least one parameter being provided. This is necessary
|
||||
// because this path can be called on startup to resolve our own ID before
|
||||
// our phone number or UUID are known. The existing behavior in these
|
||||
// cases can handle a returned `undefined` id, so we do that.
|
||||
if (!e164 && !uuid) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lowerUuid = uuid ? uuid.toLowerCase() : undefined;
|
||||
|
||||
const convoE164 = this.get(e164);
|
||||
const convoUuid = this.get(lowerUuid);
|
||||
|
||||
// Check for mismatched UUID and E164
|
||||
if (
|
||||
convoE164 &&
|
||||
convoUuid &&
|
||||
convoE164.get('id') !== convoUuid.get('id')
|
||||
) {
|
||||
window.log.warn('Received a message with a mismatched UUID and E164.');
|
||||
}
|
||||
|
||||
const convo = convoUuid || convoE164;
|
||||
|
||||
const idOrIdentifier = convo ? convo.get('id') : e164 || lowerUuid;
|
||||
|
||||
const finalConversation = this.getOrCreate(idOrIdentifier, 'private');
|
||||
finalConversation.updateE164(e164);
|
||||
finalConversation.updateUuid(lowerUuid);
|
||||
|
||||
return finalConversation.get('id');
|
||||
},
|
||||
/**
|
||||
* Given a groupId and optional additional initialization properties,
|
||||
* ensures the existence of a group conversation and returns a string
|
||||
* representing the local database ID of the group conversation.
|
||||
*/
|
||||
ensureGroup(groupId, additionalInitProps = {}) {
|
||||
return this.getOrCreate(groupId, 'group', additionalInitProps).get('id');
|
||||
},
|
||||
/**
|
||||
* Given certain metadata about a message (an identifier of who wrote the
|
||||
* message and the sent_at timestamp of the message) returns the
|
||||
* conversation the message belongs to OR null if a conversation isn't
|
||||
* found.
|
||||
* @param {string} targetFrom The E164, UUID, or Conversation ID of the message author
|
||||
* @param {number} targetTimestamp The sent_at timestamp of the target message
|
||||
*/
|
||||
async getConversationForTargetMessage(targetFrom, targetTimestamp) {
|
||||
const targetFromId = this.getConversationId(targetFrom);
|
||||
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
targetTimestamp,
|
||||
{
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
}
|
||||
);
|
||||
const targetMessage = messages.find(m => {
|
||||
const contact = m.getContact();
|
||||
|
||||
if (!contact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mcid = contact.get('id');
|
||||
return mcid === targetFromId;
|
||||
});
|
||||
|
||||
if (targetMessage) {
|
||||
return targetMessage.getConversation();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
prepareForSend(id, options) {
|
||||
// id is any valid conversation identifier
|
||||
const conversation = this.get(id);
|
||||
const sendOptions = conversation
|
||||
? conversation.getSendOptions(options)
|
||||
: null;
|
||||
const wrap = conversation
|
||||
? conversation.wrapSend.bind(conversation)
|
||||
: promise => promise;
|
||||
|
||||
return { wrap, sendOptions };
|
||||
},
|
||||
async getAllGroupsInvolvingId(conversationId) {
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingId(
|
||||
conversationId,
|
||||
{
|
||||
ConversationCollection: Whisper.ConversationCollection,
|
||||
}
|
||||
);
|
||||
return groups.map(group => conversations.add(group));
|
||||
},
|
||||
loadPromise() {
|
||||
return this._initialPromise;
|
||||
},
|
||||
reset() {
|
||||
this._initialPromise = Promise.resolve();
|
||||
this._initialFetchComplete = false;
|
||||
conversations.reset([]);
|
||||
},
|
||||
async load() {
|
||||
window.log.info('ConversationController: starting initial fetch');
|
||||
|
||||
if (conversations.length) {
|
||||
throw new Error('ConversationController: Already loaded!');
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const collection = await window.Signal.Data.getAllConversations({
|
||||
ConversationCollection: Whisper.ConversationCollection,
|
||||
});
|
||||
|
||||
conversations.add(collection.models);
|
||||
|
||||
this._initialFetchComplete = true;
|
||||
await Promise.all(
|
||||
conversations.map(async conversation => {
|
||||
if (!conversation.get('lastMessage')) {
|
||||
await conversation.updateLastMessage();
|
||||
}
|
||||
|
||||
// In case a too-large draft was saved to the database
|
||||
const draft = conversation.get('draft');
|
||||
if (draft && draft.length > MAX_MESSAGE_BODY_LENGTH) {
|
||||
this.model.set({
|
||||
draft: draft.slice(0, MAX_MESSAGE_BODY_LENGTH),
|
||||
});
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
}
|
||||
})
|
||||
);
|
||||
window.log.info('ConversationController: done with initial fetch');
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'ConversationController: initial fetch failed',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
this._initialPromise = load();
|
||||
|
||||
return this._initialPromise;
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -30,11 +30,10 @@
|
|||
this.remove(receipts);
|
||||
return receipts;
|
||||
},
|
||||
async getTargetMessage(source, messages) {
|
||||
async getTargetMessage(sourceId, messages) {
|
||||
if (messages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const sourceId = ConversationController.getConversationId(source);
|
||||
const message = messages.find(
|
||||
item => !item.isIncoming() && sourceId === item.get('conversationId')
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
forConversation(conversation) {
|
||||
if (conversation.get('e164')) {
|
||||
const syncByE164 = this.findWhere({
|
||||
e164: conversation.get('e164'),
|
||||
threadE164: conversation.get('e164'),
|
||||
});
|
||||
if (syncByE164) {
|
||||
window.log.info(
|
||||
|
@ -30,7 +30,7 @@
|
|||
|
||||
if (conversation.get('uuid')) {
|
||||
const syncByUuid = this.findWhere({
|
||||
uuid: conversation.get('uuid'),
|
||||
threadUuid: conversation.get('uuid'),
|
||||
});
|
||||
if (syncByUuid) {
|
||||
window.log.info(
|
||||
|
@ -45,7 +45,7 @@
|
|||
|
||||
if (conversation.get('groupId')) {
|
||||
const syncByGroupId = this.findWhere({
|
||||
uuid: conversation.get('groupId'),
|
||||
groupId: conversation.get('groupId'),
|
||||
});
|
||||
if (syncByGroupId) {
|
||||
window.log.info(
|
||||
|
@ -65,12 +65,19 @@
|
|||
const threadE164 = sync.get('threadE164');
|
||||
const threadUuid = sync.get('threadUuid');
|
||||
const groupId = sync.get('groupId');
|
||||
const identifier = threadE164 || threadUuid || groupId;
|
||||
const conversation = ConversationController.get(identifier);
|
||||
|
||||
const conversation = groupId
|
||||
? ConversationController.get(groupId)
|
||||
: ConversationController.get(
|
||||
ConversationController.ensureContactIds({
|
||||
e164: threadE164,
|
||||
uuid: threadUuid,
|
||||
})
|
||||
);
|
||||
|
||||
if (!conversation) {
|
||||
window.log(
|
||||
`Received message request response for unknown conversation: ${identifier}`
|
||||
`Received message request response for unknown conversation: ${groupId} ${threadUuid} ${threadE164}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,15 +15,8 @@ let scheduleNext = null;
|
|||
// do not support unidentified delivery.
|
||||
function refreshOurProfile() {
|
||||
window.log.info('refreshOurProfile');
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const ourId = ConversationController.ensureContactIds({
|
||||
e164: ourNumber,
|
||||
uuid: ourUuid,
|
||||
});
|
||||
const conversation = ConversationController.get(ourId, 'private');
|
||||
conversation.updateUuid(ourUuid);
|
||||
conversation.updateE164(ourNumber);
|
||||
const ourId = ConversationController.getOurConversationId();
|
||||
const conversation = ConversationController.get(ourId);
|
||||
conversation.getProfiles();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
const { bindActionCreators } = require('redux');
|
||||
const Backbone = require('../../ts/backbone');
|
||||
const Crypto = require('../../ts/Crypto');
|
||||
const {
|
||||
start: conversationControllerStart,
|
||||
} = require('../../ts/ConversationController');
|
||||
const Data = require('../../ts/sql/Client').default;
|
||||
const Emojis = require('./emojis');
|
||||
const EmojiLib = require('../../ts/components/emoji/lib');
|
||||
|
@ -357,6 +360,7 @@ exports.setup = (options = {}) => {
|
|||
Backbone,
|
||||
Components,
|
||||
Crypto,
|
||||
conversationControllerStart,
|
||||
Data,
|
||||
Emojis,
|
||||
EmojiLib,
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
|
||||
const reactionsBySource = this.filter(re => {
|
||||
const mcid = message.get('conversationId');
|
||||
const recid = ConversationController.getConversationId(
|
||||
re.get('targetAuthorE164') || re.get('targetAuthorUuid')
|
||||
);
|
||||
const recid = ConversationController.ensureContactIds({
|
||||
e164: re.get('targetAuthorE164'),
|
||||
uuid: re.get('targetAuthorUuid'),
|
||||
});
|
||||
const mTime = message.get('sent_at');
|
||||
const rTime = re.get('targetTimestamp');
|
||||
return mcid === recid && mTime === rTime;
|
||||
|
@ -47,9 +48,10 @@
|
|||
async onReaction(reaction) {
|
||||
try {
|
||||
const targetConversation = await ConversationController.getConversationForTargetMessage(
|
||||
// Do not use ensureContactIds here since maliciously malformed
|
||||
// reactions from clients could cause issues
|
||||
reaction.get('targetAuthorE164') || reaction.get('targetAuthorUuid'),
|
||||
ConversationController.ensureContactIds({
|
||||
e164: reaction.get('targetAuthorE164'),
|
||||
uuid: reaction.get('targetAuthorUuid'),
|
||||
}),
|
||||
reaction.get('targetTimestamp')
|
||||
);
|
||||
if (!targetConversation) {
|
||||
|
@ -85,10 +87,10 @@
|
|||
}
|
||||
|
||||
const mcid = contact.get('id');
|
||||
const recid = ConversationController.getConversationId(
|
||||
reaction.get('targetAuthorE164') ||
|
||||
reaction.get('targetAuthorUuid')
|
||||
);
|
||||
const recid = ConversationController.ensureContactIds({
|
||||
e164: reaction.get('targetAuthorE164'),
|
||||
uuid: reaction.get('targetAuthorUuid'),
|
||||
});
|
||||
return mcid === recid;
|
||||
});
|
||||
|
||||
|
|
|
@ -2058,7 +2058,7 @@
|
|||
await contact.setApproved();
|
||||
}
|
||||
|
||||
message.resend(contact.get('uuid') || contact.get('e164'));
|
||||
message.resend(contact.getSendTarget());
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -84,6 +84,13 @@
|
|||
model: { window: options.window },
|
||||
});
|
||||
|
||||
Whisper.events.on('refreshConversation', ({ oldId, newId }) => {
|
||||
const convo = this.conversation_stack.lastConversation;
|
||||
if (convo && convo.get('id') === oldId) {
|
||||
this.conversation_stack.open(newId);
|
||||
}
|
||||
});
|
||||
|
||||
if (!options.initialLoadComplete) {
|
||||
this.appLoadingScreen = new Whisper.AppLoadingScreen();
|
||||
this.appLoadingScreen.render();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue