diff --git a/lib/types/message-types.js b/lib/types/message-types.js index d74971194..d57bbd495 100644 --- a/lib/types/message-types.js +++ b/lib/types/message-types.js @@ -1,606 +1,678 @@ // @flow import invariant from 'invariant'; +import t, { type TUnion, type TInterface } from 'tcomb'; import { type ClientDBMediaInfo } from './media-types.js'; import { messageTypes, type MessageType } from './message-types-enum.js'; -import type { - AddMembersMessageData, - AddMembersMessageInfo, - RawAddMembersMessageInfo, +import { + type AddMembersMessageData, + type AddMembersMessageInfo, + type RawAddMembersMessageInfo, + rawAddMembersMessageInfoValidator, } from './messages/add-members.js'; -import type { - ChangeRoleMessageData, - ChangeRoleMessageInfo, - RawChangeRoleMessageInfo, +import { + type ChangeRoleMessageData, + type ChangeRoleMessageInfo, + type RawChangeRoleMessageInfo, + rawChangeRoleMessageInfoValidator, } from './messages/change-role.js'; -import type { - ChangeSettingsMessageData, - ChangeSettingsMessageInfo, - RawChangeSettingsMessageInfo, +import { + type ChangeSettingsMessageData, + type ChangeSettingsMessageInfo, + type RawChangeSettingsMessageInfo, + rawChangeSettingsMessageInfoValidator, } from './messages/change-settings.js'; -import type { - CreateEntryMessageData, - CreateEntryMessageInfo, - RawCreateEntryMessageInfo, +import { + type CreateEntryMessageData, + type CreateEntryMessageInfo, + type RawCreateEntryMessageInfo, + rawCreateEntryMessageInfoValidator, } from './messages/create-entry.js'; -import type { - CreateSidebarMessageData, - CreateSidebarMessageInfo, - RawCreateSidebarMessageInfo, +import { + type CreateSidebarMessageData, + type CreateSidebarMessageInfo, + type RawCreateSidebarMessageInfo, + rawCreateSidebarMessageInfoValidator, } from './messages/create-sidebar.js'; -import type { - CreateSubthreadMessageData, - CreateSubthreadMessageInfo, - RawCreateSubthreadMessageInfo, +import { + type CreateSubthreadMessageData, + type CreateSubthreadMessageInfo, + type RawCreateSubthreadMessageInfo, + rawCreateSubthreadMessageInfoValidator, } from './messages/create-subthread.js'; -import type { - CreateThreadMessageData, - CreateThreadMessageInfo, - RawCreateThreadMessageInfo, +import { + type CreateThreadMessageData, + type CreateThreadMessageInfo, + type RawCreateThreadMessageInfo, + rawCreateThreadMessageInfoValidator, } from './messages/create-thread.js'; -import type { - DeleteEntryMessageData, - DeleteEntryMessageInfo, - RawDeleteEntryMessageInfo, +import { + type DeleteEntryMessageData, + type DeleteEntryMessageInfo, + type RawDeleteEntryMessageInfo, + rawDeleteEntryMessageInfoValidator, } from './messages/delete-entry.js'; -import type { - EditEntryMessageData, - EditEntryMessageInfo, - RawEditEntryMessageInfo, +import { + type EditEntryMessageData, + type EditEntryMessageInfo, + type RawEditEntryMessageInfo, + rawEditEntryMessageInfoValidator, } from './messages/edit-entry.js'; -import type { - RawEditMessageInfo, - EditMessageData, - EditMessageInfo, +import { + type RawEditMessageInfo, + rawEditMessageInfoValidator, + type EditMessageData, + type EditMessageInfo, } from './messages/edit.js'; -import type { - ImagesMessageData, - ImagesMessageInfo, - RawImagesMessageInfo, +import { + type ImagesMessageData, + type ImagesMessageInfo, + type RawImagesMessageInfo, + rawImagesMessageInfoValidator, } from './messages/images.js'; -import type { - JoinThreadMessageData, - JoinThreadMessageInfo, - RawJoinThreadMessageInfo, +import { + type JoinThreadMessageData, + type JoinThreadMessageInfo, + type RawJoinThreadMessageInfo, + rawJoinThreadMessageInfoValidator, } from './messages/join-thread.js'; -import type { - LeaveThreadMessageData, - LeaveThreadMessageInfo, - RawLeaveThreadMessageInfo, +import { + type LeaveThreadMessageData, + type LeaveThreadMessageInfo, + type RawLeaveThreadMessageInfo, + rawLeaveThreadMessageInfoValidator, } from './messages/leave-thread.js'; -import type { - MediaMessageData, - MediaMessageInfo, - MediaMessageServerDBContent, - RawMediaMessageInfo, +import { + type MediaMessageData, + type MediaMessageInfo, + type MediaMessageServerDBContent, + type RawMediaMessageInfo, + rawMediaMessageInfoValidator, } from './messages/media.js'; -import type { - ReactionMessageData, - RawReactionMessageInfo, - ReactionMessageInfo, +import { + type ReactionMessageData, + type RawReactionMessageInfo, + rawReactionMessageInfoValidator, + type ReactionMessageInfo, } from './messages/reaction.js'; -import type { - RawRemoveMembersMessageInfo, - RemoveMembersMessageData, - RemoveMembersMessageInfo, +import { + type RawRemoveMembersMessageInfo, + rawRemoveMembersMessageInfoValidator, + type RemoveMembersMessageData, + type RemoveMembersMessageInfo, } from './messages/remove-members.js'; -import type { - RawRestoreEntryMessageInfo, - RestoreEntryMessageData, - RestoreEntryMessageInfo, +import { + type RawRestoreEntryMessageInfo, + rawRestoreEntryMessageInfoValidator, + type RestoreEntryMessageData, + type RestoreEntryMessageInfo, } from './messages/restore-entry.js'; -import type { - RawTextMessageInfo, - TextMessageData, - TextMessageInfo, +import { + type RawTextMessageInfo, + rawTextMessageInfoValidator, + type TextMessageData, + type TextMessageInfo, } from './messages/text.js'; -import type { - TogglePinMessageData, - TogglePinMessageInfo, - RawTogglePinMessageInfo, +import { + type TogglePinMessageData, + type TogglePinMessageInfo, + type RawTogglePinMessageInfo, + rawTogglePinMessageInfoValidator, } from './messages/toggle-pin.js'; -import type { - RawUnsupportedMessageInfo, - UnsupportedMessageInfo, +import { + type RawUnsupportedMessageInfo, + rawUnsupportedMessageInfoValidator, + type UnsupportedMessageInfo, } from './messages/unsupported.js'; -import type { - RawUpdateRelationshipMessageInfo, - UpdateRelationshipMessageData, - UpdateRelationshipMessageInfo, +import { + type RawUpdateRelationshipMessageInfo, + rawUpdateRelationshipMessageInfoValidator, + type UpdateRelationshipMessageData, + type UpdateRelationshipMessageInfo, } from './messages/update-relationship.js'; import { type RelativeUserInfo, type UserInfos } from './user-types.js'; import type { CallServerEndpointResultInfoInterface } from '../utils/call-server-endpoint.js'; +import { tNumber, tShape, tID } from '../utils/validation-utils.js'; const composableMessageTypes = new Set([ messageTypes.TEXT, messageTypes.IMAGES, messageTypes.MULTIMEDIA, ]); export function isComposableMessageType(ourMessageType: MessageType): boolean { return composableMessageTypes.has(ourMessageType); } export function assertComposableMessageType( ourMessageType: MessageType, ): MessageType { invariant( isComposableMessageType(ourMessageType), 'MessageType is not composed', ); return ourMessageType; } export function assertComposableRawMessage( message: RawMessageInfo, ): RawComposableMessageInfo { invariant( message.type === messageTypes.TEXT || message.type === messageTypes.IMAGES || message.type === messageTypes.MULTIMEDIA, 'Message is not composable', ); return message; } export function messageDataLocalID(messageData: MessageData): ?string { if ( messageData.type !== messageTypes.TEXT && messageData.type !== messageTypes.IMAGES && messageData.type !== messageTypes.MULTIMEDIA && messageData.type !== messageTypes.REACTION ) { return null; } return messageData.localID; } export function isMessageSidebarSourceReactionOrEdit( message: RawMessageInfo | MessageInfo, ): boolean %checks { return ( message.type === messageTypes.SIDEBAR_SOURCE || message.type === messageTypes.REACTION || message.type === messageTypes.EDIT_MESSAGE ); } const mediaMessageTypes = new Set([ messageTypes.IMAGES, messageTypes.MULTIMEDIA, ]); export function isMediaMessageType(ourMessageType: MessageType): boolean { return mediaMessageTypes.has(ourMessageType); } export function assertMediaMessageType( ourMessageType: MessageType, ): MessageType { invariant(isMediaMessageType(ourMessageType), 'MessageType is not media'); return ourMessageType; } // *MessageData = passed to createMessages function to insert into database // Raw*MessageInfo = used by server, and contained in client's local store // *MessageInfo = used by client in UI code export type SidebarSourceMessageData = { +type: 17, +threadID: string, +creatorID: string, +time: number, +sourceMessage?: RawComposableMessageInfo | RawRobotextMessageInfo, }; export type MessageData = | TextMessageData | CreateThreadMessageData | AddMembersMessageData | CreateSubthreadMessageData | ChangeSettingsMessageData | RemoveMembersMessageData | ChangeRoleMessageData | LeaveThreadMessageData | JoinThreadMessageData | CreateEntryMessageData | EditEntryMessageData | DeleteEntryMessageData | RestoreEntryMessageData | ImagesMessageData | MediaMessageData | UpdateRelationshipMessageData | SidebarSourceMessageData | CreateSidebarMessageData | ReactionMessageData | EditMessageData | TogglePinMessageData; export type MultimediaMessageData = ImagesMessageData | MediaMessageData; export type RawMultimediaMessageInfo = | RawImagesMessageInfo | RawMediaMessageInfo; +const rawMultimediaMessageInfoValidator = t.union([ + rawImagesMessageInfoValidator, + rawMediaMessageInfoValidator, +]); + export type RawComposableMessageInfo = | RawTextMessageInfo | RawMultimediaMessageInfo; +const rawComposableMessageInfoValidator = t.union([ + rawTextMessageInfoValidator, + rawMultimediaMessageInfoValidator, +]); export type RawRobotextMessageInfo = | RawCreateThreadMessageInfo | RawAddMembersMessageInfo | RawCreateSubthreadMessageInfo | RawChangeSettingsMessageInfo | RawRemoveMembersMessageInfo | RawChangeRoleMessageInfo | RawLeaveThreadMessageInfo | RawJoinThreadMessageInfo | RawCreateEntryMessageInfo | RawEditEntryMessageInfo | RawDeleteEntryMessageInfo | RawRestoreEntryMessageInfo | RawUpdateRelationshipMessageInfo | RawCreateSidebarMessageInfo | RawUnsupportedMessageInfo | RawTogglePinMessageInfo; +const rawRobotextMessageInfoValidator = t.union([ + rawCreateThreadMessageInfoValidator, + rawAddMembersMessageInfoValidator, + rawCreateSubthreadMessageInfoValidator, + rawChangeSettingsMessageInfoValidator, + rawRemoveMembersMessageInfoValidator, + rawChangeRoleMessageInfoValidator, + rawLeaveThreadMessageInfoValidator, + rawJoinThreadMessageInfoValidator, + rawCreateEntryMessageInfoValidator, + rawEditEntryMessageInfoValidator, + rawDeleteEntryMessageInfoValidator, + rawRestoreEntryMessageInfoValidator, + rawUpdateRelationshipMessageInfoValidator, + rawCreateSidebarMessageInfoValidator, + rawUnsupportedMessageInfoValidator, + rawTogglePinMessageInfoValidator, +]); + export type RawSidebarSourceMessageInfo = { ...SidebarSourceMessageData, id: string, }; +export const rawSidebarSourceMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.SIDEBAR_SOURCE), + threadID: tID, + creatorID: t.String, + time: t.Number, + sourceMessage: t.maybe( + t.union([ + rawComposableMessageInfoValidator, + rawRobotextMessageInfoValidator, + ]), + ), + id: tID, + }); export type RawMessageInfo = | RawComposableMessageInfo | RawRobotextMessageInfo | RawSidebarSourceMessageInfo | RawReactionMessageInfo | RawEditMessageInfo; +export const rawMessageInfoValidator: TUnion = t.union([ + rawComposableMessageInfoValidator, + rawRobotextMessageInfoValidator, + rawSidebarSourceMessageInfoValidator, + rawReactionMessageInfoValidator, + rawEditMessageInfoValidator, +]); export type LocallyComposedMessageInfo = | ({ ...RawImagesMessageInfo, +localID: string, } & RawImagesMessageInfo) | ({ ...RawMediaMessageInfo, +localID: string, } & RawMediaMessageInfo) | ({ ...RawTextMessageInfo, +localID: string, } & RawTextMessageInfo) | ({ ...RawReactionMessageInfo, +localID: string, } & RawReactionMessageInfo); export type MultimediaMessageInfo = ImagesMessageInfo | MediaMessageInfo; export type ComposableMessageInfo = TextMessageInfo | MultimediaMessageInfo; export type RobotextMessageInfo = | CreateThreadMessageInfo | AddMembersMessageInfo | CreateSubthreadMessageInfo | ChangeSettingsMessageInfo | RemoveMembersMessageInfo | ChangeRoleMessageInfo | LeaveThreadMessageInfo | JoinThreadMessageInfo | CreateEntryMessageInfo | EditEntryMessageInfo | DeleteEntryMessageInfo | RestoreEntryMessageInfo | UnsupportedMessageInfo | UpdateRelationshipMessageInfo | CreateSidebarMessageInfo | TogglePinMessageInfo; export type PreviewableMessageInfo = | RobotextMessageInfo | MultimediaMessageInfo | ReactionMessageInfo; export type SidebarSourceMessageInfo = { +type: 17, +id: string, +threadID: string, +creator: RelativeUserInfo, +time: number, +sourceMessage: ComposableMessageInfo | RobotextMessageInfo, }; export type MessageInfo = | ComposableMessageInfo | RobotextMessageInfo | SidebarSourceMessageInfo | ReactionMessageInfo | EditMessageInfo; export type ThreadMessageInfo = { messageIDs: string[], startReached: boolean, lastNavigatedTo: number, // millisecond timestamp lastPruned: number, // millisecond timestamp }; // Tracks client-local information about a message that hasn't been assigned an // ID by the server yet. As soon as the client gets an ack from the server for // this message, it will clear the LocalMessageInfo. export type LocalMessageInfo = { +sendFailed?: boolean, }; export type MessageStoreThreads = { +[threadID: string]: ThreadMessageInfo, }; export type MessageStore = { +messages: { +[id: string]: RawMessageInfo }, +threads: MessageStoreThreads, +local: { +[id: string]: LocalMessageInfo }, +currentAsOf: number, }; // MessageStore messages ops export type RemoveMessageOperation = { +type: 'remove', +payload: { +ids: $ReadOnlyArray }, }; export type RemoveMessagesForThreadsOperation = { +type: 'remove_messages_for_threads', +payload: { +threadIDs: $ReadOnlyArray }, }; export type ReplaceMessageOperation = { +type: 'replace', +payload: { +id: string, +messageInfo: RawMessageInfo }, }; export type RekeyMessageOperation = { +type: 'rekey', +payload: { +from: string, +to: string }, }; export type RemoveAllMessagesOperation = { +type: 'remove_all', }; // MessageStore threads ops export type ReplaceMessageStoreThreadsOperation = { +type: 'replace_threads', +payload: { +threads: MessageStoreThreads }, }; export type RemoveMessageStoreThreadsOperation = { +type: 'remove_threads', +payload: { +ids: $ReadOnlyArray }, }; export type RemoveMessageStoreAllThreadsOperation = { +type: 'remove_all_threads', }; // We were initially using `number`s` for `thread`, `type`, `future_type`, etc. // However, we ended up changing `thread` to `string` to account for thread IDs // including information about the keyserver (eg 'GENESIS|123') in the future. // // At that point we discussed whether we should switch the remaining `number` // fields to `string`s for consistency and flexibility. We researched whether // there was any performance cost to using `string`s instead of `number`s and // found the differences to be negligible. We also concluded using `string`s // may be safer after considering `jsi::Number` and the various C++ number // representations on the CommCoreModule side. export type ClientDBMessageInfo = { +id: string, +local_id: ?string, +thread: string, +user: string, +type: string, +future_type: ?string, +content: ?string, +time: string, +media_infos: ?$ReadOnlyArray, }; export type ClientDBReplaceMessageOperation = { +type: 'replace', +payload: ClientDBMessageInfo, }; export type ClientDBThreadMessageInfo = { +id: string, +start_reached: string, +last_navigated_to: string, +last_pruned: string, }; export type ClientDBReplaceThreadsOperation = { +type: 'replace_threads', +payload: { +threads: $ReadOnlyArray }, }; export type MessageStoreOperation = | RemoveMessageOperation | ReplaceMessageOperation | RekeyMessageOperation | RemoveMessagesForThreadsOperation | RemoveAllMessagesOperation | ReplaceMessageStoreThreadsOperation | RemoveMessageStoreThreadsOperation | RemoveMessageStoreAllThreadsOperation; export type ClientDBMessageStoreOperation = | RemoveMessageOperation | ClientDBReplaceMessageOperation | RekeyMessageOperation | RemoveMessagesForThreadsOperation | RemoveAllMessagesOperation | ClientDBReplaceThreadsOperation | RemoveMessageStoreThreadsOperation | RemoveMessageStoreAllThreadsOperation; export const messageTruncationStatus = Object.freeze({ // EXHAUSTIVE means we've reached the start of the thread. Either the result // set includes the very first message for that thread, or there is nothing // behind the cursor you queried for. Given that the client only ever issues // ranged queries whose range, when unioned with what is in state, represent // the set of all messages for a given thread, we can guarantee that getting // EXHAUSTIVE means the start has been reached. EXHAUSTIVE: 'exhaustive', // TRUNCATED is rare, and means that the server can't guarantee that the // result set for a given thread is contiguous with what the client has in its // state. If the client can't verify the contiguousness itself, it needs to // replace its Redux store's contents with what it is in this payload. // 1) getMessageInfosSince: Result set for thread is equal to max, and the // truncation status isn't EXHAUSTIVE (ie. doesn't include the very first // message). // 2) getMessageInfos: MessageSelectionCriteria does not specify cursors, the // result set for thread is equal to max, and the truncation status isn't // EXHAUSTIVE. If cursors are specified, we never return truncated, since // the cursor given us guarantees the contiguousness of the result set. // Note that in the reducer, we can guarantee contiguousness if there is any // intersection between messageIDs in the result set and the set currently in // the Redux store. TRUNCATED: 'truncated', // UNCHANGED means the result set is guaranteed to be contiguous with what the // client has in its state, but is not EXHAUSTIVE. Basically, it's anything // that isn't either EXHAUSTIVE or TRUNCATED. UNCHANGED: 'unchanged', }); export type MessageTruncationStatus = $Values; export function assertMessageTruncationStatus( ourMessageTruncationStatus: string, ): MessageTruncationStatus { invariant( ourMessageTruncationStatus === 'truncated' || ourMessageTruncationStatus === 'unchanged' || ourMessageTruncationStatus === 'exhaustive', 'string is not ourMessageTruncationStatus enum', ); return ourMessageTruncationStatus; } export type MessageTruncationStatuses = { [threadID: string]: MessageTruncationStatus, }; export type ThreadCursors = { +[threadID: string]: ?string }; export type MessageSelectionCriteria = { +threadCursors?: ?ThreadCursors, +joinedThreads?: ?boolean, +newerThan?: ?number, }; export type FetchMessageInfosRequest = { +cursors: ThreadCursors, +numberPerThread?: ?number, }; export type FetchMessageInfosResponse = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: UserInfos, }; export type FetchMessageInfosResult = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, }; export type FetchMessageInfosPayload = { +threadID: string, +rawMessageInfos: $ReadOnlyArray, +truncationStatus: MessageTruncationStatus, }; export type MessagesResponse = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +currentAsOf: number, }; export type SimpleMessagesPayload = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, }; export const defaultNumberPerThread = 20; export const defaultMaxMessageAge = 14 * 24 * 60 * 60 * 1000; // 2 weeks export type SendMessageResponse = { +newMessageInfo: RawMessageInfo, }; export type SendMessageResult = { +id: string, +time: number, +interface: CallServerEndpointResultInfoInterface, }; export type SendMessagePayload = { +localID: string, +serverID: string, +threadID: string, +time: number, +interface: CallServerEndpointResultInfoInterface, }; export type SendTextMessageRequest = { +threadID: string, +localID?: string, +text: string, +sidebarCreation?: boolean, }; export type SendMultimediaMessageRequest = // This option is only used for messageTypes.IMAGES | { +threadID: string, +localID: string, +sidebarCreation?: boolean, +mediaIDs: $ReadOnlyArray, } | { +threadID: string, +localID: string, +sidebarCreation?: boolean, +mediaMessageContents: $ReadOnlyArray, }; export type SendReactionMessageRequest = { +threadID: string, +localID?: string, +targetMessageID: string, +reaction: string, +action: 'add_reaction' | 'remove_reaction', }; export type SendEditMessageRequest = { +targetMessageID: string, +text: string, }; export type SendEditMessageResponse = { +newMessageInfos: $ReadOnlyArray, }; export type EditMessagePayload = SendEditMessageResponse; export type SendEditMessageResult = SendEditMessageResponse; export type EditMessageContent = { +text: string, }; // Used for the message info included in log-in type actions export type GenericMessagesResult = { +messageInfos: RawMessageInfo[], +truncationStatus: MessageTruncationStatuses, +watchedIDsAtRequestTime: $ReadOnlyArray, +currentAsOf: number, }; export type SaveMessagesPayload = { +rawMessageInfos: $ReadOnlyArray, +updatesCurrentAsOf: number, }; export type NewMessagesPayload = { +messagesResult: MessagesResponse, }; export type MessageStorePrunePayload = { +threadIDs: $ReadOnlyArray, }; export type FetchPinnedMessagesRequest = { +threadID: string, }; export type FetchPinnedMessagesResult = { +pinnedMessages: $ReadOnlyArray, }; diff --git a/lib/types/messages/add-members.js b/lib/types/messages/add-members.js index 25d60b5a3..ab2e1d6f8 100644 --- a/lib/types/messages/add-members.js +++ b/lib/types/messages/add-members.js @@ -1,25 +1,39 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tShape, tID, tNumber } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type AddMembersMessageData = { type: 2, threadID: string, creatorID: string, time: number, addedUserIDs: string[], }; export type RawAddMembersMessageInfo = { ...AddMembersMessageData, id: string, }; +export const rawAddMembersMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.ADD_MEMBERS), + threadID: tID, + creatorID: t.String, + time: t.Number, + addedUserIDs: t.list(t.String), + id: tID, + }); + export type AddMembersMessageInfo = { type: 2, id: string, threadID: string, creator: RelativeUserInfo, time: number, addedMembers: RelativeUserInfo[], }; diff --git a/lib/types/messages/change-role.js b/lib/types/messages/change-role.js index 0196a6c11..2df53fa02 100644 --- a/lib/types/messages/change-role.js +++ b/lib/types/messages/change-role.js @@ -1,27 +1,42 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type ChangeRoleMessageData = { type: 6, threadID: string, creatorID: string, time: number, userIDs: string[], newRole: string, }; export type RawChangeRoleMessageInfo = { ...ChangeRoleMessageData, id: string, }; +export const rawChangeRoleMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CHANGE_ROLE), + threadID: tID, + creatorID: t.String, + time: t.Number, + userIDs: t.list(t.String), + newRole: tID, + id: tID, + }); + export type ChangeRoleMessageInfo = { type: 6, id: string, threadID: string, creator: RelativeUserInfo, time: number, members: RelativeUserInfo[], newRole: string, }; diff --git a/lib/types/messages/change-settings.js b/lib/types/messages/change-settings.js index 7c926358a..03bd1e954 100644 --- a/lib/types/messages/change-settings.js +++ b/lib/types/messages/change-settings.js @@ -1,27 +1,42 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type ChangeSettingsMessageData = { type: 4, threadID: string, creatorID: string, time: number, field: string, value: string | number, }; export type RawChangeSettingsMessageInfo = { ...ChangeSettingsMessageData, id: string, }; +export const rawChangeSettingsMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CHANGE_SETTINGS), + threadID: tID, + creatorID: t.String, + time: t.Number, + field: t.String, + value: t.union([t.String, t.Number]), + id: tID, + }); + export type ChangeSettingsMessageInfo = { type: 4, id: string, threadID: string, creator: RelativeUserInfo, time: number, field: string, value: string | number, }; diff --git a/lib/types/messages/create-entry.js b/lib/types/messages/create-entry.js index 129562801..fc0d42319 100644 --- a/lib/types/messages/create-entry.js +++ b/lib/types/messages/create-entry.js @@ -1,29 +1,45 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type CreateEntryMessageData = { type: 9, threadID: string, creatorID: string, time: number, entryID: string, date: string, text: string, }; export type RawCreateEntryMessageInfo = { ...CreateEntryMessageData, id: string, }; +export const rawCreateEntryMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CREATE_ENTRY), + threadID: tID, + creatorID: t.String, + time: t.Number, + entryID: tID, + date: t.String, + text: t.String, + id: tID, + }); + export type CreateEntryMessageInfo = { type: 9, id: string, threadID: string, creator: RelativeUserInfo, time: number, entryID: string, date: string, text: string, }; diff --git a/lib/types/messages/create-sidebar.js b/lib/types/messages/create-sidebar.js index fc6c3bc7d..5d474ebde 100644 --- a/lib/types/messages/create-sidebar.js +++ b/lib/types/messages/create-sidebar.js @@ -1,38 +1,58 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { ThreadInfo } from '../thread-types.js'; import type { RelativeUserInfo } from '../user-types.js'; export type CreateSidebarMessageData = { +type: 18, +threadID: string, +creatorID: string, +time: number, +sourceMessageAuthorID: string, +initialThreadState: { +name: ?string, +parentThreadID: string, +color: string, +memberIDs: string[], }, }; export type RawCreateSidebarMessageInfo = { ...CreateSidebarMessageData, id: string, }; +export const rawCreateSidebarMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CREATE_SIDEBAR), + threadID: tID, + creatorID: t.String, + time: t.Number, + sourceMessageAuthorID: t.String, + initialThreadState: tShape({ + name: t.maybe(t.String), + parentThreadID: tID, + color: t.String, + memberIDs: t.list(t.String), + }), + id: tID, + }); + export type CreateSidebarMessageInfo = { +type: 18, +id: string, +threadID: string, +creator: RelativeUserInfo, +time: number, +sourceMessageAuthor: RelativeUserInfo, +initialThreadState: { +name: ?string, +parentThreadInfo: ThreadInfo, +color: string, +otherMembers: RelativeUserInfo[], }, }; diff --git a/lib/types/messages/create-subthread.js b/lib/types/messages/create-subthread.js index 036e843c9..b35a5f685 100644 --- a/lib/types/messages/create-subthread.js +++ b/lib/types/messages/create-subthread.js @@ -1,26 +1,40 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { ThreadInfo } from '../thread-types.js'; import type { RelativeUserInfo } from '../user-types.js'; export type CreateSubthreadMessageData = { type: 3, threadID: string, creatorID: string, time: number, childThreadID: string, }; export type RawCreateSubthreadMessageInfo = { ...CreateSubthreadMessageData, id: string, }; +export const rawCreateSubthreadMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CREATE_SUB_THREAD), + threadID: tID, + creatorID: t.String, + time: t.Number, + childThreadID: tID, + id: tID, + }); + export type CreateSubthreadMessageInfo = { type: 3, id: string, threadID: string, creator: RelativeUserInfo, time: number, childThreadInfo: ThreadInfo, }; diff --git a/lib/types/messages/create-thread.js b/lib/types/messages/create-thread.js index 8943985f1..0b247090b 100644 --- a/lib/types/messages/create-thread.js +++ b/lib/types/messages/create-thread.js @@ -1,38 +1,68 @@ // @flow -import type { ThreadInfo, ThreadType } from '../thread-types.js'; +import t, { type TInterface } from 'tcomb'; + +import { values } from '../../utils/objects.js'; +import { + tID, + tNumber, + tShape, + tNumEnum, +} from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; +import { + threadTypes, + type ThreadInfo, + type ThreadType, +} from '../thread-types.js'; import type { RelativeUserInfo } from '../user-types.js'; export type CreateThreadMessageData = { type: 1, threadID: string, creatorID: string, time: number, initialThreadState: { type: ThreadType, name: ?string, parentThreadID: ?string, color: string, memberIDs: string[], }, }; export type RawCreateThreadMessageInfo = { ...CreateThreadMessageData, id: string, }; +export const rawCreateThreadMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.CREATE_THREAD), + threadID: tID, + creatorID: t.String, + time: t.Number, + initialThreadState: tShape({ + type: tNumEnum(values(threadTypes)), + name: t.maybe(t.String), + parentThreadID: t.maybe(tID), + color: t.String, + memberIDs: t.list(t.String), + }), + id: tID, + }); + export type CreateThreadMessageInfo = { type: 1, id: string, threadID: string, creator: RelativeUserInfo, time: number, initialThreadState: { type: ThreadType, name: ?string, parentThreadInfo: ?ThreadInfo, color: string, otherMembers: RelativeUserInfo[], }, }; diff --git a/lib/types/messages/delete-entry.js b/lib/types/messages/delete-entry.js index 73405b48f..c4664a2e7 100644 --- a/lib/types/messages/delete-entry.js +++ b/lib/types/messages/delete-entry.js @@ -1,29 +1,45 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type DeleteEntryMessageData = { type: 11, threadID: string, creatorID: string, time: number, entryID: string, date: string, text: string, }; export type RawDeleteEntryMessageInfo = { ...DeleteEntryMessageData, id: string, }; +export const rawDeleteEntryMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.DELETE_ENTRY), + threadID: tID, + creatorID: t.String, + time: t.Number, + entryID: tID, + date: t.String, + text: t.String, + id: tID, + }); + export type DeleteEntryMessageInfo = { type: 11, id: string, threadID: string, creator: RelativeUserInfo, time: number, entryID: string, date: string, text: string, }; diff --git a/lib/types/messages/edit-entry.js b/lib/types/messages/edit-entry.js index 1451a9e06..889b2b19e 100644 --- a/lib/types/messages/edit-entry.js +++ b/lib/types/messages/edit-entry.js @@ -1,29 +1,45 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type EditEntryMessageData = { type: 10, threadID: string, creatorID: string, time: number, entryID: string, date: string, text: string, }; export type RawEditEntryMessageInfo = { ...EditEntryMessageData, id: string, }; +export const rawEditEntryMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.EDIT_ENTRY), + threadID: tID, + creatorID: t.String, + time: t.Number, + entryID: tID, + date: t.String, + text: t.String, + id: tID, + }); + export type EditEntryMessageInfo = { type: 10, id: string, threadID: string, creator: RelativeUserInfo, time: number, entryID: string, date: string, text: string, }; diff --git a/lib/types/messages/edit.js b/lib/types/messages/edit.js index c43b5139d..fb3860d14 100644 --- a/lib/types/messages/edit.js +++ b/lib/types/messages/edit.js @@ -1,27 +1,42 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type EditMessageData = { +type: 20, +threadID: string, +creatorID: string, +time: number, +targetMessageID: string, +text: string, }; export type RawEditMessageInfo = { ...EditMessageData, +id: string, }; +export const rawEditMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.EDIT_MESSAGE), + threadID: tID, + creatorID: t.String, + time: t.Number, + targetMessageID: tID, + text: t.String, + id: tID, + }); + export type EditMessageInfo = { +type: 20, +id: string, +threadID: string, +creator: RelativeUserInfo, +time: number, // millisecond timestamp +targetMessageID: string, +text: string, }; diff --git a/lib/types/messages/images.js b/lib/types/messages/images.js index c0f441e24..9492084cf 100644 --- a/lib/types/messages/images.js +++ b/lib/types/messages/images.js @@ -1,33 +1,48 @@ // @flow -import type { Image } from '../media-types.js'; +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { imageValidator, type Image } from '../media-types.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; type ImagesSharedBase = { +type: 14, +localID?: string, // for optimistic creations. included by new clients +threadID: string, +creatorID: string, +time: number, +media: $ReadOnlyArray, }; export type ImagesMessageData = { ...ImagesSharedBase, +sidebarCreation?: boolean, }; export type RawImagesMessageInfo = { ...ImagesSharedBase, +id?: string, // null if local copy without ID yet }; +export const rawImagesMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.IMAGES), + localID: t.maybe(t.String), + threadID: tID, + creatorID: t.String, + time: t.Number, + media: t.list(imageValidator), + id: t.maybe(tID), + }); + export type ImagesMessageInfo = { +type: 14, +id?: string, // null if local copy without ID yet +localID?: string, // for optimistic creations +threadID: string, +creator: RelativeUserInfo, +time: number, // millisecond timestamp +media: $ReadOnlyArray, }; diff --git a/lib/types/messages/join-thread.js b/lib/types/messages/join-thread.js index f51870b7f..6ab83153a 100644 --- a/lib/types/messages/join-thread.js +++ b/lib/types/messages/join-thread.js @@ -1,23 +1,36 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type JoinThreadMessageData = { type: 8, threadID: string, creatorID: string, time: number, }; export type RawJoinThreadMessageInfo = { ...JoinThreadMessageData, id: string, }; +export const rawJoinThreadMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.JOIN_THREAD), + threadID: tID, + creatorID: t.String, + time: t.Number, + id: tID, + }); + export type JoinThreadMessageInfo = { type: 8, id: string, threadID: string, creator: RelativeUserInfo, time: number, }; diff --git a/lib/types/messages/leave-thread.js b/lib/types/messages/leave-thread.js index 1c63e3226..7e4145cfc 100644 --- a/lib/types/messages/leave-thread.js +++ b/lib/types/messages/leave-thread.js @@ -1,23 +1,36 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type LeaveThreadMessageData = { type: 7, threadID: string, creatorID: string, time: number, }; export type RawLeaveThreadMessageInfo = { ...LeaveThreadMessageData, id: string, }; +export const rawLeaveThreadMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.LEAVE_THREAD), + threadID: tID, + creatorID: t.String, + time: t.Number, + id: tID, + }); + export type LeaveThreadMessageInfo = { type: 7, id: string, threadID: string, creator: RelativeUserInfo, time: number, }; diff --git a/lib/types/messages/media.js b/lib/types/messages/media.js index 3398fda18..c7bb281c2 100644 --- a/lib/types/messages/media.js +++ b/lib/types/messages/media.js @@ -1,81 +1,96 @@ // @flow -import type { Media } from '../media-types.js'; +import t, { type TInterface } from 'tcomb'; + +import { tShape, tNumber, tID } from '../../utils/validation-utils.js'; +import { type Media, mediaValidator } from '../media-types.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; type MediaSharedBase = { +type: 15, +localID?: string, // for optimistic creations. included by new clients +threadID: string, +creatorID: string, +time: number, +media: $ReadOnlyArray, }; export type MediaMessageData = { ...MediaSharedBase, +sidebarCreation?: boolean, }; export type RawMediaMessageInfo = { ...MediaSharedBase, +id?: string, // null if local copy without ID yet }; +export const rawMediaMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.MULTIMEDIA), + localID: t.maybe(t.String), + threadID: tID, + creatorID: t.String, + time: t.Number, + media: t.list(mediaValidator), + id: t.maybe(tID), + }); + export type MediaMessageInfo = { +type: 15, +id?: string, // null if local copy without ID yet +localID?: string, // for optimistic creations +threadID: string, +creator: RelativeUserInfo, +time: number, // millisecond timestamp +media: $ReadOnlyArray, }; export type PhotoMessageServerDBContent = { +type: 'photo', +uploadID: string, }; export type VideoMessageServerDBContent = { +type: 'video', +uploadID: string, +thumbnailUploadID: string, }; export type MediaMessageServerDBContent = | PhotoMessageServerDBContent | VideoMessageServerDBContent; function getUploadIDsFromMediaMessageServerDBContents( mediaMessageContents: $ReadOnlyArray, ): $ReadOnlyArray { const uploadIDs: string[] = []; for (const mediaContent of mediaMessageContents) { uploadIDs.push(mediaContent.uploadID); if (mediaContent.type === 'video') { uploadIDs.push(mediaContent.thumbnailUploadID); } } return uploadIDs; } function getMediaMessageServerDBContentsFromMedia( media: $ReadOnlyArray, ): $ReadOnlyArray { return media.map(m => { if (m.type === 'photo' || m.type === 'encrypted_photo') { return { type: 'photo', uploadID: m.id }; } else if (m.type === 'video' || m.type === 'encrypted_video') { return { type: 'video', uploadID: m.id, thumbnailUploadID: m.thumbnailID, }; } throw new Error(`Unexpected media type: ${m.type}`); }); } export { getUploadIDsFromMediaMessageServerDBContents, getMediaMessageServerDBContentsFromMedia, }; diff --git a/lib/types/messages/reaction.js b/lib/types/messages/reaction.js index b154d2246..f069ea860 100644 --- a/lib/types/messages/reaction.js +++ b/lib/types/messages/reaction.js @@ -1,31 +1,48 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type ReactionMessageData = { +type: 19, +localID?: string, // for optimistic creations. included by new clients +threadID: string, +creatorID: string, +time: number, +targetMessageID: string, +reaction: string, +action: 'add_reaction' | 'remove_reaction', }; export type RawReactionMessageInfo = { ...ReactionMessageData, id?: string, // null if local copy without ID yet }; +export const rawReactionMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.REACTION), + localID: t.maybe(t.String), + threadID: tID, + creatorID: t.String, + time: t.Number, + targetMessageID: tID, + reaction: t.String, + action: t.enums.of(['add_reaction', 'remove_reaction']), + id: t.maybe(tID), + }); + export type ReactionMessageInfo = { +type: 19, +id?: string, // null if local copy without ID yet +localID?: string, // for optimistic creations +threadID: string, +creator: RelativeUserInfo, +time: number, +targetMessageID: string, +reaction: string, +action: 'add_reaction' | 'remove_reaction', }; diff --git a/lib/types/messages/remove-members.js b/lib/types/messages/remove-members.js index 56e2da3b4..c87962cf3 100644 --- a/lib/types/messages/remove-members.js +++ b/lib/types/messages/remove-members.js @@ -1,25 +1,39 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type RemoveMembersMessageData = { type: 5, threadID: string, creatorID: string, time: number, removedUserIDs: string[], }; export type RawRemoveMembersMessageInfo = { ...RemoveMembersMessageData, id: string, }; +export const rawRemoveMembersMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.REMOVE_MEMBERS), + threadID: tID, + creatorID: t.String, + time: t.Number, + removedUserIDs: t.list(t.String), + id: tID, + }); + export type RemoveMembersMessageInfo = { type: 5, id: string, threadID: string, creator: RelativeUserInfo, time: number, removedMembers: RelativeUserInfo[], }; diff --git a/lib/types/messages/restore-entry.js b/lib/types/messages/restore-entry.js index 085ed1024..b4479a941 100644 --- a/lib/types/messages/restore-entry.js +++ b/lib/types/messages/restore-entry.js @@ -1,29 +1,45 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type RestoreEntryMessageData = { type: 12, threadID: string, creatorID: string, time: number, entryID: string, date: string, text: string, }; export type RawRestoreEntryMessageInfo = { ...RestoreEntryMessageData, id: string, }; +export const rawRestoreEntryMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.RESTORE_ENTRY), + threadID: tID, + creatorID: t.String, + time: t.Number, + entryID: tID, + date: t.String, + text: t.String, + id: tID, + }); + export type RestoreEntryMessageInfo = { type: 12, id: string, threadID: string, creator: RelativeUserInfo, time: number, entryID: string, date: string, text: string, }; diff --git a/lib/types/messages/text.js b/lib/types/messages/text.js index fcf7ef08e..43ddc6574 100644 --- a/lib/types/messages/text.js +++ b/lib/types/messages/text.js @@ -1,32 +1,47 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; type TextSharedBase = { +type: 0, +localID?: string, // for optimistic creations. included by new clients +threadID: string, +creatorID: string, +time: number, +text: string, }; export type TextMessageData = { ...TextSharedBase, +sidebarCreation?: boolean, }; export type RawTextMessageInfo = { ...TextSharedBase, +id?: string, // null if local copy without ID yet }; +export const rawTextMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.TEXT), + localID: t.maybe(t.String), + threadID: tID, + creatorID: t.String, + time: t.Number, + text: t.String, + id: t.maybe(tID), + }); + export type TextMessageInfo = { +type: 0, +id?: string, // null if local copy without ID yet +localID?: string, // for optimistic creations +threadID: string, +creator: RelativeUserInfo, +time: number, // millisecond timestamp +text: string, }; diff --git a/lib/types/messages/toggle-pin.js b/lib/types/messages/toggle-pin.js index 29c0d24ae..b3ffe7666 100644 --- a/lib/types/messages/toggle-pin.js +++ b/lib/types/messages/toggle-pin.js @@ -1,29 +1,45 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type TogglePinMessageData = { +type: 21, +threadID: string, +targetMessageID: string, +action: 'pin' | 'unpin', +pinnedContent: string, +creatorID: string, +time: number, }; export type RawTogglePinMessageInfo = { ...TogglePinMessageData, +id: string, }; +export const rawTogglePinMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.TOGGLE_PIN), + threadID: tID, + targetMessageID: tID, + action: t.enums.of(['pin', 'unpin']), + pinnedContent: t.String, + creatorID: t.String, + time: t.Number, + id: tID, + }); + export type TogglePinMessageInfo = { +type: 21, +id: string, +threadID: string, +targetMessageID: string, +action: 'pin' | 'unpin', +pinnedContent: string, +creator: RelativeUserInfo, +time: number, }; diff --git a/lib/types/messages/unsupported.js b/lib/types/messages/unsupported.js index 0fe28b5d6..a89053b3a 100644 --- a/lib/types/messages/unsupported.js +++ b/lib/types/messages/unsupported.js @@ -1,25 +1,41 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type RawUnsupportedMessageInfo = { type: 13, id: string, threadID: string, creatorID: string, time: number, robotext: string, dontPrefixCreator?: boolean, unsupportedMessageInfo: Object, }; +export const rawUnsupportedMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.UNSUPPORTED), + id: tID, + threadID: tID, + creatorID: t.String, + time: t.Number, + robotext: t.String, + dontPrefixCreator: t.maybe(t.Boolean), + unsupportedMessageInfo: t.Object, + }); + export type UnsupportedMessageInfo = { type: 13, id: string, threadID: string, creator: RelativeUserInfo, time: number, robotext: string, dontPrefixCreator?: boolean, unsupportedMessageInfo: Object, }; diff --git a/lib/types/messages/update-relationship.js b/lib/types/messages/update-relationship.js index 133d1fffe..43e6d0575 100644 --- a/lib/types/messages/update-relationship.js +++ b/lib/types/messages/update-relationship.js @@ -1,27 +1,42 @@ // @flow +import t, { type TInterface } from 'tcomb'; + +import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; +import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; export type UpdateRelationshipMessageData = { +type: 16, +threadID: string, +creatorID: string, +targetID: string, +time: number, +operation: 'request_sent' | 'request_accepted', }; export type RawUpdateRelationshipMessageInfo = { ...UpdateRelationshipMessageData, id: string, }; +export const rawUpdateRelationshipMessageInfoValidator: TInterface = + tShape({ + type: tNumber(messageTypes.UPDATE_RELATIONSHIP), + threadID: tID, + creatorID: t.String, + targetID: t.String, + time: t.Number, + operation: t.enums.of(['request_sent', 'request_accepted']), + id: tID, + }); + export type UpdateRelationshipMessageInfo = { +type: 16, +id: string, +threadID: string, +creator: RelativeUserInfo, +target: RelativeUserInfo, +time: number, +operation: 'request_sent' | 'request_accepted', }; diff --git a/lib/types/validation.test.js b/lib/types/validation.test.js index 8d6f3e9f0..b16b27203 100644 --- a/lib/types/validation.test.js +++ b/lib/types/validation.test.js @@ -1,44 +1,362 @@ // @flow +import _findKey from 'lodash/fp/findKey.js'; + import { imageValidator, videoValidator, mediaValidator, } from './media-types.js'; +import { messageTypes } from './message-types-enum.js'; +import { rawSidebarSourceMessageInfoValidator } from './message-types.js'; +import { rawAddMembersMessageInfoValidator } from './messages/add-members.js'; +import { rawChangeRoleMessageInfoValidator } from './messages/change-role.js'; +import { rawChangeSettingsMessageInfoValidator } from './messages/change-settings.js'; +import { rawCreateEntryMessageInfoValidator } from './messages/create-entry.js'; +import { rawCreateSidebarMessageInfoValidator } from './messages/create-sidebar.js'; +import { rawCreateSubthreadMessageInfoValidator } from './messages/create-subthread.js'; +import { rawCreateThreadMessageInfoValidator } from './messages/create-thread.js'; +import { rawDeleteEntryMessageInfoValidator } from './messages/delete-entry.js'; +import { rawEditEntryMessageInfoValidator } from './messages/edit-entry.js'; +import { rawEditMessageInfoValidator } from './messages/edit.js'; +import { rawImagesMessageInfoValidator } from './messages/images.js'; +import { rawJoinThreadMessageInfoValidator } from './messages/join-thread.js'; +import { rawLeaveThreadMessageInfoValidator } from './messages/leave-thread.js'; +import { rawMediaMessageInfoValidator } from './messages/media.js'; +import { rawReactionMessageInfoValidator } from './messages/reaction.js'; +import { rawRemoveMembersMessageInfoValidator } from './messages/remove-members.js'; +import { rawRestoreEntryMessageInfoValidator } from './messages/restore-entry.js'; +import { rawTextMessageInfoValidator } from './messages/text.js'; +import { rawTogglePinMessageInfoValidator } from './messages/toggle-pin.js'; +import { rawUnsupportedMessageInfoValidator } from './messages/unsupported.js'; +import { rawUpdateRelationshipMessageInfoValidator } from './messages/update-relationship.js'; describe('media validation', () => { const photo = { id: '92696', type: 'photo', uri: 'http://0.0.0.0:3000/comm/upload/92696/0fb272bd1c75d976', dimensions: { width: 340, height: 288, }, }; const video = { type: 'video', id: '92769', uri: 'http://0.0.0.0:3000/comm/upload/92769/4bcc6987b25b2f66', dimensions: { width: 480, height: 270, }, thumbnailID: '92770', thumbnailURI: 'http://0.0.0.0:3000/comm/upload/92770/d56466051dcef1db', }; it('should validate correct media', () => { expect(mediaValidator.is(photo)).toBe(true); expect(imageValidator.is(photo)).toBe(true); expect(mediaValidator.is(video)).toBe(true); expect(videoValidator.is(video)).toBe(true); }); it('should not validate incorrect media', () => { expect(imageValidator.is(video)).toBe(false); expect(videoValidator.is(photo)).toBe(false); expect(mediaValidator.is({ ...photo, type: undefined })).toBe(false); expect(mediaValidator.is({ ...video, dimensions: undefined })).toBe(false); }); }); + +describe('message validation', () => { + const messages = [ + { + type: messageTypes.TEXT, + threadID: '83859', + creatorID: '83853', + time: 1682077048858, + text: 'text', + localID: 'local1', + id: '92837', + }, + { + type: messageTypes.CREATE_THREAD, + id: '83876', + threadID: '83859', + time: 1673561105839, + creatorID: '83853', + initialThreadState: { + type: 6, + name: null, + parentThreadID: '1', + color: '57697f', + memberIDs: ['256', '83853'], + }, + }, + { + type: messageTypes.ADD_MEMBERS, + id: '4754380', + threadID: '4746046', + time: 1680179819346, + creatorID: '256', + addedUserIDs: ['518252', '1329299', '1559042'], + }, + { + type: messageTypes.CREATE_SUB_THREAD, + threadID: '87111', + creatorID: '83928', + time: 1682083573756, + childThreadID: '92993', + id: '93000', + }, + { + type: messageTypes.CHANGE_SETTINGS, + threadID: '83859', + creatorID: '83853', + time: 1682082984605, + field: 'color', + value: 'b8753d', + id: '92880', + }, + { + type: messageTypes.REMOVE_MEMBERS, + threadID: '92993', + creatorID: '83928', + time: 1682083613415, + removedUserIDs: ['83890'], + id: '93012', + }, + { + type: messageTypes.CHANGE_ROLE, + threadID: '85027', + creatorID: '256', + time: 1632393331694, + userIDs: ['85081'], + newRole: 'role', + id: '85431', + }, + { + type: messageTypes.LEAVE_THREAD, + id: '93027', + threadID: '92993', + time: 1682083651037, + creatorID: '83928', + }, + { + type: messageTypes.JOIN_THREAD, + threadID: '92993', + creatorID: '83928', + time: 1682083678595, + id: '93035', + }, + { + type: messageTypes.CREATE_ENTRY, + threadID: '84695', + creatorID: '83928', + time: 1682083217395, + entryID: '92917', + date: '2023-04-02', + text: 'text', + id: '92920', + }, + { + type: messageTypes.EDIT_ENTRY, + threadID: '84695', + creatorID: '83928', + time: 1682083374471, + entryID: '92917', + date: '2023-04-02', + text: 'text', + id: '92950', + }, + { + type: messageTypes.DELETE_ENTRY, + threadID: '86033', + creatorID: '83928', + time: 1682083220296, + entryID: '92904', + date: '2023-04-02', + text: 'text', + id: '92932', + }, + { + type: messageTypes.RESTORE_ENTRY, + id: '92962', + threadID: '86033', + time: 1682083414244, + creatorID: '83928', + entryID: '92904', + date: '2023-04-02', + text: 'text', + }, + { + type: messageTypes.UNSUPPORTED, + threadID: '87080', + creatorID: '256', + time: 1640733462322, + robotext: 'unsupported message', + unsupportedMessageInfo: { + type: 105, + threadID: '97489', + creatorID: '256', + time: 1640773011289, + id: '97672', + }, + id: '97730', + }, + { + type: messageTypes.IMAGES, + threadID: '92796', + creatorID: '83928', + time: 1682083469079, + media: [ + { + id: '92974', + uri: 'http://0.0.0.0:3000/comm/upload/92974/ff3d02ded71e2762', + type: 'photo', + dimensions: { + width: 220, + height: 220, + }, + }, + ], + localID: 'local0', + id: '92976', + }, + { + type: messageTypes.MULTIMEDIA, + threadID: '89644', + creatorID: '83853', + time: 1682076177257, + media: [ + { + type: 'video', + id: '92769', + uri: 'http://0.0.0.0:3000/comm/upload/92769/4bcc6987b25b2f66', + dimensions: { + width: 480, + height: 270, + }, + thumbnailID: '92770', + thumbnailURI: + 'http://0.0.0.0:3000/comm/upload/92770/d56466051dcef1db', + }, + ], + id: '92771', + }, + { + type: messageTypes.UPDATE_RELATIONSHIP, + threadID: '92796', + creatorID: '83928', + targetID: '83853', + time: 1682083716312, + operation: 'request_sent', + id: '93039', + }, + { + type: messageTypes.SIDEBAR_SOURCE, + threadID: '93044', + creatorID: '83928', + time: 1682083756831, + sourceMessage: { + type: 0, + id: '92816', + threadID: '92796', + time: 1682076737518, + creatorID: '83928', + text: 'text', + }, + id: '93049', + }, + { + type: messageTypes.CREATE_SIDEBAR, + threadID: '93044', + creatorID: '83928', + time: 1682083756831, + sourceMessageAuthorID: '83928', + initialThreadState: { + name: 'text', + parentThreadID: '92796', + color: 'aa4b4b', + memberIDs: ['83853', '83928'], + }, + id: '93050', + }, + { + type: messageTypes.REACTION, + threadID: '86033', + localID: 'local8', + creatorID: '83928', + time: 1682083295820, + targetMessageID: '91607', + reaction: '😂', + action: 'add_reaction', + id: '92943', + }, + { + type: messageTypes.EDIT_MESSAGE, + threadID: '86033', + creatorID: '83928', + time: 1682083295820, + targetMessageID: '91607', + text: 'text', + id: '92943', + }, + { + type: messageTypes.TOGGLE_PIN, + threadID: '86033', + targetMessageID: '91607', + action: 'pin', + pinnedContent: 'text', + creatorID: '83928', + time: 1682083295820, + id: '92943', + }, + ]; + + const validatorByMessageType = { + '0': rawTextMessageInfoValidator, + '1': rawCreateThreadMessageInfoValidator, + '2': rawAddMembersMessageInfoValidator, + '3': rawCreateSubthreadMessageInfoValidator, + '4': rawChangeSettingsMessageInfoValidator, + '5': rawRemoveMembersMessageInfoValidator, + '6': rawChangeRoleMessageInfoValidator, + '7': rawLeaveThreadMessageInfoValidator, + '8': rawJoinThreadMessageInfoValidator, + '9': rawCreateEntryMessageInfoValidator, + '10': rawEditEntryMessageInfoValidator, + '11': rawDeleteEntryMessageInfoValidator, + '12': rawRestoreEntryMessageInfoValidator, + '13': rawUnsupportedMessageInfoValidator, + '14': rawImagesMessageInfoValidator, + '15': rawMediaMessageInfoValidator, + '16': rawUpdateRelationshipMessageInfoValidator, + '17': rawSidebarSourceMessageInfoValidator, + '18': rawCreateSidebarMessageInfoValidator, + '19': rawReactionMessageInfoValidator, + '20': rawEditMessageInfoValidator, + '21': rawTogglePinMessageInfoValidator, + }; + + for (const validatorMessageType in validatorByMessageType) { + const validator = validatorByMessageType[validatorMessageType]; + const validatorMessageTypeName = _findKey( + e => e === Number(validatorMessageType), + )(messageTypes); + + for (const message of messages) { + const messageTypeName = _findKey(e => e === message.type)(messageTypes); + + if (Number(validatorMessageType) === message.type) { + it(`${validatorMessageTypeName} should validate ${messageTypeName}`, () => { + expect(validator.is(message)).toBe(true); + }); + } else { + it(`${validatorMessageTypeName} shouldn't validate ${messageTypeName}`, () => { + expect(validator.is(message)).toBe(false); + }); + } + } + } +});