diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js --- a/lib/selectors/chat-selectors.js +++ b/lib/selectors/chat-selectors.js @@ -457,7 +457,8 @@ if ( messageInfo.type === messageTypes.REACTION || - messageInfo.type === messageTypes.EDIT_MESSAGE + messageInfo.type === messageTypes.EDIT_MESSAGE || + messageInfo.type === messageTypes.DELETE_MESSAGE ) { continue; } 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 @@ -42,6 +42,7 @@ defaultNumberPerThread, messageTruncationStatus, } from '../types/message-types.js'; +import type { DeleteMessageInfo } from '../types/messages/delete.js'; import type { EditMessageInfo, RawEditMessageInfo, @@ -387,7 +388,8 @@ | ComposableMessageInfo | RobotextMessageInfo | ReactionMessageInfo - | EditMessageInfo, + | EditMessageInfo + | DeleteMessageInfo, threadInfo: ThreadInfo, parentThreadInfo: ?ThreadInfo, markdownRules: ParserRules, @@ -402,7 +404,8 @@ messageInfo.type !== messageTypes.IMAGES && messageInfo.type !== messageTypes.MULTIMEDIA && messageInfo.type !== messageTypes.REACTION && - messageInfo.type !== messageTypes.EDIT_MESSAGE, + messageInfo.type !== messageTypes.EDIT_MESSAGE && + messageInfo.type !== messageTypes.DELETE_MESSAGE, 'messageTitle can only be auto-generated for RobotextMessageInfo', ); return robotextForMessageInfo(messageInfo, threadInfo, parentThreadInfo); @@ -639,7 +642,8 @@ (message.type === messageTypes.REACTION || message.type === messageTypes.EDIT_MESSAGE || message.type === messageTypes.SIDEBAR_SOURCE || - message.type === messageTypes.TOGGLE_PIN) && + message.type === messageTypes.TOGGLE_PIN || + message.type === messageTypes.DELETE_MESSAGE) && !messageSpecs[message.type].canBeSidebarSource ); } diff --git a/lib/shared/messages/delete-message-spec.js b/lib/shared/messages/delete-message-spec.js new file mode 100644 --- /dev/null +++ b/lib/shared/messages/delete-message-spec.js @@ -0,0 +1,66 @@ +// @flow + +import invariant from 'invariant'; + +import type { MessageSpec } from './message-spec.js'; +import { + assertMessageType, + messageTypes, +} from '../../types/message-types-enum.js'; +import type { ClientDBMessageInfo } from '../../types/message-types.js'; +import type { + DeleteMessageData, + DeleteMessageInfo, + RawDeleteMessageInfo, +} from '../../types/messages/delete.js'; +import { rawDeleteMessageInfoValidator } from '../../types/messages/delete.js'; +import type { RelativeUserInfo } from '../../types/user-types.js'; + +export const deleteMessageSpec: MessageSpec< + DeleteMessageData, + RawDeleteMessageInfo, + DeleteMessageInfo, +> = Object.freeze({ + rawMessageInfoFromClientDB( + clientDBMessageInfo: ClientDBMessageInfo, + ): RawDeleteMessageInfo { + const messageType = assertMessageType(parseInt(clientDBMessageInfo.type)); + invariant( + messageType === messageTypes.DELETE_MESSAGE, + 'message must be of type DELETE_MESSAGE', + ); + invariant( + clientDBMessageInfo.content !== undefined && + clientDBMessageInfo.content !== null, + 'content must be defined for DeleteMessage', + ); + const content = JSON.parse(clientDBMessageInfo.content); + + return { + type: messageTypes.DELETE_MESSAGE, + id: clientDBMessageInfo.id, + threadID: clientDBMessageInfo.thread, + time: parseInt(clientDBMessageInfo.time), + creatorID: clientDBMessageInfo.user, + targetMessageID: content.targetMessageID, + }; + }, + + createMessageInfo( + rawMessageInfo: RawDeleteMessageInfo, + creator: RelativeUserInfo, + ): DeleteMessageInfo { + return { + type: messageTypes.DELETE_MESSAGE, + id: rawMessageInfo.id, + threadID: rawMessageInfo.threadID, + creator, + time: rawMessageInfo.time, + targetMessageID: rawMessageInfo.targetMessageID, + }; + }, + + canBeSidebarSource: false, + canBePinned: false, + validator: rawDeleteMessageInfoValidator, +}); diff --git a/lib/shared/messages/message-specs.js b/lib/shared/messages/message-specs.js --- a/lib/shared/messages/message-specs.js +++ b/lib/shared/messages/message-specs.js @@ -8,6 +8,7 @@ import { createSubThreadMessageSpec } from './create-sub-thread-message-spec.js'; import { createThreadMessageSpec } from './create-thread-message-spec.js'; import { deleteEntryMessageSpec } from './delete-entry-message-spec.js'; +import { deleteMessageSpec } from './delete-message-spec.js'; import { editEntryMessageSpec } from './edit-entry-message-spec.js'; import { editMessageSpec } from './edit-message-spec.js'; import { joinThreadMessageSpec } from './join-thread-message-spec.js'; @@ -53,4 +54,5 @@ [messageTypes.EDIT_MESSAGE]: editMessageSpec, [messageTypes.TOGGLE_PIN]: togglePinMessageSpec, [messageTypes.UPDATE_RELATIONSHIP]: updateRelationshipMessageSpec, + [messageTypes.DELETE_MESSAGE]: deleteMessageSpec, }); diff --git a/lib/types/message-types-enum.js b/lib/types/message-types-enum.js --- a/lib/types/message-types-enum.js +++ b/lib/types/message-types-enum.js @@ -37,6 +37,7 @@ EDIT_MESSAGE: 20, TOGGLE_PIN: 21, UPDATE_RELATIONSHIP: 22, + DELETE_MESSAGE: 23, }); export type MessageType = $Values<typeof messageTypes>; export function assertMessageType(ourMessageType: number): MessageType { @@ -63,7 +64,8 @@ ourMessageType === 19 || ourMessageType === 20 || ourMessageType === 21 || - ourMessageType === 22, + ourMessageType === 22 || + ourMessageType === 23, 'number is not MessageType enum', ); return ourMessageType; diff --git a/lib/types/message-types.js b/lib/types/message-types.js --- a/lib/types/message-types.js +++ b/lib/types/message-types.js @@ -58,6 +58,12 @@ type RawDeleteEntryMessageInfo, rawDeleteEntryMessageInfoValidator, } from './messages/delete-entry.js'; +import type { + DeleteMessageData, + DeleteMessageInfo, + RawDeleteMessageInfo, +} from './messages/delete.js'; +import { rawDeleteMessageInfoValidator } from './messages/delete.js'; import { type EditEntryMessageData, type EditEntryMessageInfo, @@ -261,7 +267,8 @@ | ReactionMessageData | EditMessageData | TogglePinMessageData - | UpdateRelationshipMessageData; + | UpdateRelationshipMessageData + | DeleteMessageData; export type MultimediaMessageData = ImagesMessageData | MediaMessageData; @@ -341,13 +348,15 @@ | RawRobotextMessageInfo | RawSidebarSourceMessageInfo | RawReactionMessageInfo - | RawEditMessageInfo; + | RawEditMessageInfo + | RawDeleteMessageInfo; export const rawMessageInfoValidator: TUnion<RawMessageInfo> = t.union([ rawComposableMessageInfoValidator, rawRobotextMessageInfoValidator, rawSidebarSourceMessageInfoValidator, rawReactionMessageInfoValidator, rawEditMessageInfoValidator, + rawDeleteMessageInfoValidator, ]); export type LocallyComposedMessageInfo = @@ -429,7 +438,8 @@ | RobotextMessageInfo | SidebarSourceMessageInfo | ReactionMessageInfo - | EditMessageInfo; + | EditMessageInfo + | DeleteMessageInfo; export type ThreadMessageInfo = { messageIDs: string[], diff --git a/lib/types/messages/delete.js b/lib/types/messages/delete.js new file mode 100644 --- /dev/null +++ b/lib/types/messages/delete.js @@ -0,0 +1,39 @@ +// @flow + +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape, tUserID } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; +import type { RelativeUserInfo } from '../user-types.js'; + +export type DeleteMessageData = { + +type: 23, + +threadID: string, + +creatorID: string, + +time: number, + +targetMessageID: string, +}; + +export type RawDeleteMessageInfo = $ReadOnly<{ + ...DeleteMessageData, + +id: string, +}>; + +export const rawDeleteMessageInfoValidator: TInterface<RawDeleteMessageInfo> = + tShape<RawDeleteMessageInfo>({ + type: tNumber(messageTypes.DELETE_MESSAGE), + threadID: tID, + creatorID: tUserID, + time: t.Number, + targetMessageID: tID, + id: tID, + }); + +export type DeleteMessageInfo = { + +type: 23, + +id: string, + +threadID: string, + +creator: RelativeUserInfo, + +time: number, // millisecond timestamp + +targetMessageID: string, +}; diff --git a/lib/types/messages/unsupported.js b/lib/types/messages/unsupported.js --- a/lib/types/messages/unsupported.js +++ b/lib/types/messages/unsupported.js @@ -6,6 +6,8 @@ rawChangeRoleMessageInfoValidator, type RawChangeRoleMessageInfo, } from './change-role.js'; +import type { RawDeleteMessageInfo } from './delete.js'; +import { rawDeleteMessageInfoValidator } from './delete.js'; import { rawEditMessageInfoValidator, type RawEditMessageInfo, @@ -44,7 +46,8 @@ | RawEditMessageInfo | RawMediaMessageInfo | RawReactionMessageInfo - | RawTogglePinMessageInfo, + | RawTogglePinMessageInfo + | RawDeleteMessageInfo, }; export const rawUnsupportedMessageInfoValidator: TInterface<RawUnsupportedMessageInfo> = @@ -65,6 +68,7 @@ rawMediaMessageInfoValidator, rawReactionMessageInfoValidator, rawTogglePinMessageInfoValidator, + rawDeleteMessageInfoValidator, t.Object, ]), }); @@ -83,5 +87,6 @@ | RawEditMessageInfo | RawMediaMessageInfo | RawReactionMessageInfo - | RawTogglePinMessageInfo, + | RawTogglePinMessageInfo + | RawDeleteMessageInfo, }; diff --git a/lib/types/validation.test.js b/lib/types/validation.test.js --- a/lib/types/validation.test.js +++ b/lib/types/validation.test.js @@ -307,6 +307,14 @@ time: 1682083295820, id: '92943', }, + { + type: messageTypes.DELETE_MESSAGE, + threadID: '86033', + creatorID: '83928', + time: 1682083295820, + targetMessageID: '91607', + id: '92943', + }, ]; describe('message validation', () => {