diff --git a/js/models/conversations.js b/js/models/conversations.js index 8074d374a..06e902fa4 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -196,9 +196,9 @@ removeMessage(); }, - addSingleMessage(message) { + addSingleMessage(message, setToExpire = true) { const model = this.messageCollection.add(message, { merge: true }); - model.setToExpire(); + if (setToExpire) model.setToExpire(); return model; }, @@ -245,14 +245,16 @@ }, async getPendingFriendRequests(direction) { // Theoretically all ouur messages could be friend requests, thus we have to unfortunately go through each one :( - // We are most likely to find the friend request in the more recent conversations first - const messages = await window.Signal.Data.getMessagesByConversation(this.id, { - MessageCollection: Whisper.MessageCollection, - limit: Number.MAX_VALUE, - }).reverse(); + const messages = await window.Signal.Data.getMessagesByConversation( + this.id, + { MessageCollection: Whisper.MessageCollection } + ); + // We are most likely to find the friend request in the more recent conversations first // Get the messages that are matching the direction and the friendStatus - return messages.filter(m => (m.direction === direction && m.friendStatus === 'pending')); + return messages.models.reverse().filter(m => { + return (m.attributes.direction === direction && m.attributes.friendStatus === 'pending') + }); }, getPropsForListItem() { const result = { @@ -425,7 +427,7 @@ return this.get('keyExchangeCompleted') || false; }, - setKeyExchangeCompleted(completed) { + async setKeyExchangeCompleted(completed) { if (typeof completed !== 'boolean') { throw new Error('setKeyExchangeCompleted expects a bool'); } @@ -680,7 +682,7 @@ }, // This will add a message which will allow the user to reply to a friend request async addFriendRequest(body, options = {}) { - const mergedOptions = { + const _options = { status: 'pending', direction: 'incoming', preKeyBundle: null, @@ -714,6 +716,27 @@ Conversation: Whisper.Conversation, }); + // If we need to add new incoming friend requests + // Then 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 + if (_options.direction === 'incoming') { + const requests = await this.getPendingFriendRequests('incoming'); + + for (const request of requests) { + // Delete the old message if it's pending + await window.Signal.Data.removeMessage(request.id, { Message: Whisper.Message }); + const existing = this.messageCollection.get(request.id); + if (existing) { + this.messageCollection.remove(request.id); + existing.trigger('destroy'); + } + } + // Trigger an update if we removed messages + if (requests.length > 0) + this.trigger('change'); + } + + // Add the new message const timestamp = Date.now(); const message = { conversationId: this.id, @@ -723,10 +746,10 @@ unread: 1, from: this.id, to: this.ourNumber, - friendStatus: mergedOptions.status, - direction: mergedOptions.direction, + friendStatus: _options.status, + direction: _options.direction, body, - preKeyBundle: mergedOptions.preKeyBundle, + preKeyBundle: _options.preKeyBundle, }; const id = await window.Signal.Data.saveMessage(message, { diff --git a/js/models/messages.js b/js/models/messages.js index 0c5be577f..af80477ba 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -297,13 +297,13 @@ getPropsForFriendRequest() { const source = this.get('from'); const target = this.get('to'); - const status = this.get('friendStatus') || 'pending'; - const type = this.get('direction') || 'incoming'; + const friendStatus = this.get('friendStatus') || 'pending'; + const direction = this.get('direction') || 'incoming'; const conversation = this.getConversation(); // I.e do we send a network request from the model? or call a function in the conversation to send the new status const onAccept = async () => { - this.set({ status: 'accepted' }); + this.set({ friendStatus: 'accepted' }); await window.Signal.Data.saveMessage(this.attributes, { Message: Whisper.Message, }); @@ -315,7 +315,7 @@ }; const onDecline = async () => { - this.set({ status: 'declined' }); + this.set({ friendStatus: 'declined' }); await window.Signal.Data.saveMessage(this.attributes, { Message: Whisper.Message, }); @@ -334,8 +334,8 @@ text: this.createNonBreakingLastSeparator(this.get('body')), source: this.findAndFormatContact(source), target: this.findAndFormatContact(target), - status, - type, + direction, + friendStatus, onAccept, onDecline, onDelete, diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index c643ee850..3647cbe17 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -943,7 +943,7 @@ MessageReceiver.prototype.extend({ }, // A handler function for when a friend request is accepted or declined async onFriendRequestUpdate(pubKey, message) { - if (!message || !message.requestType || !message.friendStatus) return; + if (!message || !message.direction || !message.friendStatus) return; // Update the conversation const conversation = ConversationController.get(pubKey); @@ -953,7 +953,7 @@ MessageReceiver.prototype.extend({ } // Send our own prekeys as a response - if (message.requestType === 'incoming' && message.friendStatus === 'accepted') { + if (message.direction === 'incoming' && message.friendStatus === 'accepted') { libloki.sendEmptyMessageWithPreKeys(pubKey); // Register the preKeys used for communication diff --git a/ts/components/conversation/FriendRequest.tsx b/ts/components/conversation/FriendRequest.tsx index 69a3390b5..8f7f07fe8 100644 --- a/ts/components/conversation/FriendRequest.tsx +++ b/ts/components/conversation/FriendRequest.tsx @@ -12,11 +12,11 @@ interface Contact { interface Props { text?: string; - type: 'incoming' | 'outgoing'; + direction: 'incoming' | 'outgoing'; source: Contact; target: Contact; i18n: Localizer; - status: 'pending' | 'accepted' | 'declined'; + friendStatus: 'pending' | 'accepted' | 'declined'; onAccept: () => void; onDecline: () => void; onDelete: () => void; @@ -24,27 +24,27 @@ interface Props { export class FriendRequest extends React.Component { public getStringId() { - const { status, type } = this.props; + const { friendStatus, direction } = this.props; - switch (status) { + switch (friendStatus) { case 'pending': - return `${type}FriendRequestPending`; + return `${direction}FriendRequestPending`; case 'accepted': return `friendRequestAccepted`; case 'declined': return `friendRequestDeclined`; default: - throw new Error(`Invalid friend request status: ${status}`); + throw new Error(`Invalid friend request status: ${friendStatus}`); } } public renderContents() { - const { type, i18n, text } = this.props; + const { direction, i18n, text } = this.props; const id = this.getStringId(); return (
-
{i18n(id)}
+
{i18n(id)}
{!!text &&
@@ -56,19 +56,19 @@ export class FriendRequest extends React.Component { } public renderButtons() { - const { status, type, onAccept, onDecline, onDelete } = this.props; + const { friendStatus, direction, onAccept, onDecline, onDelete } = this.props; - if (type === 'incoming') { - if (status === 'pending') { + if (direction === 'incoming') { + if (friendStatus === 'pending') { return ( -
+
); - } else if (status === 'declined') { + } else if (friendStatus === 'declined') { return ( -
+
); @@ -78,12 +78,12 @@ export class FriendRequest extends React.Component { } public render() { - const { type} = this.props; + const { direction } = this.props; return ( -
-
-
+
+
+
{this.renderContents()} {this.renderButtons()}