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 @@ -853,6 +853,10 @@ await utils.processAndSendDMOperation(opSpecification); }, + pinMessage: async () => { + invariant(false, 'Pin message is not supported for DM threads'); + }, + joinThread: async ( input: ProtocolJoinThreadInput, utils: JoinThreadUtils, @@ -952,6 +956,9 @@ protocolName: protocolNames.COMM_DM, canReactToRobotext: true, supportsThreadRefreshing: false, + temporarilyDisabledFeatures: { + pinningMessages: true, + }, supportsMessageEdit: true, supportsRelationships: true, pinsStoredOnServer: false, diff --git a/lib/shared/threads/protocols/farcaster-thread-protocol.js b/lib/shared/threads/protocols/farcaster-thread-protocol.js --- a/lib/shared/threads/protocols/farcaster-thread-protocol.js +++ b/lib/shared/threads/protocols/farcaster-thread-protocol.js @@ -110,6 +110,8 @@ ProtocolJoinThreadInput, ProtocolDeleteMessageInput, DeleteMessageUtils, + ProtocolPinMessageInput, + PinMessageUtils, } from '../thread-spec.js'; import { threadTypeIsPersonal } from '../thread-specs.js'; @@ -946,6 +948,21 @@ }); }, + pinMessage: async ( + input: ProtocolPinMessageInput, + utils: PinMessageUtils, + ): Promise => { + const { messageID, threadInfo } = input; + const { farcasterPinMessage } = utils; + + const conversationId = conversationIDFromFarcasterThreadID(threadInfo.id); + + await farcasterPinMessage({ + conversationId, + messageId: messageID, + }); + }, + joinThread: async ( input: ProtocolJoinThreadInput, utils: JoinThreadUtils, 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 @@ -10,6 +10,7 @@ sendDeleteMessageActionTypes, sendEditMessageActionTypes, sendReactionMessageActionTypes, + toggleMessagePinActionTypes, } from '../../../actions/message-actions.js'; import { changeThreadMemberRolesActionTypes, @@ -117,6 +118,8 @@ CreateRealThreadParameters, DeleteMessageUtils, ProtocolDeleteMessageInput, + PinMessageUtils, + ProtocolPinMessageInput, JoinThreadUtils, ProtocolJoinThreadInput, ProtocolChangeThreadMemberRolesInput, @@ -674,6 +677,18 @@ await promise; }, + pinMessage: async ( + input: ProtocolPinMessageInput, + utils: PinMessageUtils, + ) => { + const promise = utils.keyserverToggleMessagePin({ + messageID: input.messageID, + action: input.action, + }); + void utils.dispatchActionPromise(toggleMessagePinActionTypes, promise); + await promise; + }, + joinThread: async ( input: ProtocolJoinThreadInput, utils: JoinThreadUtils, 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 @@ -82,6 +82,8 @@ NewThreadResult, RawThreadInfos, ThreadJoinPayload, + ToggleMessagePinRequest, + ToggleMessagePinResult, UpdateThreadRequest, } from '../../types/thread-types.js'; import type { DispatchActionPromise } from '../../utils/redux-promise-utils.js'; @@ -103,6 +105,7 @@ AcceptInviteInput, AcceptOneOnOneInviteInput, DeleteFarcasterMessageInput, + PinMessageInput, } from '../farcaster/farcaster-api.js'; import type { FarcasterConversation } from '../farcaster/farcaster-conversation-types.js'; import type { FarcasterMessageFetchingContextType } from '../farcaster/farcaster-message-fetching-context.js'; @@ -373,6 +376,17 @@ +dispatch: Dispatch, }; +export type ProtocolPinMessageInput = { + +messageID: string, + +threadInfo: ThreadInfo, + +action: 'pin' | 'unpin', +}; +export type PinMessageUtils = { + +keyserverToggleMessagePin: ToggleMessagePinRequest => Promise, + +farcasterPinMessage: PinMessageInput => Promise, + +dispatchActionPromise: DispatchActionPromise, +}; + export type ProtocolJoinThreadInput = { +rawThreadInfo: RawThreadInfo, +viewerID: ?string, @@ -493,6 +507,10 @@ input: ProtocolDeleteMessageInput, utils: DeleteMessageUtils, ) => Promise, + +pinMessage: ( + input: ProtocolPinMessageInput, + utils: PinMessageUtils, + ) => Promise, +joinThread: ( input: ProtocolJoinThreadInput, utils: JoinThreadUtils, diff --git a/lib/utils/pin-message-utils.js b/lib/utils/pin-message-utils.js new file mode 100644 --- /dev/null +++ b/lib/utils/pin-message-utils.js @@ -0,0 +1,39 @@ +// @flow + +import * as React from 'react'; + +import { useDispatchActionPromise } from './redux-promise-utils.js'; +import { useToggleMessagePin } from '../hooks/message-hooks.js'; +import { usePinMessage } from '../shared/farcaster/farcaster-api.js'; +import { threadSpecs } from '../shared/threads/thread-specs.js'; +import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; + +function usePinMessageAction(): ( + messageID: string, + threadInfo: ThreadInfo, + action: 'pin' | 'unpin', +) => Promise { + const keyserverToggleMessagePin = useToggleMessagePin(); + const farcasterPinMessage = usePinMessage(); + const dispatchActionPromise = useDispatchActionPromise(); + + return React.useCallback( + async ( + messageID: string, + threadInfo: ThreadInfo, + action: 'pin' | 'unpin', + ) => { + await threadSpecs[threadInfo.type].protocol().pinMessage( + { messageID, threadInfo, action }, + { + keyserverToggleMessagePin, + farcasterPinMessage, + dispatchActionPromise, + }, + ); + }, + [keyserverToggleMessagePin, farcasterPinMessage, dispatchActionPromise], + ); +} + +export { usePinMessageAction }; diff --git a/native/chat/toggle-pin-modal.react.js b/native/chat/toggle-pin-modal.react.js --- a/native/chat/toggle-pin-modal.react.js +++ b/native/chat/toggle-pin-modal.react.js @@ -4,11 +4,8 @@ import * as React from 'react'; import { Text, View } from 'react-native'; -import { toggleMessagePinActionTypes } from 'lib/actions/message-actions.js'; -import { useToggleMessagePin } from 'lib/hooks/message-hooks.js'; -import type { RawMessageInfo } from 'lib/types/message-types.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; +import { usePinMessageAction } from 'lib/utils/pin-message-utils.js'; import MessageResult from './message-result.react.js'; import Button from '../components/button.react.js'; @@ -34,8 +31,7 @@ const { messageInfo, isPinned } = item; const styles = useStyles(unboundStyles); - const callToggleMessagePin = useToggleMessagePin(); - const dispatchActionPromise = useDispatchActionPromise(); + const pinMessageAction = usePinMessageAction(); const modalInfo = React.useMemo(() => { if (isPinned) { @@ -61,26 +57,17 @@ }; }, [isPinned, styles.pinButton, styles.removePinButton]); - const createToggleMessagePinPromise = React.useCallback(async () => { - invariant(messageInfo.id, 'messageInfo.id should be defined'); - const result = await callToggleMessagePin({ - messageID: messageInfo.id, - action: modalInfo.action, - }); - return ({ - newMessageInfos: result.newMessageInfos, - threadID: result.threadID, - }: { +newMessageInfos: $ReadOnlyArray, +threadID: string }); - }, [callToggleMessagePin, messageInfo.id, modalInfo.action]); - const onPress = React.useCallback(() => { - void dispatchActionPromise( - toggleMessagePinActionTypes, - createToggleMessagePinPromise(), - ); - + invariant(messageInfo.id, 'messageInfo.id should be defined'); + void pinMessageAction(messageInfo.id, threadInfo, modalInfo.action); navigation.goBack(); - }, [createToggleMessagePinPromise, dispatchActionPromise, navigation]); + }, [ + pinMessageAction, + messageInfo.id, + threadInfo, + modalInfo.action, + navigation, + ]); const onCancel = React.useCallback(() => { navigation.goBack(); diff --git a/web/modals/chat/toggle-pin-modal.react.js b/web/modals/chat/toggle-pin-modal.react.js --- a/web/modals/chat/toggle-pin-modal.react.js +++ b/web/modals/chat/toggle-pin-modal.react.js @@ -3,15 +3,12 @@ import invariant from 'invariant'; import * as React from 'react'; -import { toggleMessagePinActionTypes } from 'lib/actions/message-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; -import { useToggleMessagePin } from 'lib/hooks/message-hooks.js'; import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js'; import { chatMessageItemEngagementTargetMessageInfo } from 'lib/shared/chat-message-item-utils.js'; import { modifyItemForResultScreen } from 'lib/shared/message-utils.js'; -import type { RawMessageInfo } from 'lib/types/message-types.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; +import { usePinMessageAction } from 'lib/utils/pin-message-utils.js'; import css from './toggle-pin-modal.css'; import Button, { buttonThemes } from '../../components/button.react.js'; @@ -28,8 +25,7 @@ const { isPinned } = item; const { popModal } = useModalContext(); - const callToggleMessagePin = useToggleMessagePin(); - const dispatchActionPromise = useDispatchActionPromise(); + const pinMessageAction = usePinMessageAction(); const modalInfo = React.useMemo(() => { if (isPinned) { @@ -74,34 +70,21 @@ chatMessageItemEngagementTargetMessageInfo(item); const engagementTargetMessageID = engagementTargetMessageInfo?.id; const onClick = React.useCallback(() => { - const createToggleMessagePinPromise = async () => { - invariant( - engagementTargetMessageID, - 'engagement target messageID should be defined', - ); - const result = await callToggleMessagePin({ - messageID: engagementTargetMessageID, - action: modalInfo.action, - }); - return ({ - newMessageInfos: result.newMessageInfos, - threadID: result.threadID, - }: { - +newMessageInfos: $ReadOnlyArray, - +threadID: string, - }); - }; - - void dispatchActionPromise( - toggleMessagePinActionTypes, - createToggleMessagePinPromise(), + invariant( + engagementTargetMessageID, + 'engagement target messageID should be defined', + ); + void pinMessageAction( + engagementTargetMessageID, + threadInfo, + modalInfo.action, ); popModal(); }, [ - modalInfo, - callToggleMessagePin, - dispatchActionPromise, + modalInfo.action, + pinMessageAction, engagementTargetMessageID, + threadInfo, popModal, ]);