diff --git a/lib/shared/dm-ops/add-members-spec.js b/lib/shared/dm-ops/add-members-spec.js index 478a1891c..510adb0c4 100644 --- a/lib/shared/dm-ops/add-members-spec.js +++ b/lib/shared/dm-ops/add-members-spec.js @@ -1,126 +1,128 @@ // @flow import invariant from 'invariant'; import uuid from 'uuid'; import { createRoleAndPermissionForThickThreads } from './create-thread-spec.js'; import type { DMOperationSpec, ProcessDMOperationUtilities, } from './dm-op-spec.js'; import { createRepliesCountUpdate } from './dm-op-utils.js'; import type { DMAddMembersOperation } from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; import { type RawMessageInfo } from '../../types/message-types.js'; import { minimallyEncodeMemberInfo, type ThickRawThreadInfo, } from '../../types/minimally-encoded-thread-permissions-types.js'; import { joinThreadSubscription } from '../../types/subscription-types.js'; import type { ThickMemberInfo } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; import { values } from '../../utils/objects.js'; import { roleIsDefaultRole, userIsMember } from '../thread-utils.js'; +export type AddMembersResult = { + rawMessageInfos: Array, + updateInfos: Array, + threadInfo: ?ThickRawThreadInfo, +}; + function createAddNewMembersResults( dmOperation: DMAddMembersOperation, viewerID: string, utilities: ProcessDMOperationUtilities, -): { - +rawMessageInfos: Array, - +updateInfos: Array, - +threadInfo: ?ThickRawThreadInfo, -} { +): AddMembersResult { const { editorID, time, messageID, addedUserIDs, threadID } = dmOperation; const addMembersMessage = { type: messageTypes.ADD_MEMBERS, id: messageID, threadID, creatorID: editorID, time, addedUserIDs: [...addedUserIDs], }; const currentThreadInfo = utilities.threadInfos[threadID]; if (!currentThreadInfo.thick) { return { rawMessageInfos: [], updateInfos: [], threadInfo: null, }; } const defaultRoleID = values(currentThreadInfo.roles).find(role => roleIsDefaultRole(role), )?.id; invariant(defaultRoleID, 'Default role ID must exist'); const { membershipPermissions } = createRoleAndPermissionForThickThreads( currentThreadInfo.type, currentThreadInfo.id, defaultRoleID, ); const newMembers = addedUserIDs .filter(userID => !userIsMember(currentThreadInfo, userID)) .map(userID => minimallyEncodeMemberInfo({ id: userID, role: defaultRoleID, permissions: membershipPermissions, isSender: editorID === viewerID, subscription: joinThreadSubscription, }), ); const resultThreadInfo = { ...currentThreadInfo, members: [...currentThreadInfo.members, ...newMembers], }; const updateWithRepliesCount = createRepliesCountUpdate(resultThreadInfo, [ addMembersMessage, ]); const update = updateWithRepliesCount ?? { type: updateTypes.UPDATE_THREAD, id: uuid.v4(), time, threadInfo: resultThreadInfo, }; return { rawMessageInfos: [addMembersMessage], updateInfos: [update], threadInfo: resultThreadInfo, }; } const addMembersSpec: DMOperationSpec = Object.freeze({ processDMOperation: async ( dmOperation: DMAddMembersOperation, viewerID: string, utilities: ProcessDMOperationUtilities, ) => { const { rawMessageInfos, updateInfos } = createAddNewMembersResults( dmOperation, viewerID, utilities, ); return { rawMessageInfos, updateInfos }; }, canBeProcessed( dmOperation: DMAddMembersOperation, viewerID: string, utilities: ProcessDMOperationUtilities, ) { if (utilities.threadInfos[dmOperation.threadID]) { return { isProcessingPossible: true }; } return { isProcessingPossible: false, reason: { type: 'missing_thread', threadID: dmOperation.threadID, }, }; }, }); export { addMembersSpec, createAddNewMembersResults }; diff --git a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js index 3310e7da8..475e9f509 100644 --- a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js +++ b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js @@ -1,99 +1,92 @@ // @flow import uuid from 'uuid'; +import type { AddMembersResult } from './add-members-spec.js'; import { createThickRawThreadInfo } from './create-thread-spec.js'; import type { DMOperationSpec } from './dm-op-spec.js'; import { createRepliesCountUpdate } from './dm-op-utils.js'; import type { DMAddViewerToThreadMembersOperation } from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; -import { - messageTruncationStatus, - type RawMessageInfo, -} from '../../types/message-types.js'; -import { type ThickRawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; +import { messageTruncationStatus } from '../../types/message-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; function createAddViewerToThreadMembersResults( dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, -): { - +rawMessageInfos: Array, - +updateInfos: Array, - +threadInfo: ?ThickRawThreadInfo, -} { +): AddMembersResult { const { editorID, time, messageID, addedUserIDs, existingThreadDetails } = dmOperation; const addMembersMessage = { type: messageTypes.ADD_MEMBERS, id: messageID, threadID: existingThreadDetails.threadID, creatorID: editorID, time, addedUserIDs: [...addedUserIDs], }; const updateInfos: Array = []; const resultThreadInfo = createThickRawThreadInfo( { ...existingThreadDetails, allMemberIDs: [...existingThreadDetails.allMemberIDs, ...addedUserIDs], }, viewerID, ); updateInfos.push({ type: updateTypes.JOIN_THREAD, id: uuid.v4(), time, threadInfo: resultThreadInfo, rawMessageInfos: [addMembersMessage], truncationStatus: messageTruncationStatus.EXHAUSTIVE, rawEntryInfos: [], }); const repliesCountUpdate = createRepliesCountUpdate(resultThreadInfo, [ addMembersMessage, ]); if ( repliesCountUpdate && repliesCountUpdate.type === updateTypes.UPDATE_THREAD ) { updateInfos.push(repliesCountUpdate); resultThreadInfo.repliesCount = repliesCountUpdate.threadInfo.repliesCount; } return { rawMessageInfos: [], updateInfos, threadInfo: resultThreadInfo, }; } const addViewerToThreadMembersSpec: DMOperationSpec = Object.freeze({ processDMOperation: async ( dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, ) => { const { rawMessageInfos, updateInfos } = createAddViewerToThreadMembersResults(dmOperation, viewerID); return { rawMessageInfos, updateInfos }; }, canBeProcessed( dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, ) { if (dmOperation.addedUserIDs.includes(viewerID)) { return { isProcessingPossible: true }; } console.log('Invalid DM operation', dmOperation); return { isProcessingPossible: false, reason: { type: 'invalid', }, }; }, }); export { addViewerToThreadMembersSpec, createAddViewerToThreadMembersResults }; diff --git a/lib/shared/dm-ops/change-thread-settings-and-add-viewer-spec.js b/lib/shared/dm-ops/change-thread-settings-and-add-viewer-spec.js new file mode 100644 index 000000000..9c1dfa11a --- /dev/null +++ b/lib/shared/dm-ops/change-thread-settings-and-add-viewer-spec.js @@ -0,0 +1,77 @@ +// @flow + +import { + addViewerToThreadMembersSpec, + createAddViewerToThreadMembersResults, +} from './add-viewer-to-thread-members-spec.js'; +import { processChangeSettingsOperation } from './change-thread-settings-spec.js'; +import type { + DMOperationSpec, + ProcessDMOperationUtilities, +} from './dm-op-spec.js'; +import type { DMChangeThreadSettingsAndAddViewerOperation } from '../../types/dm-ops.js'; + +function createAddViewerAndMembersOperation( + dmOperation: DMChangeThreadSettingsAndAddViewerOperation, +) { + const { editorID, time, messageIDsPrefix, changes, existingThreadDetails } = + dmOperation; + const newMemberIDs = + changes.newMemberIDs && changes.newMemberIDs.length > 0 + ? [...new Set(changes.newMemberIDs)] + : []; + return { + type: 'add_viewer_to_thread_members', + editorID, + time, + messageID: `${messageIDsPrefix}/add_members`, + addedUserIDs: newMemberIDs, + existingThreadDetails, + }; +} + +function processAddViewerToThreadMembersOperation( + dmOperation: DMChangeThreadSettingsAndAddViewerOperation, + viewerID: string, +) { + const operation = createAddViewerAndMembersOperation(dmOperation); + if (operation.addedUserIDs.length === 0) { + return null; + } + return createAddViewerToThreadMembersResults(operation, viewerID); +} + +const changeThreadSettingsAndAddViewerSpec: DMOperationSpec = + Object.freeze({ + processDMOperation: async ( + dmOperation: DMChangeThreadSettingsAndAddViewerOperation, + viewerID: string, + utilities: ProcessDMOperationUtilities, + ) => { + const addMembersResult = processAddViewerToThreadMembersOperation( + dmOperation, + viewerID, + ); + + return processChangeSettingsOperation( + dmOperation, + viewerID, + utilities, + addMembersResult, + ); + }, + canBeProcessed( + dmOperation: DMChangeThreadSettingsAndAddViewerOperation, + viewerID: string, + utilities: ProcessDMOperationUtilities, + ) { + const operation = createAddViewerAndMembersOperation(dmOperation); + return addViewerToThreadMembersSpec.canBeProcessed( + operation, + viewerID, + utilities, + ); + }, + }); + +export { changeThreadSettingsAndAddViewerSpec }; diff --git a/lib/shared/dm-ops/change-thread-settings-spec.js b/lib/shared/dm-ops/change-thread-settings-spec.js index e34274979..0b3f161b8 100644 --- a/lib/shared/dm-ops/change-thread-settings-spec.js +++ b/lib/shared/dm-ops/change-thread-settings-spec.js @@ -1,199 +1,188 @@ // @flow import invariant from 'invariant'; import uuid from 'uuid'; -import { createAddNewMembersResults } from './add-members-spec.js'; import { - addViewerToThreadMembersSpec, - createAddViewerToThreadMembersResults, -} from './add-viewer-to-thread-members-spec.js'; + type AddMembersResult, + createAddNewMembersResults, +} from './add-members-spec.js'; import type { DMOperationSpec, ProcessDMOperationUtilities, } from './dm-op-spec.js'; import { createRepliesCountUpdate } from './dm-op-utils.js'; -import type { DMChangeThreadSettingsOperation } from '../../types/dm-ops.js'; +import type { + DMChangeThreadSettingsAndAddViewerOperation, + DMChangeThreadSettingsOperation, + DMOperationResult, +} from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { RawMessageInfo } from '../../types/message-types.js'; import type { RawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; import type { LegacyRawThreadInfo } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; import { values } from '../../utils/objects.js'; -function createAddMembersOperation( +function processAddMembersOperation( dmOperation: DMChangeThreadSettingsOperation, viewerID: string, + utilities: ProcessDMOperationUtilities, ) { - const { editorID, time, messageIDsPrefix, changes, existingThreadDetails } = - dmOperation; + const { editorID, time, messageIDsPrefix, changes, threadID } = dmOperation; const newMemberIDs = changes.newMemberIDs && changes.newMemberIDs.length > 0 ? [...new Set(changes.newMemberIDs)] : []; - if (newMemberIDs.includes(viewerID)) { - return { - type: 'add_viewer_to_thread_members', - editorID, - time, - messageID: `${messageIDsPrefix}/add_members`, - addedUserIDs: newMemberIDs, - existingThreadDetails, - }; + if (!changes.newMemberIDs || changes.newMemberIDs.length === 0) { + return null; } - return { + const operation = { type: 'add_members', editorID, time, messageID: `${messageIDsPrefix}/add_members`, addedUserIDs: newMemberIDs, - threadID: existingThreadDetails.threadID, + threadID, }; + return createAddNewMembersResults(operation, viewerID, utilities); } -function processAddMembersOperation( - dmOperation: DMChangeThreadSettingsOperation, +function processChangeSettingsOperation( + dmOperation: + | DMChangeThreadSettingsOperation + | DMChangeThreadSettingsAndAddViewerOperation, viewerID: string, utilities: ProcessDMOperationUtilities, -) { - const operation = createAddMembersOperation(dmOperation, viewerID); - if (operation.type === 'add_viewer_to_thread_members') { - return createAddViewerToThreadMembersResults(operation, viewerID); - } else { - return createAddNewMembersResults(operation, viewerID, utilities); + addMembersResult: ?AddMembersResult, +): DMOperationResult { + const { editorID, time, changes, messageIDsPrefix } = dmOperation; + const { name, description, color, avatar } = changes; + const threadID = + dmOperation.type === 'change_thread_settings' + ? dmOperation.threadID + : dmOperation.existingThreadDetails.threadID; + + let threadInfoToUpdate: ?(RawThreadInfo | LegacyRawThreadInfo) = + utilities.threadInfos[threadID]; + const updateInfos: Array = []; + const rawMessageInfos: Array = []; + + if (addMembersResult) { + if (addMembersResult.threadInfo) { + threadInfoToUpdate = addMembersResult.threadInfo; + } + updateInfos.push(...addMembersResult.updateInfos); + rawMessageInfos.push(...addMembersResult.rawMessageInfos); } -} -const changeThreadSettingsSpec: DMOperationSpec = - Object.freeze({ - processDMOperation: async ( - dmOperation: DMChangeThreadSettingsOperation, - viewerID: string, - utilities: ProcessDMOperationUtilities, - ) => { - const { - editorID, - time, - changes, - messageIDsPrefix, - existingThreadDetails, - } = dmOperation; - const { name, description, color, avatar } = changes; - const threadID = existingThreadDetails.threadID; - - let threadInfoToUpdate: ?(RawThreadInfo | LegacyRawThreadInfo) = - utilities.threadInfos[threadID]; - const updateInfos: Array = []; - const rawMessageInfos: Array = []; - - if (changes.newMemberIDs && changes.newMemberIDs.length > 0) { - const addMembersResult = processAddMembersOperation( - dmOperation, - viewerID, - utilities, - ); - if (addMembersResult.threadInfo) { - threadInfoToUpdate = addMembersResult.threadInfo; - } - updateInfos.push(...addMembersResult.updateInfos); - rawMessageInfos.push(...addMembersResult.rawMessageInfos); - } + invariant(threadInfoToUpdate?.thick, 'Thread should be thick'); - invariant(threadInfoToUpdate?.thick, 'Thread should be thick'); + const changedFields: { [string]: string | number } = {}; - const changedFields: { [string]: string | number } = {}; + if (name !== undefined && name !== null) { + changedFields.name = name; + threadInfoToUpdate = { + ...threadInfoToUpdate, + name, + }; + } - if (name !== undefined && name !== null) { - changedFields.name = name; - threadInfoToUpdate = { - ...threadInfoToUpdate, - name, - }; - } + if (description !== undefined && description !== null) { + changedFields.description = description; + threadInfoToUpdate = { + ...threadInfoToUpdate, + description, + }; + } - if (description !== undefined && description !== null) { - changedFields.description = description; - threadInfoToUpdate = { - ...threadInfoToUpdate, - description, - }; - } + if (color) { + changedFields.color = color; + threadInfoToUpdate = { + ...threadInfoToUpdate, + color, + }; + } - if (color) { - changedFields.color = color; - threadInfoToUpdate = { - ...threadInfoToUpdate, - color, - }; - } + if (avatar) { + changedFields.avatar = JSON.stringify(avatar); + threadInfoToUpdate = { + ...threadInfoToUpdate, + avatar, + }; + } - if (avatar) { - changedFields.avatar = JSON.stringify(avatar); - threadInfoToUpdate = { - ...threadInfoToUpdate, - avatar, - }; - } + for (const fieldName in changedFields) { + const newValue = changedFields[fieldName]; + rawMessageInfos.push({ + type: messageTypes.CHANGE_SETTINGS, + threadID, + creatorID: editorID, + time, + field: fieldName, + value: newValue, + id: `${messageIDsPrefix}/${fieldName}`, + }); + } - for (const fieldName in changedFields) { - const newValue = changedFields[fieldName]; - rawMessageInfos.push({ - type: messageTypes.CHANGE_SETTINGS, - threadID, - creatorID: editorID, - time, - field: fieldName, - value: newValue, - id: `${messageIDsPrefix}/${fieldName}`, - }); - } + const repliesCountUpdate = createRepliesCountUpdate( + threadInfoToUpdate, + rawMessageInfos, + ); + if (repliesCountUpdate) { + updateInfos.push(repliesCountUpdate); + } else if (values(changedFields).length > 0) { + updateInfos.push({ + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time, + threadInfo: threadInfoToUpdate, + }); + } - const repliesCountUpdate = createRepliesCountUpdate( - threadInfoToUpdate, - rawMessageInfos, + return { + rawMessageInfos, + updateInfos, + }; +} + +const changeThreadSettingsSpec: DMOperationSpec = + Object.freeze({ + processDMOperation: async ( + dmOperation: DMChangeThreadSettingsOperation, + viewerID: string, + utilities: ProcessDMOperationUtilities, + ) => { + const addMembersResult = processAddMembersOperation( + dmOperation, + viewerID, + utilities, ); - if (repliesCountUpdate) { - updateInfos.push(repliesCountUpdate); - } else if (values(changedFields).length > 0) { - updateInfos.push({ - type: updateTypes.UPDATE_THREAD, - id: uuid.v4(), - time, - threadInfo: threadInfoToUpdate, - }); - } - return { - rawMessageInfos, - updateInfos, - }; + return processChangeSettingsOperation( + dmOperation, + viewerID, + utilities, + addMembersResult, + ); }, canBeProcessed( dmOperation: DMChangeThreadSettingsOperation, viewerID: string, utilities: ProcessDMOperationUtilities, ) { - const operation = createAddMembersOperation(dmOperation, viewerID); - if (operation.type === 'add_viewer_to_thread_members') { - return addViewerToThreadMembersSpec.canBeProcessed( - operation, - viewerID, - utilities, - ); - } else if ( - utilities.threadInfos[dmOperation.existingThreadDetails.threadID] - ) { + if (utilities.threadInfos[dmOperation.threadID]) { return { isProcessingPossible: true }; } return { isProcessingPossible: false, reason: { type: 'missing_thread', - threadID: dmOperation.existingThreadDetails.threadID, + threadID: dmOperation.threadID, }, }; }, }); -export { changeThreadSettingsSpec }; +export { changeThreadSettingsSpec, processChangeSettingsOperation }; diff --git a/lib/shared/dm-ops/dm-op-specs.js b/lib/shared/dm-ops/dm-op-specs.js index e40898dcb..f7ddc1b57 100644 --- a/lib/shared/dm-ops/dm-op-specs.js +++ b/lib/shared/dm-ops/dm-op-specs.js @@ -1,31 +1,34 @@ // @flow import { addMembersSpec } from './add-members-spec.js'; import { addViewerToThreadMembersSpec } from './add-viewer-to-thread-members-spec.js'; +import { changeThreadSettingsAndAddViewerSpec } from './change-thread-settings-and-add-viewer-spec.js'; import { changeThreadSettingsSpec } from './change-thread-settings-spec.js'; import { createSidebarSpec } from './create-sidebar-spec.js'; import { createThreadSpec } from './create-thread-spec.js'; import type { DMOperationSpec } from './dm-op-spec.js'; import { joinThreadSpec } from './join-thread-spec.js'; import { leaveThreadSpec } from './leave-thread-spec.js'; import { removeMembersSpec } from './remove-members-spec.js'; import { sendEditMessageSpec } from './send-edit-message-spec.js'; import { sendReactionMessageSpec } from './send-reaction-message-spec.js'; import { sendTextMessageSpec } from './send-text-message-spec.js'; import { type DMOperationType, dmOperationTypes } from '../../types/dm-ops.js'; export const dmOpSpecs: { +[DMOperationType]: DMOperationSpec, } = Object.freeze({ [dmOperationTypes.CREATE_THREAD]: createThreadSpec, [dmOperationTypes.CREATE_SIDEBAR]: createSidebarSpec, [dmOperationTypes.SEND_TEXT_MESSAGE]: sendTextMessageSpec, [dmOperationTypes.SEND_REACTION_MESSAGE]: sendReactionMessageSpec, [dmOperationTypes.SEND_EDIT_MESSAGE]: sendEditMessageSpec, [dmOperationTypes.ADD_MEMBERS]: addMembersSpec, [dmOperationTypes.ADD_VIEWER_TO_THREAD_MEMBERS]: addViewerToThreadMembersSpec, [dmOperationTypes.JOIN_THREAD]: joinThreadSpec, [dmOperationTypes.LEAVE_THREAD]: leaveThreadSpec, [dmOperationTypes.REMOVE_MEMBERS]: removeMembersSpec, [dmOperationTypes.CHANGE_THREAD_SETTINGS]: changeThreadSettingsSpec, + [dmOperationTypes.CHANGE_THREAD_SETTINGS_AND_ADD_VIEWER]: + changeThreadSettingsAndAddViewerSpec, }); diff --git a/lib/types/dm-ops.js b/lib/types/dm-ops.js index 027958005..a942dc26f 100644 --- a/lib/types/dm-ops.js +++ b/lib/types/dm-ops.js @@ -1,362 +1,386 @@ // @flow -import t, { type TInterface, type TUnion } from 'tcomb'; +import t, { type TInterface, type TUnion, type TStructProps } from 'tcomb'; import { clientAvatarValidator, type ClientAvatar } from './avatar-types.js'; import type { RawMessageInfo } from './message-types.js'; import type { OutboundP2PMessage } from './sqlite-types.js'; import { type NonSidebarThickThreadType, nonSidebarThickThreadTypes, type ThickThreadType, thickThreadTypeValidator, } from './thread-types-enum.js'; import type { ClientUpdateInfo } from './update-types.js'; import { values } from '../utils/objects.js'; import { tColor, tShape, tString, tUserID } from '../utils/validation-utils.js'; export const dmOperationTypes = Object.freeze({ CREATE_THREAD: 'create_thread', CREATE_SIDEBAR: 'create_sidebar', SEND_TEXT_MESSAGE: 'send_text_message', SEND_REACTION_MESSAGE: 'send_reaction_message', SEND_EDIT_MESSAGE: 'send_edit_message', ADD_MEMBERS: 'add_members', ADD_VIEWER_TO_THREAD_MEMBERS: 'add_viewer_to_thread_members', JOIN_THREAD: 'join_thread', LEAVE_THREAD: 'leave_thread', REMOVE_MEMBERS: 'remove_members', CHANGE_THREAD_SETTINGS: 'change_thread_settings', + CHANGE_THREAD_SETTINGS_AND_ADD_VIEWER: + 'change_thread_settings_and_add_viewer', }); export type DMOperationType = $Values; export type CreateThickRawThreadInfoInput = { +threadID: string, +threadType: ThickThreadType, +creationTime: number, +parentThreadID?: ?string, +allMemberIDs: $ReadOnlyArray, +roleID: string, +creatorID: string, +name?: ?string, +avatar?: ?ClientAvatar, +description?: ?string, +color?: ?string, +containingThreadID?: ?string, +sourceMessageID?: ?string, +repliesCount?: ?number, +pinnedCount?: ?number, }; export const createThickRawThreadInfoInputValidator: TInterface = tShape({ threadID: t.String, threadType: thickThreadTypeValidator, creationTime: t.Number, parentThreadID: t.maybe(t.String), allMemberIDs: t.list(tUserID), roleID: t.String, creatorID: tUserID, name: t.maybe(t.String), avatar: t.maybe(clientAvatarValidator), description: t.maybe(t.String), color: t.maybe(t.String), containingThreadID: t.maybe(t.String), sourceMessageID: t.maybe(t.String), repliesCount: t.maybe(t.Number), pinnedCount: t.maybe(t.Number), }); export type DMCreateThreadOperation = { +type: 'create_thread', +threadID: string, +creatorID: string, +time: number, +threadType: NonSidebarThickThreadType, +memberIDs: $ReadOnlyArray, +roleID: string, +newMessageID: string, }; export const dmCreateThreadOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.CREATE_THREAD), threadID: t.String, creatorID: tUserID, time: t.Number, threadType: t.enums.of(values(nonSidebarThickThreadTypes)), memberIDs: t.list(tUserID), roleID: t.String, newMessageID: t.String, }); export type DMCreateSidebarOperation = { +type: 'create_sidebar', +threadID: string, +creatorID: string, +time: number, +parentThreadID: string, +memberIDs: $ReadOnlyArray, +sourceMessageID: string, +roleID: string, +newSidebarSourceMessageID: string, +newCreateSidebarMessageID: string, }; export const dmCreateSidebarOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.CREATE_SIDEBAR), threadID: t.String, creatorID: tUserID, time: t.Number, parentThreadID: t.String, memberIDs: t.list(tUserID), sourceMessageID: t.String, roleID: t.String, newSidebarSourceMessageID: t.String, newCreateSidebarMessageID: t.String, }); export type DMSendTextMessageOperation = { +type: 'send_text_message', +threadID: string, +creatorID: string, +time: number, +messageID: string, +text: string, }; export const dmSendTextMessageOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.SEND_TEXT_MESSAGE), threadID: t.String, creatorID: tUserID, time: t.Number, messageID: t.String, text: t.String, }); export type DMSendReactionMessageOperation = { +type: 'send_reaction_message', +threadID: string, +creatorID: string, +time: number, +messageID: string, +targetMessageID: string, +reaction: string, +action: 'add_reaction' | 'remove_reaction', }; export const dmSendReactionMessageOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.SEND_REACTION_MESSAGE), threadID: t.String, creatorID: tUserID, time: t.Number, messageID: t.String, targetMessageID: t.String, reaction: t.String, action: t.enums.of(['add_reaction', 'remove_reaction']), }); export type DMSendEditMessageOperation = { +type: 'send_edit_message', +threadID: string, +creatorID: string, +time: number, +messageID: string, +targetMessageID: string, +text: string, }; export const dmSendEditMessageOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.SEND_EDIT_MESSAGE), threadID: t.String, creatorID: tUserID, time: t.Number, messageID: t.String, targetMessageID: t.String, text: t.String, }); type DMAddMembersBase = { +editorID: string, +time: number, +messageID: string, +addedUserIDs: $ReadOnlyArray, }; const dmAddMembersBaseValidatorShape = { editorID: tUserID, time: t.Number, messageID: t.String, addedUserIDs: t.list(tUserID), }; export type DMAddMembersOperation = $ReadOnly<{ +type: 'add_members', +threadID: string, ...DMAddMembersBase, }>; export const dmAddMembersOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.ADD_MEMBERS), threadID: t.String, ...dmAddMembersBaseValidatorShape, }); export type DMAddViewerToThreadMembersOperation = $ReadOnly<{ +type: 'add_viewer_to_thread_members', +existingThreadDetails: CreateThickRawThreadInfoInput, ...DMAddMembersBase, }>; export const dmAddViewerToThreadMembersValidator: TInterface = tShape({ type: tString(dmOperationTypes.ADD_VIEWER_TO_THREAD_MEMBERS), existingThreadDetails: createThickRawThreadInfoInputValidator, ...dmAddMembersBaseValidatorShape, }); export type DMJoinThreadOperation = { +type: 'join_thread', +joinerID: string, +time: number, +messageID: string, +existingThreadDetails: CreateThickRawThreadInfoInput, }; export const dmJoinThreadOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.JOIN_THREAD), joinerID: tUserID, time: t.Number, messageID: t.String, existingThreadDetails: createThickRawThreadInfoInputValidator, }); export type DMLeaveThreadOperation = { +type: 'leave_thread', +editorID: string, +time: number, +messageID: string, +threadID: string, }; export const dmLeaveThreadOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.LEAVE_THREAD), editorID: tUserID, time: t.Number, messageID: t.String, threadID: t.String, }); export type DMRemoveMembersOperation = { +type: 'remove_members', +editorID: string, +time: number, +messageID: string, +threadID: string, +removedUserIDs: $ReadOnlyArray, }; export const dmRemoveMembersOperationValidator: TInterface = tShape({ type: tString(dmOperationTypes.REMOVE_MEMBERS), editorID: tUserID, time: t.Number, messageID: t.String, threadID: t.String, removedUserIDs: t.list(tUserID), }); -export type DMChangeThreadSettingsOperation = { - +type: 'change_thread_settings', +type DMChangeThreadSettingsBase = { +editorID: string, +time: number, +changes: { +name?: string, +description?: string, +color?: string, +newMemberIDs?: $ReadOnlyArray, +avatar?: ClientAvatar, }, +messageIDsPrefix: string, - +existingThreadDetails: CreateThickRawThreadInfoInput, }; -export const dmChangeThreadSettingsOperationValidator: TInterface = - tShape({ - type: tString(dmOperationTypes.CHANGE_THREAD_SETTINGS), +const dmChangeThreadSettingsBaseValidatorShape: TStructProps = + { editorID: tUserID, time: t.Number, changes: tShape({ name: t.maybe(t.String), description: t.maybe(t.String), color: t.maybe(tColor), newMemberIDs: t.maybe(t.list(tUserID)), avatar: t.maybe(clientAvatarValidator), }), messageIDsPrefix: t.String, + }; + +export type DMChangeThreadSettingsOperation = $ReadOnly<{ + +type: 'change_thread_settings', + +threadID: string, + ...DMChangeThreadSettingsBase, +}>; +export const dmChangeThreadSettingsOperationValidator: TInterface = + tShape({ + type: tString(dmOperationTypes.CHANGE_THREAD_SETTINGS), + threadID: t.String, + ...dmChangeThreadSettingsBaseValidatorShape, + }); + +export type DMChangeThreadSettingsAndAddViewerOperation = $ReadOnly<{ + +type: 'change_thread_settings_and_add_viewer', + +existingThreadDetails: CreateThickRawThreadInfoInput, + ...DMChangeThreadSettingsBase, +}>; +export const dmChangeThreadSettingsAndAddViewerOperationValidator: TInterface = + tShape({ + type: tString(dmOperationTypes.CHANGE_THREAD_SETTINGS_AND_ADD_VIEWER), existingThreadDetails: createThickRawThreadInfoInputValidator, + ...dmChangeThreadSettingsBaseValidatorShape, }); export type DMOperation = | DMCreateThreadOperation | DMCreateSidebarOperation | DMSendTextMessageOperation | DMSendReactionMessageOperation | DMSendEditMessageOperation | DMAddMembersOperation | DMAddViewerToThreadMembersOperation | DMJoinThreadOperation | DMLeaveThreadOperation | DMRemoveMembersOperation - | DMChangeThreadSettingsOperation; + | DMChangeThreadSettingsOperation + | DMChangeThreadSettingsAndAddViewerOperation; export const dmOperationValidator: TUnion = t.union([ dmCreateThreadOperationValidator, dmCreateSidebarOperationValidator, dmSendTextMessageOperationValidator, dmSendReactionMessageOperationValidator, dmSendEditMessageOperationValidator, dmAddMembersOperationValidator, dmAddViewerToThreadMembersValidator, dmJoinThreadOperationValidator, dmLeaveThreadOperationValidator, dmRemoveMembersOperationValidator, dmChangeThreadSettingsOperationValidator, + dmChangeThreadSettingsAndAddViewerOperationValidator, ]); export type DMOperationResult = { rawMessageInfos: Array, updateInfos: Array, }; export const processDMOpsActionType = 'PROCESS_DM_OPS'; export type ProcessDMOpsPayload = { +rawMessageInfos: $ReadOnlyArray, +updateInfos: $ReadOnlyArray, }; export const queueDMOpsActionType = 'QUEUE_DM_OPS'; export type QueueDMOpsPayload = { +operation: DMOperation, +threadID: string, +timestamp: number, }; export const pruneDMOpsQueueActionType = 'PRUNE_DM_OPS_QUEUE'; export type PruneDMOpsQueuePayload = { +pruneMaxTimestamp: number, }; export const scheduleP2PMessagesActionType = 'SCHEDULE_P2P_MESSAGES'; export type ScheduleP2PMessagesPayload = { +dmOpID: string, +messages: $ReadOnlyArray, }; export const clearQueuedThreadDMOpsActionType = 'CLEAR_QUEUED_THREAD_DM_OPS'; export type ClearQueuedThreadDMOpsPayload = { +threadID: string, }; export type QueuedDMOperations = { +operations: { +[threadID: string]: $ReadOnlyArray<{ +operation: DMOperation, +timestamp: number, }>, }, };