Merge pull request #63 from BeaudanBrown/friend-state

Refactor conversation UI logic to rely on friendRequestStatus
pull/70/head
sachaaaaa 6 years ago committed by GitHub
commit 59b2c75c18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -396,7 +396,7 @@ async function updateToSchemaVersion6(currentVersion, instance) {
await instance.run( await instance.run(
`ALTER TABLE conversations `ALTER TABLE conversations
ADD COLUMN friendStatus INTEGER;` ADD COLUMN friendRequestStatus INTEGER;`
); );
await instance.run( await instance.run(
@ -964,7 +964,7 @@ async function getConversationCount() {
async function saveConversation(data) { async function saveConversation(data) {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
const { id, active_at, type, members, name, friendStatus, profileName } = data; const { id, active_at, type, members, name, friendRequestStatus, profileName } = data;
await db.run( await db.run(
`INSERT INTO conversations ( `INSERT INTO conversations (
@ -975,7 +975,7 @@ async function saveConversation(data) {
type, type,
members, members,
name, name,
friendStatus, friendRequestStatus,
profileName profileName
) values ( ) values (
$id, $id,
@ -985,7 +985,7 @@ async function saveConversation(data) {
$type, $type,
$members, $members,
$name, $name,
$friendStatus, $friendRequestStatus,
$profileName $profileName
);`, );`,
{ {
@ -996,7 +996,7 @@ async function saveConversation(data) {
$type: type, $type: type,
$members: members ? members.join(' ') : null, $members: members ? members.join(' ') : null,
$name: name, $name: name,
$friendStatus: friendStatus, $friendRequestStatus: friendRequestStatus,
$profileName: profileName, $profileName: profileName,
} }
); );
@ -1020,7 +1020,7 @@ async function saveConversations(arrayOfConversations) {
async function updateConversation(data) { async function updateConversation(data) {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
const { id, active_at, type, members, name, friendStatus, profileName } = data; const { id, active_at, type, members, name, friendRequestStatus, profileName } = data;
await db.run( await db.run(
`UPDATE conversations SET `UPDATE conversations SET
@ -1030,7 +1030,7 @@ async function updateConversation(data) {
type = $type, type = $type,
members = $members, members = $members,
name = $name, name = $name,
friendStatus = $friendStatus, friendRequestStatus = $friendRequestStatus,
profileName = $profileName profileName = $profileName
WHERE id = $id;`, WHERE id = $id;`,
{ {
@ -1041,7 +1041,7 @@ async function updateConversation(data) {
$type: type, $type: type,
$members: members ? members.join(' ') : null, $members: members ? members.join(' ') : null,
$name: name, $name: name,
$friendStatus: friendStatus, $friendRequestStatus: friendRequestStatus,
$profileName: profileName, $profileName: profileName,
} }
); );

@ -1293,7 +1293,7 @@
unread: 1, unread: 1,
}; };
if (data.type === 'friend-request') { if (data.friendRequest) {
messageData = { messageData = {
...messageData, ...messageData,
type: 'friend-request', type: 'friend-request',

@ -257,6 +257,8 @@
conversation.updateLastMessage(), conversation.updateLastMessage(),
conversation.updateProfile(), conversation.updateProfile(),
conversation.updateProfileAvatar(), conversation.updateProfileAvatar(),
conversation.resetPendingSend(),
conversation.updateProfile(),
]); ]);
}); });
await Promise.all(promises); await Promise.all(promises);

@ -40,13 +40,17 @@
} = window.Signal.Migrations; } = window.Signal.Migrations;
// Possible conversation friend states // Possible conversation friend states
const FriendStatusEnum = Object.freeze({ const FriendRequestStatusEnum = Object.freeze({
// New conversation, no messages sent or received // New conversation, no messages sent or received
none: 0, none: 0,
// Friend request not complete yet, input blocked // This state is used to lock the input early while sending
pending: 1, pendingSend: 1,
// Friend request sent, awaiting response
requestSent: 2,
// Friend request received, awaiting user input
requestReceived: 3,
// We did it! // We did it!
friends: 2, friends: 4,
}); });
const COLORS = [ const COLORS = [
@ -69,7 +73,7 @@
return { return {
unreadCount: 0, unreadCount: 0,
verified: textsecure.storage.protocol.VerifiedStatus.DEFAULT, verified: textsecure.storage.protocol.VerifiedStatus.DEFAULT,
friendStatus: FriendStatusEnum.none, friendRequestStatus: FriendRequestStatusEnum.none,
unlockTimestamp: null, // Timestamp used for expiring friend requests. unlockTimestamp: null, // Timestamp used for expiring friend requests.
}; };
}, },
@ -105,8 +109,6 @@
conversation: this, conversation: this,
}); });
this.pendingFriendRequest = false;
this.messageCollection.on('change:errors', this.handleMessageError, this); this.messageCollection.on('change:errors', this.handleMessageError, this);
this.messageCollection.on('send-error', this.onMessageError, this); this.messageCollection.on('send-error', this.onMessageError, this);
@ -147,7 +149,6 @@
this.unset('lastMessage'); this.unset('lastMessage');
this.unset('lastMessageStatus'); this.unset('lastMessageStatus');
this.updateTextInputState();
this.setFriendRequestExpiryTimeout(); this.setFriendRequestExpiryTimeout();
}, },
@ -253,35 +254,8 @@
title: this.getTitle(), title: this.getTitle(),
}; };
}, },
// This function sets `pendingFriendRequest` variable in memory
async updatePendingFriendRequests() {
const pendingFriendRequest = await this.hasPendingFriendRequests();
// Only update if we have different values
if (this.pendingFriendRequest !== pendingFriendRequest) {
this.pendingFriendRequest = pendingFriendRequest;
// trigger an update
this.trigger('change');
}
},
// This goes through all our message history and finds a friend request // This goes through all our message history and finds a friend request
// But this is not a concurrent operation and thus updatePendingFriendRequests is used async getPendingFriendRequests(direction = null) {
async hasPendingFriendRequests() {
// Go through the messages and check for any pending friend requests
const messages = await window.Signal.Data.getMessagesByConversation(
this.id,
{
type: 'friend-request',
MessageCollection: Whisper.MessageCollection,
}
);
const pendingFriendRequest =
messages.models.find(message =>
message.isFriendRequest() &&
message.attributes.friendStatus === 'pending'
);
return pendingFriendRequest !== undefined;
},
async getPendingFriendRequests(direction) {
// Theoretically all our messages could be friend requests, // Theoretically all our messages could be friend requests,
// thus we have to unfortunately go through each one :( // thus we have to unfortunately go through each one :(
const messages = await window.Signal.Data.getMessagesByConversation( const messages = await window.Signal.Data.getMessagesByConversation(
@ -291,12 +265,13 @@
MessageCollection: Whisper.MessageCollection, MessageCollection: Whisper.MessageCollection,
} }
); );
// Get the pending friend requests that match the direction
// Get the messages that are matching the direction and the friendStatus // If no direction is supplied then return all pending friend requests
return messages.models.filter(m => return messages.models.filter(m => {
m.attributes.direction === direction && if (m.get('friendStatus') !== 'pending')
m.attributes.friendStatus === 'pending' return false;
); return direction === null || m.get('direction') === direction;
});
}, },
getPropsForListItem() { getPropsForListItem() {
const result = { const result = {
@ -306,7 +281,7 @@
lastUpdated: this.get('timestamp'), lastUpdated: this.get('timestamp'),
unreadCount: this.get('unreadCount') || 0, unreadCount: this.get('unreadCount') || 0,
isSelected: this.isSelected, isSelected: this.isSelected,
showFriendRequestIndicator: this.pendingFriendRequest, showFriendRequestIndicator: this.isPendingFriendRequest(),
isBlocked: this.isBlocked(), isBlocked: this.isBlocked(),
lastMessage: { lastMessage: {
status: this.lastMessageStatus, status: this.lastMessageStatus,
@ -459,81 +434,105 @@
return contact.isVerified(); return contact.isVerified();
}); });
}, },
async waitingForFriendRequestApproval() { isFriendRequestStatusNone() {
// Check if we have an incoming friend request return this.get('friendRequestStatus') === FriendRequestStatusEnum.none;
// Or any successful outgoing ones },
const incoming = await this.getPendingFriendRequests('incoming'); isPendingFriendRequest() {
const outgoing = await this.getPendingFriendRequests('outgoing'); const status = this.get('friendRequestStatus');
const successfulOutgoing = outgoing.filter(o => !o.hasErrors()); return status === FriendRequestStatusEnum.requestSent ||
status === FriendRequestStatusEnum.requestReceived ||
return (incoming.length > 0 || successfulOutgoing.length > 0); status === FriendRequestStatusEnum.pendingSend;
},
hasSentFriendRequest() {
return this.get('friendRequestStatus') === FriendRequestStatusEnum.requestSent;
},
hasReceivedFriendRequest() {
return this.get('friendRequestStatus') === FriendRequestStatusEnum.requestReceived;
}, },
isFriend() { isFriend() {
return this.get('friendStatus') === FriendStatusEnum.friends; return this.get('friendRequestStatus') === FriendRequestStatusEnum.friends;
},
updateTextInputState() {
switch (this.get('friendRequestStatus')) {
case FriendRequestStatusEnum.none:
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'friend-request');
return;
case FriendRequestStatusEnum.pendingSend:
case FriendRequestStatusEnum.requestReceived:
case FriendRequestStatusEnum.requestSent:
this.trigger('disable:input', true);
this.trigger('change:placeholder', 'disabled');
return;
case FriendRequestStatusEnum.friends:
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'chat');
return;
default:
throw new Error('Invalid friend request state');
}
}, },
// Update any pending friend requests for the current user async setFriendRequestStatus(newStatus) {
async updateFriendRequestUI() { // Ensure that the new status is a valid FriendStatusEnum value
// Enable the text inputs early if (!(newStatus in Object.values(FriendRequestStatusEnum)))
return;
if (this.get('friendRequestStatus') !== newStatus) {
this.set({ friendRequestStatus: newStatus });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
this.updateTextInputState(); this.updateTextInputState();
}
// We only update our friend requests if we have the user as a friend },
if (!this.isFriend()) return; async respondToAllPendingFriendRequests(options) {
const { response, direction = null } = options;
// Update any pending outgoing messages // Ignore if no response supplied
const pending = await this.getPendingFriendRequests('outgoing'); if (!response) return;
const pending = await this.getPendingFriendRequests(direction);
await Promise.all( await Promise.all(
pending.map(async request => { pending.map(async request => {
if (request.hasErrors()) return; if (request.hasErrors()) return;
request.set({ friendStatus: 'accepted' }); request.set({ friendStatus: response });
await window.Signal.Data.saveMessage(request.attributes, { await window.Signal.Data.saveMessage(request.attributes, {
Message: Whisper.Message, Message: Whisper.Message,
}); });
this.trigger('updateMessage', request); this.trigger('updateMessage', request);
}) })
); );
},
// Update our local state async resetPendingSend() {
await this.updatePendingFriendRequests(); if (this.get('friendRequestStatus') === FriendRequestStatusEnum.pendingSend) {
await this.setFriendRequestStatus(FriendRequestStatusEnum.none);
// Send the notification if we had an outgoing friend request }
if (pending.length > 0)
this.notifyFriendRequest(this.id, 'accepted')
}, },
// We have declined an incoming friend request // We have declined an incoming friend request
async onDeclineFriendRequest() { async onDeclineFriendRequest() {
// Should we change states for other states? (They should never happen) this.setFriendRequestStatus(FriendRequestStatusEnum.none);
if (this.get('friendStatus') === FriendStatusEnum.pending) { await this.respondToAllPendingFriendRequests({
this.set({ friendStatus: FriendStatusEnum.none }); response: 'declined',
await window.Signal.Data.updateConversation(this.id, this.attributes, { direction: 'incoming',
Conversation: Whisper.Conversation,
}); });
await this.updateFriendRequestUI();
await this.updatePendingFriendRequests();
await window.libloki.removePreKeyBundleForNumber(this.id); await window.libloki.removePreKeyBundleForNumber(this.id);
}
}, },
// We have accepted an incoming friend request // We have accepted an incoming friend request
async onAcceptFriendRequest() { async onAcceptFriendRequest() {
// Should we change states for other states? (They should never happen) if (this.hasReceivedFriendRequest()) {
if (this.get('friendStatus') === FriendStatusEnum.pending) { this.setFriendRequestStatus(FriendRequestStatusEnum.friends);
this.set({ friendStatus: FriendStatusEnum.friends }); await this.respondToAllPendingFriendRequests({
await window.Signal.Data.updateConversation(this.id, this.attributes, { response: 'accepted',
Conversation: Whisper.Conversation, direction: 'incoming',
}); });
await this.updateFriendRequestUI();
await this.updatePendingFriendRequests();
window.libloki.sendFriendRequestAccepted(this.id); window.libloki.sendFriendRequestAccepted(this.id);
} }
}, },
// Our outgoing friend request has been accepted // Our outgoing friend request has been accepted
async onFriendRequestAccepted() { async onFriendRequestAccepted() {
if (this.get('friendStatus') === FriendStatusEnum.pending) { if (this.hasSentFriendRequest()) {
this.set({ friendStatus: FriendStatusEnum.friends }); this.setFriendRequestStatus(FriendRequestStatusEnum.friends);
await window.Signal.Data.updateConversation(this.id, this.attributes, { await this.respondToAllPendingFriendRequests({
Conversation: Whisper.Conversation, response: 'accepted',
}); });
await this.updateFriendRequestUI();
} }
}, },
async onFriendRequestTimeout() { async onFriendRequestTimeout() {
@ -552,31 +551,31 @@
} }
// Change any pending outgoing friend requests to expired // Change any pending outgoing friend requests to expired
const outgoing = await this.getPendingFriendRequests('outgoing'); await this.respondToAllPendingFriendRequests({
await Promise.all( response: 'expired',
outgoing.map(async request => { direction: 'outgoing',
if (request.hasErrors()) return;
request.set({ friendStatus: 'expired' });
await window.Signal.Data.saveMessage(request.attributes, {
Message: Whisper.Message,
}); });
this.trigger('updateMessage', request); await this.setFriendRequestStatus(FriendRequestStatusEnum.none);
})
);
// Update the UI
await this.updatePendingFriendRequests();
await this.updateFriendRequestUI();
}, },
async onFriendRequestReceived() { async onFriendRequestReceived() {
if (this.get('friendStatus') === FriendStatusEnum.none) { if (this.isFriendRequestStatusNone()) {
this.set({ friendStatus: FriendStatusEnum.pending }); this.setFriendRequestStatus(FriendRequestStatusEnum.requestReceived);
await window.Signal.Data.updateConversation(this.id, this.attributes, { } else if (this.hasSentFriendRequest()) {
Conversation: Whisper.Conversation, await Promise.all([
}); this.setFriendRequestStatus(FriendRequestStatusEnum.friends),
await this.updateFriendRequestUI(); // Accept all outgoing FR
this.respondToAllPendingFriendRequests({
direction: 'outgoing',
response: 'accepted',
}),
]);
} }
// Delete stale incoming friend requests
const incoming = await this.getPendingFriendRequests('incoming');
await Promise.all(
incoming.map(request => this._removeMessage(request.id))
);
this.trigger('change');
}, },
async onFriendRequestSent() { async onFriendRequestSent() {
// Check if we need to set the friend request expiry // Check if we need to set the friend request expiry
@ -587,20 +586,9 @@
const ms = 60 * 60 * 1000 * hourLockDuration; const ms = 60 * 60 * 1000 * hourLockDuration;
this.set({ unlockTimestamp: Date.now() + ms }); this.set({ unlockTimestamp: Date.now() + ms });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
this.setFriendRequestExpiryTimeout(); this.setFriendRequestExpiryTimeout();
} }
await this.setFriendRequestStatus(FriendRequestStatusEnum.requestSent);
if (this.get('friendStatus') === FriendStatusEnum.none) {
this.set({ friendStatus: FriendStatusEnum.pending });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: Whisper.Conversation,
});
}
this.updateFriendRequestUI();
}, },
setFriendRequestExpiryTimeout() { setFriendRequestExpiryTimeout() {
const unlockTimestamp = this.get('unlockTimestamp'); const unlockTimestamp = this.get('unlockTimestamp');
@ -972,6 +960,9 @@
}, },
async sendMessage(body, attachments, quote) { async sendMessage(body, attachments, quote) {
// Input should be blocked if there is a pending friend request
if (this.isPendingFriendRequest())
return;
const destination = this.id; const destination = this.id;
const expireTimer = this.get('expireTimer'); const expireTimer = this.get('expireTimer');
const recipients = this.getRecipients(); const recipients = this.getRecipients();
@ -1007,12 +998,7 @@
recipients, recipients,
}); });
} else { } else {
// We also need to make sure we don't send a new friend request // Check if we have sent a friend request
// if we already have an existing one
const incomingRequests = await this.getPendingFriendRequests('incoming');
if (incomingRequests.length > 0) return null;
// Otherwise check if we have sent a friend request
const outgoingRequests = await this.getPendingFriendRequests('outgoing'); const outgoingRequests = await this.getPendingFriendRequests('outgoing');
if (outgoingRequests.length > 0) { if (outgoingRequests.length > 0) {
// Check if the requests have errored, if so then remove them // Check if the requests have errored, if so then remove them
@ -1033,6 +1019,7 @@
// because one of them was sent successfully // because one of them was sent successfully
if (friendRequestSent) return null; if (friendRequestSent) return null;
} }
await this.setFriendRequestStatus(FriendRequestStatusEnum.pendingSend);
// Send the friend request! // Send the friend request!
messageWithSchema = await upgradeMessageSchema({ messageWithSchema = await upgradeMessageSchema({
@ -1123,50 +1110,39 @@
return true; return true;
}); });
}, },
async updateTextInputState() {
// Check if we need to disable the text field
if (!this.isFriend()) {
// Disable the input if we're waiting for friend request approval
const waiting = await this.waitingForFriendRequestApproval();
if (waiting) {
this.trigger('disable:input', true);
this.trigger('change:placeholder', 'disabled');
return;
}
// Tell the user to introduce themselves
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'friend-request');
return;
}
this.trigger('disable:input', false);
this.trigger('change:placeholder', 'chat');
},
wrapSend(promise) { wrapSend(promise) {
return promise.then( return promise.then(
async result => { async result => {
// success // success
if (result) { if (result) {
await this.handleMessageSendResult( await this.handleMessageSendResult({
result.failoverNumbers, ...result,
result.unidentifiedDeliveries success: true,
); });
} }
return result; return result;
}, },
async result => { async result => {
// failure // failure
if (result) { if (result) {
await this.handleMessageSendResult( await this.handleMessageSendResult({
result.failoverNumbers, ...result,
result.unidentifiedDeliveries success: false,
); });
} }
throw result; throw result;
} }
); );
}, },
async handleMessageSendResult(failoverNumbers, unidentifiedDeliveries) { async handleMessageSendResult({
failoverNumbers,
unidentifiedDeliveries,
messageType,
success,
}) {
if (success && messageType === 'friend-request')
await this.onFriendRequestSent();
await Promise.all( await Promise.all(
(failoverNumbers || []).map(async number => { (failoverNumbers || []).map(async number => {
const conversation = ConversationController.get(number); const conversation = ConversationController.get(number);
@ -1291,36 +1267,8 @@
}, },
}; };
}, },
// eslint-disable-next-line no-unused-vars
async onNewMessage(message) { async onNewMessage(message) {
if (message.get('type') === 'friend-request' && message.get('direction') === 'incoming') {
// We need to make sure we remove any pending requests that we may have
// This is to ensure that one user cannot spam us with multiple friend requests.
const incoming = await this.getPendingFriendRequests('incoming');
// Delete the old messages if it's pending
await Promise.all(
incoming
.filter(i => i.id !== message.id)
.map(request => this._removeMessage(request.id))
);
// If we have an outgoing friend request then
// we auto accept the incoming friend request
const outgoing = await this.getPendingFriendRequests('outgoing');
if (outgoing.length > 0) {
const current = this.messageCollection.find(i => i.id === message.id);
if (current) {
await current.acceptFriendRequest();
} else {
window.log.debug('onNewMessage: Failed to find incoming friend request');
}
}
// Trigger an update if we removed or updated messages
if (outgoing.length > 0 || incoming.length > 0)
this.trigger('change');
}
return this.updateLastMessage(); return this.updateLastMessage();
}, },
async updateLastMessage() { async updateLastMessage() {
@ -1328,12 +1276,6 @@
return; return;
} }
// Update our friend indicator
this.updatePendingFriendRequests();
// Update our text input state
await this.updateTextInputState();
const messages = await window.Signal.Data.getMessagesByConversation( const messages = await window.Signal.Data.getMessagesByConversation(
this.id, this.id,
{ limit: 1, MessageCollection: Whisper.MessageCollection } { limit: 1, MessageCollection: Whisper.MessageCollection }
@ -1981,11 +1923,12 @@
}, },
notify(message) { notify(message) {
if (!message.isIncoming()) { if (message.isFriendRequest()) {
if (message.isFriendRequest()) if (this.hasSentFriendRequest())
return this.notifyFriendRequest(message.get('source'), 'accepted')
return this.notifyFriendRequest(message.get('source'), 'requested'); return this.notifyFriendRequest(message.get('source'), 'requested');
return Promise.resolve();
} }
if (!message.isIncoming()) return Promise.resolve();
const conversationId = this.id; const conversationId = this.id;
return ConversationController.getOrCreateAndWait( return ConversationController.getOrCreateAndWait(

@ -1375,6 +1375,20 @@
} }
} }
let autoAccept = false;
if (message.get('type') === 'friend-request') {
if (conversation.hasSentFriendRequest()) {
// Automatically accept incoming friend requests if we have send one already
autoAccept = true;
message.set({ friendStatus: 'accepted' });
await conversation.onFriendRequestAccepted();
window.libloki.sendFriendRequestAccepted(message.get('source'));
} else if (conversation.isFriendRequestStatusNone()) {
await conversation.onFriendRequestReceived();
}
} else {
await conversation.onFriendRequestAccepted();
}
const id = await window.Signal.Data.saveMessage(message.attributes, { const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message, Message: Whisper.Message,
}); });
@ -1422,6 +1436,10 @@
} }
if (message.get('unread')) { if (message.get('unread')) {
// Need to do this here because the conversation has already changed states
if (autoAccept)
await conversation.notifyFriendRequest(source, 'accepted');
else
await conversation.notify(message); await conversation.notify(message);
} }

