Apply reactions optimistically

This commit is contained in:
Fedor Indutny 2021-05-13 12:10:20 -07:00 committed by Scott Nonnenberg
parent 18abe93022
commit 03eaa9eb3e
6 changed files with 54 additions and 33 deletions

View file

@ -61,11 +61,11 @@
reaction.get('targetAuthorUuid'), reaction.get('targetAuthorUuid'),
reaction.get('targetTimestamp') reaction.get('targetTimestamp')
); );
return; return undefined;
} }
// awaiting is safe since `onReaction` is never called from inside the queue // awaiting is safe since `onReaction` is never called from inside the queue
await targetConversation.queueJob(async () => { return await targetConversation.queueJob(async () => {
window.log.info( window.log.info(
'Handling reaction for', 'Handling reaction for',
reaction.get('targetTimestamp') reaction.get('targetTimestamp')
@ -112,7 +112,7 @@
oldReaction.forEach(r => this.remove(r)); oldReaction.forEach(r => this.remove(r));
} }
return; return undefined;
} }
const message = MessageController.register( const message = MessageController.register(
@ -120,15 +120,18 @@
targetMessage targetMessage
); );
await message.handleReaction(reaction); const oldReaction = await message.handleReaction(reaction);
this.remove(reaction); this.remove(reaction);
return oldReaction;
}); });
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'Reactions.onReaction error:', 'Reactions.onReaction error:',
error && error.stack ? error.stack : error error && error.stack ? error.stack : error
); );
return undefined;
} }
}, },
}))(); }))();

21
ts/model-types.d.ts vendored
View file

@ -109,15 +109,6 @@ export type MessageAttributesType = {
quote?: QuotedMessageType; quote?: QuotedMessageType;
reactions?: Array<{ reactions?: Array<{
emoji: string; emoji: string;
from: {
id: string;
color?: string;
avatarPath?: string;
name?: string;
profileName?: string;
isMe?: boolean;
phoneNumber?: string;
};
fromId: string; fromId: string;
targetAuthorUuid: string; targetAuthorUuid: string;
targetTimestamp: number; targetTimestamp: number;
@ -348,3 +339,15 @@ export declare class ConversationModelCollectionType extends Backbone.Collection
} }
export declare class MessageModelCollectionType extends Backbone.Collection<MessageModel> {} export declare class MessageModelCollectionType extends Backbone.Collection<MessageModel> {}
export type ReactionAttributesType = {
emoji: string;
remove?: boolean;
targetAuthorUuid: string;
targetTimestamp: number;
fromId?: string;
timestamp: number;
fromSync?: boolean;
};
export declare class ReactionModelType extends Backbone.Model<ReactionAttributesType> {}

View file

@ -9,6 +9,7 @@ import {
MessageModelCollectionType, MessageModelCollectionType,
WhatIsThis, WhatIsThis,
MessageAttributesType, MessageAttributesType,
ReactionModelType,
ConversationAttributesType, ConversationAttributesType,
VerificationOptions, VerificationOptions,
} from '../model-types.d'; } from '../model-types.d';
@ -3067,6 +3068,11 @@ export class ConversationModel extends window.Backbone
fromSync: true, fromSync: true,
}); });
// Apply reaction optimistically
const oldReaction = await window.Whisper.Reactions.onReaction(
reactionModel
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const destination = this.getSendTarget()!; const destination = this.getSendTarget()!;
const recipients = this.getRecipients(); const recipients = this.getRecipients();
@ -3176,9 +3182,23 @@ export class ConversationModel extends window.Backbone
throw new Error('No successful delivery for reaction'); throw new Error('No successful delivery for reaction');
} }
window.Whisper.Reactions.onReaction(reactionModel);
return result; return result;
}).catch(() => {
let reverseReaction: ReactionModelType;
if (oldReaction) {
// Either restore old reaction
reverseReaction = window.Whisper.Reactions.add({
...oldReaction,
fromId: window.ConversationController.getOurConversationId(),
timestamp,
});
} else {
// Or remove a new one on failure
reverseReaction = reactionModel.clone();
reverseReaction.set('remove', !reverseReaction.get('remove'));
}
window.Whisper.Reactions.onReaction(reverseReaction);
}); });
} }

