diff --git a/lib/permissions/thread-permissions.js b/lib/permissions/thread-permissions.js --- a/lib/permissions/thread-permissions.js +++ b/lib/permissions/thread-permissions.js @@ -466,6 +466,10 @@ threadType: ThickThreadType, ): ThreadRolePermissionsBlob { invariant(threadTypeIsThick(threadType), 'ThreadType should be thick'); + const openDescendantKnowOf = OPEN_DESCENDANT + threadPermissions.KNOW_OF; + const openDescendantVisible = OPEN_DESCENDANT + threadPermissions.VISIBLE; + const openChildJoinThread = OPEN_CHILD + threadPermissions.JOIN_THREAD; + const basePermissions = { [threadPermissions.KNOW_OF]: true, [threadPermissions.VISIBLE]: true, @@ -493,6 +497,9 @@ ...basePermissions, [threadPermissions.EDIT_ENTRIES]: true, [threadPermissions.CREATE_SIDEBARS]: true, + [openDescendantKnowOf]: true, + [openDescendantVisible]: true, + [openChildJoinThread]: true, }; } return { @@ -501,6 +508,9 @@ [threadPermissions.CREATE_SIDEBARS]: true, [threadPermissions.ADD_MEMBERS]: true, [threadPermissions.LEAVE_THREAD]: true, + [openDescendantKnowOf]: true, + [openDescendantVisible]: true, + [openChildJoinThread]: true, }; } 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 --- a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js +++ b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js @@ -55,7 +55,7 @@ ) => { const { time, messageID, addedUserIDs, existingThreadDetails } = dmOperation; - const { viewerID, threadInfos } = utilities; + const { threadInfos } = utilities; const { rawMessageInfo } = createAddViewerToThreadMembersMessageDataWithInfoFromDMOp(dmOperation); @@ -115,7 +115,7 @@ }, }, }, - viewerID, + utilities, ); const updateInfos = [ { diff --git a/lib/shared/dm-ops/create-sidebar-spec.js b/lib/shared/dm-ops/create-sidebar-spec.js --- a/lib/shared/dm-ops/create-sidebar-spec.js +++ b/lib/shared/dm-ops/create-sidebar-spec.js @@ -153,7 +153,7 @@ containingThreadID: parentThreadID, timestamps: createThreadTimestamps(time, allMemberIDs), }, - viewerID, + utilities, ); const { sidebarSourceMessageInfo, createSidebarMessageInfo } = diff --git a/lib/shared/dm-ops/create-thread-spec.js b/lib/shared/dm-ops/create-thread-spec.js --- a/lib/shared/dm-ops/create-thread-spec.js +++ b/lib/shared/dm-ops/create-thread-spec.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import uuid from 'uuid'; import type { @@ -11,6 +12,7 @@ getAllThreadPermissions, makePermissionsBlob, getThickThreadRolePermissionsBlob, + makePermissionsForChildrenBlob, } from '../../permissions/thread-permissions.js'; import type { CreateThickRawThreadInfoInput, @@ -59,10 +61,41 @@ }; } +function createPermissionsInfoForNonMembers( + threadID: string, + threadType: ThickThreadType, + parentThreadInfo: ?ThickRawThreadInfo, +): ThreadPermissionsInfo { + if (!parentThreadInfo) { + return getAllThreadPermissions( + makePermissionsBlob(null, null, threadID, threadType), + threadID, + ); + } + const parentThreadRolePermissions = getThickThreadRolePermissionsBlob( + parentThreadInfo.type, + ); + const parentPermissionsBlob = makePermissionsBlob( + parentThreadRolePermissions, + null, + parentThreadInfo.id, + parentThreadInfo.type, + ); + return getAllThreadPermissions( + makePermissionsBlob( + null, + makePermissionsForChildrenBlob(parentPermissionsBlob), + threadID, + threadType, + ), + threadID, + ); +} + type MutableThickRawThreadInfo = { ...ThickRawThreadInfo }; function createThickRawThreadInfo( input: CreateThickRawThreadInfoInput, - viewerID: string, + utilities: ProcessDMOperationUtilities, ): MutableThickRawThreadInfo { const { threadID, @@ -89,6 +122,27 @@ const { membershipPermissions, role } = createRoleAndPermissionForThickThreads(threadType, threadID, roleID); + const isViewerMember = allMemberIDsWithSubscriptions.some( + member => member.id === utilities.viewerID, + ); + let viewerMembershipPermissions = membershipPermissions; + let viewerRoleID: ?string = role.id; + if (!isViewerMember) { + viewerRoleID = null; + const parentThreadInfo = parentThreadID + ? utilities.threadInfos[parentThreadID] + : null; + invariant( + !parentThreadInfo || parentThreadInfo.thick, + 'Parent thread should be thick', + ); + viewerMembershipPermissions = createPermissionsInfoForNonMembers( + threadID, + threadType, + parentThreadInfo, + ); + } + const newThread: MutableThickRawThreadInfo = { thick: true, minimallyEncoded: true, @@ -101,9 +155,12 @@ ({ id: memberID, subscription }) => minimallyEncodeMemberInfo({ id: memberID, - role: role.id, - permissions: membershipPermissions, - isSender: memberID === viewerID, + role: memberID === utilities.viewerID ? viewerRoleID : role.id, + permissions: + memberID === utilities.viewerID + ? viewerMembershipPermissions + : membershipPermissions, + isSender: memberID === utilities.viewerID, subscription, }), ), @@ -111,8 +168,8 @@ [role.id]: role, }, currentUser: minimallyEncodeThreadCurrentUserInfo({ - role: role.id, - permissions: membershipPermissions, + role: viewerRoleID, + permissions: viewerMembershipPermissions, subscription: joinThreadSubscription, unread, }), @@ -189,7 +246,7 @@ unread: creatorID !== viewerID, timestamps: createThreadTimestamps(time, allMemberIDs), }, - viewerID, + utilities, ); const { rawMessageInfo } = @@ -222,4 +279,5 @@ createThickRawThreadInfo, createThreadSpec, createRoleAndPermissionForThickThreads, + createPermissionsInfoForNonMembers, }; diff --git a/lib/shared/dm-ops/join-thread-spec.js b/lib/shared/dm-ops/join-thread-spec.js --- a/lib/shared/dm-ops/join-thread-spec.js +++ b/lib/shared/dm-ops/join-thread-spec.js @@ -118,7 +118,7 @@ members: memberTimestamps, }, }, - viewerID, + utilities, ); updateInfos.push({ type: updateTypes.JOIN_THREAD, diff --git a/lib/shared/dm-ops/leave-thread-spec.js b/lib/shared/dm-ops/leave-thread-spec.js --- a/lib/shared/dm-ops/leave-thread-spec.js +++ b/lib/shared/dm-ops/leave-thread-spec.js @@ -3,10 +3,12 @@ import invariant from 'invariant'; import uuid from 'uuid'; +import { createPermissionsInfoForNonMembers } from './create-thread-spec.js'; import type { DMOperationSpec, ProcessDMOperationUtilities, } from './dm-op-spec.js'; +import { permissionsToBitmaskHex } from '../../permissions/minimally-encoded-thread-permissions.js'; import type { DMLeaveThreadOperation } from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ThickRawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; @@ -121,9 +123,33 @@ ...memberTimestamps[editorID], isMember: time, }; + + let currentUser = threadInfo.currentUser; + if (editorID === viewerID) { + const parentThreadID = threadInfo.parentThreadID; + const parentThreadInfo = parentThreadID + ? utilities.threadInfos[parentThreadID] + : null; + invariant( + parentThreadInfo?.thick, + 'Parent thread should be present and thick', + ); + const viewerMembershipPermissions = createPermissionsInfoForNonMembers( + threadID, + threadInfo.type, + parentThreadInfo, + ); + currentUser = { + ...currentUser, + role: null, + permissions: permissionsToBitmaskHex(viewerMembershipPermissions), + }; + } + const updatedThreadInfo = { ...threadInfo, members: threadInfo.members.filter(member => member.id !== editorID), + currentUser, timestamps: { ...threadInfo.timestamps, members: memberTimestamps,