diff --git a/lib/shared/reaction-utils.js b/lib/shared/reaction-utils.js --- a/lib/shared/reaction-utils.js +++ b/lib/shared/reaction-utils.js @@ -3,35 +3,22 @@ import invariant from 'invariant'; import _sortBy from 'lodash/fp/sortBy.js'; import * as React from 'react'; -import uuid from 'uuid'; -import { - type OutboundDMOperationSpecification, - dmOperationSpecificationTypes, -} from './dm-ops/dm-op-types.js'; import { useProcessAndSendDMOperation } from './dm-ops/process-dm-ops.js'; -import { getNextLocalID } from './id-utils.js'; import { relationshipBlockedInEitherDirection } from './relationship-utils.js'; import { useThreadHasPermission } from './thread-utils.js'; +import { threadSpecs } from './threads/thread-specs.js'; import { stringForUserExplicit } from './user-utils.js'; -import { sendReactionMessageActionTypes } from '../actions/message-actions.js'; import { useENSNames } from '../hooks/ens-cache.js'; import { useSendReactionMessage } from '../hooks/message-hooks.js'; import type { ReactionInfo } from '../selectors/chat-selectors.js'; -import type { DMSendReactionMessageOperation } from '../types/dm-ops.js'; -import { messageTypes } from '../types/message-types-enum.js'; import type { ComposableMessageInfo, RobotextMessageInfo, } from '../types/message-types.js'; -import type { RawReactionMessageInfo } from '../types/messages/reaction.js'; import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import { threadPermissions } from '../types/thread-permission-types.js'; -import { - thickThreadTypes, - threadTypeIsThick, -} from '../types/thread-types-enum.js'; -import { getMessageForException, SendMessageError } from '../utils/errors.js'; +import { threadTypeIsThick } from '../types/thread-types-enum.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; @@ -144,8 +131,6 @@ return; } - const localID = getNextLocalID(); - invariant(viewerID, 'viewerID should be set'); const viewerReacted = reactions[reaction] @@ -153,86 +138,27 @@ : false; const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; - const threadID = threadInfo.id; - - if (threadTypeIsThick(threadInfo.type)) { - const op: DMSendReactionMessageOperation = { - type: 'send_reaction_message', - threadID, - creatorID: viewerID, - time: Date.now(), - messageID: uuid.v4(), - targetMessageID: messageID, + void threadSpecs[threadInfo.type].protocol.sendReaction( + { + messageID, + threadInfo, reaction, action, - }; - const opSpecification: OutboundDMOperationSpecification = { - type: dmOperationSpecificationTypes.OUTBOUND, - op, - recipients: { - type: 'all_thread_members', - threadID: - threadInfo.type === thickThreadTypes.THICK_SIDEBAR && - threadInfo.parentThreadID - ? threadInfo.parentThreadID - : threadInfo.id, - }, - }; - void processAndSendDMOperation(opSpecification); - return; - } - - const reactionMessagePromise = (async () => { - try { - const result = await callSendReactionMessage({ - threadID, - localID, - targetMessageID: messageID, - reaction, - action, - }); - return { - localID, - serverID: result.id, - threadID, - time: result.time, - }; - } catch (e) { - showErrorAlert(); - const exceptionMessage = getMessageForException(e) ?? ''; - throw new SendMessageError( - `Exception while sending reaction: ${exceptionMessage}`, - localID, - threadID, - ); - } - })(); - - const startingPayload: RawReactionMessageInfo = { - type: messageTypes.REACTION, - threadID, - localID, - creatorID: viewerID, - time: Date.now(), - targetMessageID: messageID, - reaction, - action, - }; - - void dispatchActionPromise( - sendReactionMessageActionTypes, - reactionMessagePromise, - undefined, - startingPayload, + viewerID, + showErrorAlert, + }, + { + processAndSendDMOperation, + keyserverSendReaction: callSendReactionMessage, + dispatchActionPromise, + }, ); }, [ messageID, viewerID, reactions, - threadInfo.id, - threadInfo.type, - threadInfo.parentThreadID, + threadInfo, dispatchActionPromise, processAndSendDMOperation, callSendReactionMessage, diff --git a/lib/shared/threads/protocols/dm-thread-protocol.js b/lib/shared/threads/protocols/dm-thread-protocol.js --- a/lib/shared/threads/protocols/dm-thread-protocol.js +++ b/lib/shared/threads/protocols/dm-thread-protocol.js @@ -11,6 +11,7 @@ DMDeleteEntryOperation, DMEditEntryOperation, DMSendEditMessageOperation, + DMSendReactionMessageOperation, DMThreadSettingsChanges, } from '../../../types/dm-ops.js'; import { thickThreadTypes } from '../../../types/thread-types-enum.js'; @@ -40,6 +41,8 @@ EditEntryUtils, ProtocolSetThreadUnreadStatusInput, SetThreadUnreadStatusUtils, + ProtocolSendReactionInput, + SendReactionUtils, } from '../thread-spec.js'; const dmThreadProtocol: ThreadProtocol = Object.freeze({ @@ -436,6 +439,38 @@ }; }, + sendReaction: async ( + input: ProtocolSendReactionInput, + utils: SendReactionUtils, + ) => { + const { threadInfo, viewerID, messageID, reaction, action } = input; + const threadID = threadInfo.id; + + const op: DMSendReactionMessageOperation = { + type: 'send_reaction_message', + threadID, + creatorID: viewerID, + time: Date.now(), + messageID: uuid.v4(), + targetMessageID: messageID, + reaction, + action, + }; + const opSpecification: OutboundDMOperationSpecification = { + type: dmOperationSpecificationTypes.OUTBOUND, + op, + recipients: { + type: 'all_thread_members', + threadID: + threadInfo.type === thickThreadTypes.THICK_SIDEBAR && + threadInfo.parentThreadID + ? threadInfo.parentThreadID + : threadInfo.id, + }, + }; + await utils.processAndSendDMOperation(opSpecification); + }, + allowsDeletingSidebarSource: false, presentationDetails: { diff --git a/lib/shared/threads/protocols/keyserver-thread-protocol.js b/lib/shared/threads/protocols/keyserver-thread-protocol.js --- a/lib/shared/threads/protocols/keyserver-thread-protocol.js +++ b/lib/shared/threads/protocols/keyserver-thread-protocol.js @@ -4,7 +4,10 @@ import * as React from 'react'; import type { ProcessHolders } from '../../../actions/holder-actions.js'; -import { sendEditMessageActionTypes } from '../../../actions/message-actions.js'; +import { + sendEditMessageActionTypes, + sendReactionMessageActionTypes, +} from '../../../actions/message-actions.js'; import { type MediaMetadataReassignmentAction, updateMultimediaMessageMediaActionType, @@ -20,6 +23,7 @@ type EncryptedVideo, type Media, } from '../../../types/media-types.js'; +import { messageTypes } from '../../../types/message-types-enum.js'; import { type MediaIDUpdatePayload, type MediaIDUpdates, @@ -27,11 +31,17 @@ type RawMessageInfo, } from '../../../types/message-types.js'; import { getMediaMessageServerDBContentsFromMedia } from '../../../types/messages/media.js'; +import type { RawReactionMessageInfo } from '../../../types/messages/reaction.js'; import type { Dispatch } from '../../../types/redux-types.js'; import { blobHashFromBlobServiceURI, isBlobServiceURI, } from '../../../utils/blob-service.js'; +import { + getMessageForException, + SendMessageError, +} from '../../../utils/errors.js'; +import { getNextLocalID } from '../../id-utils.js'; import type { ThreadProtocol, ProtocolSendTextMessageInput, @@ -50,6 +60,8 @@ EditEntryUtils, ProtocolSetThreadUnreadStatusInput, SetThreadUnreadStatusUtils, + ProtocolSendReactionInput, + SendReactionUtils, } from '../thread-spec.js'; const keyserverThreadProtocol: ThreadProtocol = Object.freeze({ @@ -214,6 +226,68 @@ return utils.keyserverSetThreadUnreadStatus(rest); }, + sendReaction: async ( + input: ProtocolSendReactionInput, + utils: SendReactionUtils, + ) => { + const { + threadInfo, + viewerID, + messageID, + reaction, + action, + showErrorAlert, + } = input; + const threadID = threadInfo.id; + const localID = getNextLocalID(); + + const reactionMessagePromise = (async () => { + try { + const result = await utils.keyserverSendReaction({ + threadID, + localID, + targetMessageID: messageID, + reaction, + action, + }); + return { + localID, + serverID: result.id, + threadID, + time: result.time, + }; + } catch (e) { + showErrorAlert(); + const exceptionMessage = getMessageForException(e) ?? ''; + throw new SendMessageError( + `Exception while sending reaction: ${exceptionMessage}`, + localID, + threadID, + ); + } + })(); + + const startingPayload: RawReactionMessageInfo = { + type: messageTypes.REACTION, + threadID, + localID, + creatorID: viewerID, + time: Date.now(), + targetMessageID: messageID, + reaction, + action, + }; + + void utils.dispatchActionPromise( + sendReactionMessageActionTypes, + reactionMessagePromise, + undefined, + startingPayload, + ); + + await reactionMessagePromise; + }, + allowsDeletingSidebarSource: true, presentationDetails: { diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js --- a/lib/shared/threads/thread-spec.js +++ b/lib/shared/threads/thread-spec.js @@ -38,6 +38,7 @@ SendMultimediaMessagePayload, SendEditMessageRequest, SendEditMessageResult, + SendReactionMessageRequest, } from '../../types/message-types.js'; import type { RawTextMessageInfo } from '../../types/messages/text.js'; import type { @@ -148,6 +149,20 @@ +keyserverSetThreadUnreadStatus: SetThreadUnreadStatusRequest => Promise, }; +export type ProtocolSendReactionInput = { + +messageID: string, + +threadInfo: ThreadInfo, + +reaction: string, + +action: 'remove_reaction' | 'add_reaction', + +viewerID: string, + +showErrorAlert: () => mixed, +}; +export type SendReactionUtils = { + +processAndSendDMOperation: OutboundDMOperationSpecification => Promise, + +keyserverSendReaction: SendReactionMessageRequest => Promise, + +dispatchActionPromise: DispatchActionPromise, +}; + export type ThreadProtocol = { +sendTextMessage: ( message: ProtocolSendTextMessageInput, @@ -186,6 +201,10 @@ input: ProtocolSetThreadUnreadStatusInput, utils: SetThreadUnreadStatusUtils, ) => Promise, + +sendReaction: ( + input: ProtocolSendReactionInput, + utils: SendReactionUtils, + ) => Promise, +allowsDeletingSidebarSource: boolean, +presentationDetails: { +membershipChangesShownInThreadPreview: boolean,