View file

@ -5,6 +5,7 @@ import {
CustomError, CustomError,
MessageAttributesType, MessageAttributesType,
RetryOptions, RetryOptions,
ReactionAttributesType,
ShallowChallengeError, ShallowChallengeError,
QuotedMessageType, QuotedMessageType,
WhatIsThis, WhatIsThis,
@ -4084,9 +4085,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
async handleReaction( async handleReaction(
reaction: typeof window.WhatIsThis, reaction: typeof window.WhatIsThis,
shouldPersist = true shouldPersist = true
): Promise<void> { ): Promise<ReactionAttributesType | undefined> {
if (this.get('deletedForEveryone')) { if (this.get('deletedForEveryone')) {
return; return undefined;
} }
// We allow you to react to messages with outgoing errors only if it has sent // We allow you to react to messages with outgoing errors only if it has sent
@ -4095,7 +4096,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.hasErrors() && this.hasErrors() &&
(this.isIncoming() || this.getMessagePropStatus() !== 'partial-sent') (this.isIncoming() || this.getMessagePropStatus() !== 'partial-sent')
) { ) {
return; return undefined;
} }
const reactions = this.get('reactions') || []; const reactions = this.get('reactions') || [];
@ -4108,6 +4109,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
let reactionToRemove: Partial<ReactionType> | undefined; let reactionToRemove: Partial<ReactionType> | undefined;
let oldReaction: ReactionAttributesType | undefined;
if (reaction.get('remove')) { if (reaction.get('remove')) {
window.log.info('Removing reaction for message', messageId); window.log.info('Removing reaction for message', messageId);
const newReactions = reactions.filter( const newReactions = reactions.filter(
@ -4137,9 +4139,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
newReactions.push(reaction.toJSON()); newReactions.push(reaction.toJSON());
this.set({ reactions: newReactions }); this.set({ reactions: newReactions });
const oldReaction = reactions.find( oldReaction = reactions.find(re => re.fromId === reaction.get('fromId'));
re => re.fromId === reaction.get('fromId')
);
if (oldReaction) { if (oldReaction) {
reactionToRemove = { reactionToRemove = {
emoji: oldReaction.emoji, emoji: oldReaction.emoji,
@ -4177,6 +4177,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
Message: window.Whisper.Message, Message: window.Whisper.Message,
}); });
} }
return oldReaction;
} }
async handleDeleteForEveryone( async handleDeleteForEveryone(

View file

@ -179,15 +179,6 @@ export type MessageType = {
reactions?: Array<{ reactions?: Array<{
emoji: string; emoji: string;
timestamp: number; timestamp: number;
from: {
id: string;
color?: string;
avatarPath?: string;
name?: string;
profileName?: string;
isMe?: boolean;
phoneNumber?: string;
};
}>; }>;
deletedForEveryone?: boolean; deletedForEveryone?: boolean;

8
ts/window.d.ts vendored
View file

@ -16,6 +16,8 @@ import {
ConversationModelCollectionType, ConversationModelCollectionType,
MessageModelCollectionType, MessageModelCollectionType,
MessageAttributesType, MessageAttributesType,
ReactionAttributesType,
ReactionModelType,
} from './model-types.d'; } from './model-types.d';
import { ContactRecordIdentityState, TextSecureType } from './textsecure.d'; import { ContactRecordIdentityState, TextSecureType } from './textsecure.d';
import { import {
@ -724,9 +726,9 @@ export type WhisperType = {
}; };
Reactions: { Reactions: {
forMessage: (message: unknown) => Array<WhatIsThis>; forMessage: (message: unknown) => Array<ReactionModelType>;
add: (reaction: unknown) => WhatIsThis; add: (reaction: ReactionAttributesType) => ReactionModelType;
onReaction: (reactionModel: unknown) => unknown; onReaction: (reactionModel: ReactionModelType) => ReactionAttributesType;
}; };
Deletes: { Deletes: {