diff --git a/keyserver/src/fetchers/thread-fetchers.js b/keyserver/src/fetchers/thread-fetchers.js --- a/keyserver/src/fetchers/thread-fetchers.js +++ b/keyserver/src/fetchers/thread-fetchers.js @@ -271,6 +271,9 @@ const codeVersionBelow221 = !hasMinCodeVersion(viewer.platformDetails, { native: 221, }); + const codeVersionBelow283 = !hasMinCodeVersion(viewer.platformDetails, { + native: 283, + }); const threadInfos = {}; for (const threadID in serverResult.threadInfos) { @@ -282,6 +285,7 @@ filterThreadEditAvatarPermission: codeVersionBelow213, excludePinInfo: codeVersionBelow209, filterManageInviteLinksPermission: codeVersionBelow221, + filterVoicedInAnnouncementChannelsPermission: codeVersionBelow283, }, ); if (threadInfo) { diff --git a/lib/permissions/minimally-encoded-thread-permissions-test-data.js b/lib/permissions/minimally-encoded-thread-permissions-test-data.js --- a/lib/permissions/minimally-encoded-thread-permissions-test-data.js +++ b/lib/permissions/minimally-encoded-thread-permissions-test-data.js @@ -93,6 +93,10 @@ value: true, source: '1', }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, isSender: false, }, @@ -176,6 +180,10 @@ value: false, source: null, }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, isSender: true, }, @@ -281,6 +289,10 @@ value: false, source: null, }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, subscription: { home: true, @@ -455,6 +467,10 @@ source: null, value: false, }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, isSender: false, }, @@ -546,6 +562,10 @@ source: null, value: false, }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, isSender: true, }, @@ -659,6 +679,10 @@ source: null, value: false, }, + voiced_in_announcement_channels: { + value: false, + source: null, + }, }, subscription: { home: true, diff --git a/lib/permissions/minimally-encoded-thread-permissions.js b/lib/permissions/minimally-encoded-thread-permissions.js --- a/lib/permissions/minimally-encoded-thread-permissions.js +++ b/lib/permissions/minimally-encoded-thread-permissions.js @@ -44,6 +44,7 @@ edit_thread_avatar: BigInt(18), manage_pins: BigInt(19), manage_invite_links: BigInt(20), + voiced_in_announcement_channels: BigInt(21), }); // `minimallyEncodedThreadPermissions` is used to map each permission diff --git a/lib/permissions/minimally-encoded-thread-permissions.test.js b/lib/permissions/minimally-encoded-thread-permissions.test.js --- a/lib/permissions/minimally-encoded-thread-permissions.test.js +++ b/lib/permissions/minimally-encoded-thread-permissions.test.js @@ -48,6 +48,7 @@ edit_thread_avatar: { value: false, source: null }, manage_pins: { value: false, source: null }, manage_invite_links: { value: false, source: null }, + voiced_in_announcement_channels: { value: false, source: null }, }; describe('minimallyEncodedThreadPermissions', () => { @@ -123,6 +124,7 @@ edit_thread_avatar: { value: false, source: null }, manage_pins: { value: false, source: null }, manage_invite_links: { value: false, source: null }, + voiced_in_announcement_channels: { value: false, source: null }, }; it('should decode ThreadPermissionsInfo from bitmask', () => { 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 @@ -23,6 +23,8 @@ type ThreadType, type CommunityRootThreadType, threadTypes, + threadTypeIsAnnouncementThread, + threadTypeIsCommunityRoot, } from '../types/thread-types-enum.js'; function permissionLookup( @@ -110,6 +112,26 @@ permissions = combinedPermissions; } + const threadTypeIsCommunityRootOrAnnouncementChannel = + threadTypeIsAnnouncementThread(threadType) || + threadTypeIsCommunityRoot(threadType); + const hasVoicedInAnnouncementChannelsPermission = permissionLookup( + (permissions: ThreadPermissionsBlob), + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS, + ); + const isMember = !!rolePermissions; + + if ( + threadTypeIsCommunityRootOrAnnouncementChannel && + hasVoicedInAnnouncementChannelsPermission && + isMember + ) { + permissions[threadPermissions.VOICED] = { + value: true, + source: threadID, + }; + } + if (Object.keys(permissions).length === 0) { return null; } @@ -249,6 +271,8 @@ const descendantRemoveMembers = DESCENDANT + threadPermissions.REMOVE_MEMBERS; const descendantChangeRole = DESCENDANT + threadPermissions.CHANGE_ROLE; const descendantManagePins = DESCENDANT + threadPermissions.MANAGE_PINS; + const topLevelDescendantVoicedInAnnouncementChannels = + TOP_LEVEL_DESCENDANT + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS; const baseAdminPermissions = { [threadPermissions.KNOW_OF]: true, @@ -269,6 +293,7 @@ [threadPermissions.CHANGE_ROLE]: true, [threadPermissions.MANAGE_PINS]: true, [threadPermissions.MANAGE_INVITE_LINKS]: true, + [threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS]: true, [descendantKnowOf]: true, [descendantVisible]: true, [topLevelDescendantJoinThread]: true, @@ -287,6 +312,7 @@ [descendantRemoveMembers]: true, [descendantChangeRole]: true, [descendantManagePins]: true, + [topLevelDescendantVoicedInAnnouncementChannels]: true, }; let adminPermissions; diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -74,6 +74,7 @@ type ThreadPermission, type ThreadPermissionsInfo, type UserSurfacedPermission, + threadPermissionFilterPrefixes, } from '../types/thread-permission-types.js'; import { type ThreadType, @@ -821,6 +822,7 @@ +filterThreadEditAvatarPermission?: boolean, +excludePinInfo?: boolean, +filterManageInviteLinksPermission?: boolean, + +filterVoicedInAnnouncementChannelsPermission?: boolean, }; function rawThreadInfoFromServerThreadInfo( serverThreadInfo: ServerThreadInfo, @@ -832,6 +834,8 @@ const excludePinInfo = options?.excludePinInfo; const filterManageInviteLinksPermission = options?.filterManageInviteLinksPermission; + const filterVoicedInAnnouncementChannelsPermission = + options?.filterVoicedInAnnouncementChannelsPermission; const filterThreadPermissions = _omitBy( (v, k) => @@ -848,7 +852,14 @@ threadPermissions.MANAGE_PINS, ].includes(k)) || (filterManageInviteLinksPermission && - [threadPermissions.MANAGE_INVITE_LINKS].includes(k)), + [threadPermissions.MANAGE_INVITE_LINKS].includes(k)) || + (filterVoicedInAnnouncementChannelsPermission && + [ + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS, + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissionFilterPrefixes.TOP_LEVEL + + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS, + ].includes(k)), ); const members = []; diff --git a/lib/types/thread-permission-types.js b/lib/types/thread-permission-types.js --- a/lib/types/thread-permission-types.js +++ b/lib/types/thread-permission-types.js @@ -32,6 +32,7 @@ EDIT_THREAD_AVATAR: 'edit_thread_avatar', MANAGE_PINS: 'manage_pins', MANAGE_INVITE_LINKS: 'manage_invite_links', + VOICED_IN_ANNOUNCEMENT_CHANNELS: 'voiced_in_announcement_channels', }); export type ThreadPermission = $Values; @@ -59,7 +60,8 @@ ourThreadPermissions === 'edit_message' || ourThreadPermissions === 'edit_thread_avatar' || ourThreadPermissions === 'manage_pins' || - ourThreadPermissions === 'manage_invite_links', + ourThreadPermissions === 'manage_invite_links' || + ourThreadPermissions === 'voiced_in_announcement_channels', 'string is not threadPermissions enum', ); return ourThreadPermissions; @@ -158,8 +160,16 @@ userSurfacedPermission: userSurfacedPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS, }; -const voiced = threadPermissions.VOICED; -const voicedPermissions = new Set([voiced]); +const voicedInAnnouncementChannels = + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS; +const descendantTopLevelVoicedInAnnouncementChannels = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissionFilterPrefixes.TOP_LEVEL + + threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS; +const voicedPermissions = new Set([ + voicedInAnnouncementChannels, + descendantTopLevelVoicedInAnnouncementChannels, +]); const createAndEditChannelsPermission = { title: 'Create and edit channels', diff --git a/lib/types/thread-types-enum.js b/lib/types/thread-types-enum.js --- a/lib/types/thread-types-enum.js +++ b/lib/types/thread-types-enum.js @@ -82,6 +82,12 @@ threadTypes.GENESIS, ]); +export const announcementThreadTypes: $ReadOnlyArray = Object.freeze([ + threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT, + threadTypes.COMMUNITY_OPEN_ANNOUNCEMENT_SUBTHREAD, + threadTypes.COMMUNITY_SECRET_ANNOUNCEMENT_SUBTHREAD, +]); + export const communitySubthreads: $ReadOnlyArray = Object.freeze([ threadTypes.COMMUNITY_OPEN_SUBTHREAD, threadTypes.COMMUNITY_OPEN_ANNOUNCEMENT_SUBTHREAD, @@ -92,3 +98,9 @@ export function threadTypeIsCommunityRoot(threadType: ThreadType): boolean { return communityThreadTypes.includes(threadType); } + +export function threadTypeIsAnnouncementThread( + threadType: ThreadType, +): boolean { + return communitySubthreads.includes(threadType); +} diff --git a/native/redux/persist.js b/native/redux/persist.js --- a/native/redux/persist.js +++ b/native/redux/persist.js @@ -931,6 +931,8 @@ return state; }, + [59]: state => + updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions), }; // After migration 31, we'll no longer want to persist `messageStore.messages` @@ -1045,7 +1047,7 @@ 'connection', ], debug: __DEV__, - version: 58, + version: 59, transforms: [ messageStoreMessagesBlocklistTransform, reportStoreTransform,