diff --git a/lib/shared/dm-ops/dm-op-specs.js b/lib/shared/dm-ops/dm-op-specs.js --- a/lib/shared/dm-ops/dm-op-specs.js +++ b/lib/shared/dm-ops/dm-op-specs.js @@ -4,6 +4,7 @@ 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 { sendEditMessageSpec } from './send-edit-message-spec.js'; import { sendReactionMessageSpec } from './send-reaction-message-spec.js'; import { sendTextMessageSpec } from './send-text-message-spec.js'; @@ -18,4 +19,5 @@ [dmOperationTypes.SEND_REACTION_MESSAGE]: sendReactionMessageSpec, [dmOperationTypes.SEND_EDIT_MESSAGE]: sendEditMessageSpec, [dmOperationTypes.ADD_MEMBERS]: addMembersSpec, + [dmOperationTypes.JOIN_THREAD]: joinThreadSpec, }); diff --git a/lib/shared/dm-ops/join-thread-spec.js b/lib/shared/dm-ops/join-thread-spec.js new file mode 100644 --- /dev/null +++ b/lib/shared/dm-ops/join-thread-spec.js @@ -0,0 +1,112 @@ +// @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 type { DMJoinThreadOperation } from '../../types/dm-ops.js'; +import { messageTypes } from '../../types/message-types-enum.js'; +import { minimallyEncodeMemberInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; +import 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'; + +const joinThreadSpec: DMOperationSpec = Object.freeze({ + processDMOperation: async ( + dmOperation: DMJoinThreadOperation, + viewerID: string, + utilities: ProcessDMOperationUtilities, + ) => { + const { + editorID, + time, + messageID, + threadInfo, + rawMessageInfos, + truncationStatus, + rawEntryInfos, + } = dmOperation; + + const currentThreadInfoOptional = utilities.getThreadInfo(threadInfo.id); + if (userIsMember(currentThreadInfoOptional, editorID)) { + return { + rawMessageInfos: [], + updateInfos: [], + }; + } + + const joinThreadMessage = { + type: messageTypes.JOIN_THREAD, + id: messageID, + threadID: threadInfo.id, + creatorID: editorID, + time, + }; + + const updateInfos: Array = []; + if (viewerID === editorID) { + updateInfos.push({ + type: updateTypes.JOIN_THREAD, + id: uuid.v4(), + time, + threadInfo, + rawMessageInfos, + truncationStatus, + rawEntryInfos, + }); + } else { + if (!currentThreadInfoOptional || !currentThreadInfoOptional.thick) { + // We can't perform this operation now. It should be queued for later. + return { + rawMessageInfos: [], + updateInfos: [], + }; + } + const currentThreadInfo: ThickRawThreadInfo = currentThreadInfoOptional; + + 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 member = minimallyEncodeMemberInfo({ + id: editorID, + role: defaultRoleID, + permissions: membershipPermissions, + isSender: editorID === viewerID, + subscription: joinThreadSubscription, + }); + if (currentThreadInfo?.thick) { + const updatedThreadInfo = { + ...currentThreadInfo, + members: [...threadInfo.members, member], + }; + updateInfos.push({ + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time, + threadInfo: updatedThreadInfo, + }); + } + } + return { + rawMessageInfos: [joinThreadMessage], + updateInfos, + }; + }, +}); + +export { joinThreadSpec }; diff --git a/lib/types/dm-ops.js b/lib/types/dm-ops.js --- a/lib/types/dm-ops.js +++ b/lib/types/dm-ops.js @@ -28,6 +28,7 @@ SEND_REACTION_MESSAGE: 'send_reaction_message', SEND_EDIT_MESSAGE: 'send_edit_message', ADD_MEMBERS: 'add_members', + JOIN_THREAD: 'join_thread', }); export type DMOperationType = $Values; @@ -163,13 +164,36 @@ rawEntryInfos: t.list(rawEntryInfoValidator), }); +export type DMJoinThreadOperation = { + +type: 'join_thread', + +editorID: string, + +time: number, + +messageID: string, + +threadInfo: ThickRawThreadInfo, + +rawMessageInfos: $ReadOnlyArray, + +truncationStatus: MessageTruncationStatus, + +rawEntryInfos: $ReadOnlyArray, +}; +export const dmJoinThreadOperationValidator: TInterface = + tShape({ + type: tString(dmOperationTypes.JOIN_THREAD), + editorID: tUserID, + time: t.Number, + messageID: t.String, + threadInfo: threadInfoValidator, + rawMessageInfos: t.list(rawMessageInfoValidator), + truncationStatus: messageTruncationStatusValidator, + rawEntryInfos: t.list(rawEntryInfoValidator), + }); + export type DMOperation = | DMCreateThreadOperation | DMCreateSidebarOperation | DMSendTextMessageOperation | DMSendReactionMessageOperation | DMSendEditMessageOperation - | DMAddMembersOperation; + | DMAddMembersOperation + | DMJoinThreadOperation; export const dmOperationValidator: TUnion = t.union([ dmCreateThreadOperationValidator, dmCreateSidebarOperationValidator, @@ -177,6 +201,7 @@ dmSendReactionMessageOperationValidator, dmSendEditMessageOperationValidator, dmAddMembersOperationValidator, + dmJoinThreadOperationValidator, ]); export type DMOperationResult = {