diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js --- a/keyserver/src/updaters/thread-updaters.js +++ b/keyserver/src/updaters/thread-updaters.js @@ -2,7 +2,10 @@ import { getRolePermissionBlobs } from 'lib/permissions/thread-permissions.js'; import { filteredThreadIDs } from 'lib/selectors/calendar-filter-selectors.js'; -import { getPinnedContentFromMessage } from 'lib/shared/message-utils.js'; +import { + getPinnedContentFromMessage, + isInvalidPinSource, +} from 'lib/shared/message-utils.js'; import { threadHasAdminRole, roleIsAdminRole, @@ -11,7 +14,6 @@ } from 'lib/shared/thread-utils.js'; import type { Shape } from 'lib/types/core.js'; import { messageTypes } from 'lib/types/message-types-enum.js'; -import { isComposableMessageType } from 'lib/types/message-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { @@ -871,7 +873,7 @@ const { messageID, action } = request; const targetMessage = await fetchMessageInfoByID(viewer, messageID); - if (!targetMessage || !isComposableMessageType(targetMessage.type)) { + if (!targetMessage || isInvalidPinSource(targetMessage)) { throw new ServerError('invalid_parameters'); } diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js --- a/lib/shared/message-utils.js +++ b/lib/shared/message-utils.js @@ -667,6 +667,10 @@ ); } +function isInvalidPinSource(message: RawMessageInfo | MessageInfo): boolean { + return !messageSpecs[message.type].canBePinned; +} + export { localIDPrefix, messageKey, @@ -697,4 +701,5 @@ constructChangeRoleEntityText, useNextLocalID, isInvalidSidebarSource, + isInvalidPinSource, }; diff --git a/lib/shared/message-utils.test.js b/lib/shared/message-utils.test.js --- a/lib/shared/message-utils.test.js +++ b/lib/shared/message-utils.test.js @@ -1,6 +1,6 @@ // @flow -import { isInvalidSidebarSource } from './message-utils.js'; +import { isInvalidSidebarSource, isInvalidPinSource } from './message-utils.js'; import { messageSpecs } from '../shared/messages/message-specs.js'; import { messageTypes } from '../types/message-types-enum.js'; import type { RawSidebarSourceMessageInfo } from '../types/message-types.js'; @@ -502,3 +502,231 @@ expect(canBeSidebarSource).toBe(false); }); }); + +describe('isInvalidPinSource & canBePinned', () => { + it('should return true for RawTextMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.TEXT]; + + const shouldBeInvalidPinSource = isInvalidPinSource(textMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(false); + expect(canBePinned).toBe(true); + }); + + it('should return false for RawCreateThreadMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CREATE_THREAD]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + createThreadMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawAddMembersMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.ADD_MEMBERS]; + + const shouldBeInvalidPinSource = isInvalidPinSource(addMembersMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawCreateSubthreadMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CREATE_SUB_THREAD]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + createSubthreadMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawChangeSettingsMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CHANGE_SETTINGS]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + changeSettingsMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawRemoveMembersMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.REMOVE_MEMBERS]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + removeMembersMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawChangeRoleMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CHANGE_ROLE]; + + const shouldBeInvalidPinSource = isInvalidPinSource(changeRoleMessageinfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawLeaveThreadMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.LEAVE_THREAD]; + + const shouldBeInvalidPinSource = isInvalidPinSource(leaveThreadMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawJoinThreadMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.JOIN_THREAD]; + + const shouldBeInvalidPinSource = isInvalidPinSource(joinThreadMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawCreateEntryMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CREATE_ENTRY]; + + const shouldBeInvalidPinSource = isInvalidPinSource(createEntryMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawEditEntryMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.EDIT_ENTRY]; + + const shouldBeInvalidPinSource = isInvalidPinSource(editEntryMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawDeleteEntryMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.DELETE_ENTRY]; + + const shouldBeInvalidPinSource = isInvalidPinSource(deleteEntryMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawRestoreEntryMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.RESTORE_ENTRY]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + restoreEntryMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawUpdateRelationshipMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.UPDATE_RELATIONSHIP]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + updateRelationshipMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return true for RawImagesMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.IMAGES]; + + const shouldBeInvalidPinSource = isInvalidPinSource(imageMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(false); + expect(canBePinned).toBe(true); + }); + + it('should return true for RawMediaMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.MULTIMEDIA]; + + const shouldBeInvalidPinSource = isInvalidPinSource(mediaMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(false); + expect(canBePinned).toBe(true); + }); + + it('should return false for RawSidebarSourceMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.SIDEBAR_SOURCE]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + sidebarSourceMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawCreateSidebarMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.CREATE_SIDEBAR]; + + const shouldBeInvalidPinSource = isInvalidPinSource( + createSidebarMessageInfo, + ); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawReactionMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.REACTION]; + + const shouldBeInvalidPinSource = isInvalidPinSource(reactionMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawEditMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.EDIT_MESSAGE]; + + const shouldBeInvalidPinSource = isInvalidPinSource(editMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); + + it('should return false for RawTogglePinMessageInfo', () => { + const messageSpec = messageSpecs[messageTypes.TOGGLE_PIN]; + + const shouldBeInvalidPinSource = isInvalidPinSource(togglePinMessageInfo); + const canBePinned = messageSpec.canBePinned; + + expect(shouldBeInvalidPinSource).toBe(true); + expect(canBePinned).toBe(false); + }); +}); diff --git a/native/chat/message-results-screen.react.js b/native/chat/message-results-screen.react.js --- a/native/chat/message-results-screen.react.js +++ b/native/chat/message-results-screen.react.js @@ -7,8 +7,10 @@ import { fetchPinnedMessages } from 'lib/actions/message-actions.js'; import { messageListData } from 'lib/selectors/chat-selectors.js'; -import { createMessageInfo } from 'lib/shared/message-utils.js'; -import { isComposableMessageType } from 'lib/types/message-types.js'; +import { + createMessageInfo, + isInvalidPinSource, +} from 'lib/shared/message-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { useServerCall } from 'lib/utils/action-utils.js'; @@ -75,7 +77,7 @@ item => item.itemType === 'message' && item.isPinned && - isComposableMessageType(item.messageInfo.type), + !isInvalidPinSource(item.messageInfo), ); // By the nature of using messageListData and passing in diff --git a/web/modals/chat/message-results-modal.react.js b/web/modals/chat/message-results-modal.react.js --- a/web/modals/chat/message-results-modal.react.js +++ b/web/modals/chat/message-results-modal.react.js @@ -11,9 +11,9 @@ import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { createMessageInfo, + isInvalidPinSource, modifyItemForResultScreen, } from 'lib/shared/message-utils.js'; -import { isComposableMessageType } from 'lib/types/message-types.js'; import { type ThreadInfo } from 'lib/types/thread-types.js'; import { useServerCall, @@ -80,7 +80,7 @@ item => item.itemType === 'message' && item.isPinned && - isComposableMessageType(item.messageInfo.type), + !isInvalidPinSource(item.messageInfo), ); // By the nature of using messageListData and passing in diff --git a/web/utils/tooltip-action-utils.js b/web/utils/tooltip-action-utils.js --- a/web/utils/tooltip-action-utils.js +++ b/web/utils/tooltip-action-utils.js @@ -10,14 +10,16 @@ ChatMessageInfoItem, } from 'lib/selectors/chat-selectors.js'; import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js'; -import { createMessageReply } from 'lib/shared/message-utils.js'; +import { + createMessageReply, + isInvalidPinSource, +} from 'lib/shared/message-utils.js'; import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils.js'; import { threadHasPermission, useSidebarExistsOrCanBeCreated, } from 'lib/shared/thread-utils.js'; import { messageTypes } from 'lib/types/message-types-enum.js'; -import { isComposableMessageType } from 'lib/types/message-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { longAbsoluteDate } from 'lib/utils/date-utils.js'; @@ -273,7 +275,7 @@ const { messageInfo, isPinned } = item; const canTogglePin = - isComposableMessageType(messageInfo.type) && + !isInvalidPinSource(messageInfo) && threadHasPermission(threadInfo, threadPermissions.MANAGE_PINS); const inputState = React.useContext(InputStateContext);