@ -7,10 +7,9 @@
/* global Signal: false */ /* global Signal: false */
/* global storage: false */ /* global storage: false */
/* global Whisper: false */ /* global Whisper: false */
/* global BlockNumberConversation: false */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function () {
'use strict'; 'use strict';
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
@ -255,8 +254,7 @@
this.$('.send-message').blur(this.unfocusBottomBar.bind(this)); this.$('.send-message').blur(this.unfocusBottomBar.bind(this));
this.$emojiPanelContainer = this.$('.emoji-panel-container'); this.$emojiPanelContainer = this.$('.emoji-panel-container');
this.model.updateTextInputState();
this.model.updateFriendRequestUI();
}, },
events: { events: {

@ -904,10 +904,7 @@ MessageReceiver.prototype.extend({
}) })
); );
}, },
async handleFriendRequestMessage(envelope, msg) { handleDataMessage(envelope, msg) {
return this.handleDataMessage(envelope, msg, 'friend-request');
},
handleDataMessage(envelope, msg, type = 'data') {
window.log.info('data message from', this.getEnvelopeId(envelope)); window.log.info('data message from', this.getEnvelopeId(envelope));
let p = Promise.resolve(); let p = Promise.resolve();
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
@ -924,6 +921,8 @@ MessageReceiver.prototype.extend({
message.group && message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
); );
const friendRequest =
envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST;
// Check if we need to update any profile names // Check if we need to update any profile names
if (!isMe && conversation) { if (!isMe && conversation) {
@ -936,7 +935,7 @@ MessageReceiver.prototype.extend({
conversation.setProfile(profile); conversation.setProfile(profile);
} }
if (type === 'friend-request' && isMe) { if (friendRequest && isMe) {
window.log.info( window.log.info(
'refusing to add a friend request to ourselves' 'refusing to add a friend request to ourselves'
); );
@ -955,7 +954,7 @@ MessageReceiver.prototype.extend({
const ev = new Event('message'); const ev = new Event('message');
ev.confirm = this.removeFromCache.bind(this, envelope); ev.confirm = this.removeFromCache.bind(this, envelope);
ev.data = { ev.data = {
type, friendRequest,
source: envelope.source, source: envelope.source,
sourceDevice: envelope.sourceDevice, sourceDevice: envelope.sourceDevice,
timestamp: envelope.timestamp.toNumber(), timestamp: envelope.timestamp.toNumber(),
@ -992,27 +991,11 @@ MessageReceiver.prototype.extend({
async innerHandleContentMessage(envelope, plaintext) { async innerHandleContentMessage(envelope, plaintext) {
const content = textsecure.protobuf.Content.decode(plaintext); const content = textsecure.protobuf.Content.decode(plaintext);
let conversation; if (content.preKeyBundleMessage)
try {
conversation = await window.ConversationController.getOrCreateAndWait(envelope.source, 'private');
} catch (e) {
window.log.info('Error getting conversation: ', envelope.source);
}
if (envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) {
conversation.onFriendRequestReceived();
} else {
conversation.onFriendRequestAccepted();
}
if (content.preKeyBundleMessage) {
const preKeyBundleMessage =
this.decodePreKeyBundleMessage(content.preKeyBundleMessage);
await this.savePreKeyBundleMessage( await this.savePreKeyBundleMessage(
envelope.source, envelope.source,
preKeyBundleMessage content.preKeyBundleMessage
); );
return this.handleDataMessage(envelope, content.dataMessage, 'friend-request');
}
if (content.syncMessage) if (content.syncMessage)
return this.handleSyncMessage(envelope, content.syncMessage); return this.handleSyncMessage(envelope, content.syncMessage);
if (content.dataMessage) if (content.dataMessage)
@ -1024,6 +1007,12 @@ MessageReceiver.prototype.extend({
if (content.receiptMessage) if (content.receiptMessage)
return this.handleReceiptMessage(envelope, content.receiptMessage); return this.handleReceiptMessage(envelope, content.receiptMessage);
// Trigger conversation friend request event for empty message
const conversation = window.ConversationController.get(envelope.source);
if (conversation) {
conversation.onFriendRequestAccepted();
conversation.notifyFriendRequest(envelope.source, 'accepted');
}
this.removeFromCache(envelope); this.removeFromCache(envelope);
return null; return null;
}, },
@ -1227,7 +1216,7 @@ MessageReceiver.prototype.extend({
return this.removeFromCache(envelope); return this.removeFromCache(envelope);
}, },
decodePreKeyBundleMessage(preKeyBundleMessage) { async savePreKeyBundleMessage(pubKey, preKeyBundleMessage) {
const [identityKey, preKey, signedKey, signature] = [ const [identityKey, preKey, signedKey, signature] = [
preKeyBundleMessage.identityKey, preKeyBundleMessage.identityKey,
preKeyBundleMessage.preKey, preKeyBundleMessage.preKey,
@ -1235,24 +1224,9 @@ MessageReceiver.prototype.extend({
preKeyBundleMessage.signature, preKeyBundleMessage.signature,
].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer()); ].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer());
return {
...preKeyBundleMessage,
identityKey,
preKey,
signedKey,
signature,
};
},
async savePreKeyBundleMessage(pubKey, preKeyBundleMessage) {
if (!preKeyBundleMessage) return null;
const { const {
preKeyId, preKeyId,
signedKeyId, signedKeyId,
identityKey,
preKey,
signedKey,
signature,
} = preKeyBundleMessage; } = preKeyBundleMessage;
if (pubKey !== StringView.arrayBufferToHex(identityKey)) { if (pubKey !== StringView.arrayBufferToHex(identityKey)) {
@ -1261,7 +1235,7 @@ MessageReceiver.prototype.extend({
); );
} }
return libloki.savePreKeyBundleForNumber({ await libloki.savePreKeyBundleForNumber({
pubKey, pubKey,
preKeyId, preKeyId,
signedKeyId, signedKeyId,

@ -60,6 +60,7 @@ OutgoingMessage.prototype = {
failoverNumbers: this.failoverNumbers, failoverNumbers: this.failoverNumbers,
errors: this.errors, errors: this.errors,
unidentifiedDeliveries: this.unidentifiedDeliveries, unidentifiedDeliveries: this.unidentifiedDeliveries,
messageType: this.messageType,
}); });
} }
}, },
@ -280,7 +281,7 @@ OutgoingMessage.prototype = {
const address = new libsignal.SignalProtocolAddress(number, deviceId); const address = new libsignal.SignalProtocolAddress(number, deviceId);
const ourKey = textsecure.storage.user.getNumber(); const ourKey = textsecure.storage.user.getNumber();
const options = {}; const options = {};
const fallBackEncryption = new libloki.FallBackSessionCipher(address); const fallBackCipher = new libloki.FallBackSessionCipher(address);
// Check if we need to attach the preKeys // Check if we need to attach the preKeys
let sessionCipher; let sessionCipher;
@ -288,7 +289,7 @@ OutgoingMessage.prototype = {
// Encrypt them with the fallback // Encrypt them with the fallback
this.message.preKeyBundleMessage = await libloki.getPreKeyBundleForNumber(number); this.message.preKeyBundleMessage = await libloki.getPreKeyBundleForNumber(number);
window.log.info('attaching prekeys to outgoing message'); window.log.info('attaching prekeys to outgoing message');
sessionCipher = fallBackEncryption; sessionCipher = fallBackCipher;
} else { } else {
sessionCipher = new libsignal.SessionCipher( sessionCipher = new libsignal.SessionCipher(
textsecure.storage.protocol, textsecure.storage.protocol,
@ -453,16 +454,10 @@ OutgoingMessage.prototype = {
log.info('Fallback encryption enabled'); log.info('Fallback encryption enabled');
this.fallBackEncryption = true; this.fallBackEncryption = true;
} }
if (this.fallBackEncryption && conversation) {
await conversation.onFriendRequestSent();
}
}) })
.then(this.reloadDevicesAndSend(number, true)) .then(this.reloadDevicesAndSend(number, true))
.catch(error => { .catch(error => {
if (this.fallBackEncryption && conversation) { conversation.resetPendingSend();
conversation.updateFriendRequestUI();
}
if (error.message === 'Identity key changed') { if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
error = new textsecure.OutgoingIdentityKeyError( error = new textsecure.OutgoingIdentityKeyError(

Loading…
Cancel
Save