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 @@ -2,14 +2,21 @@ import invariant from 'invariant'; -import { parseThreadPermissionString } from './prefixes.js'; -import type { - ThreadPermission, - ThreadPermissionInfo, - ThreadPermissionsInfo, - ThreadRolePermissionsBlob, +import { + parseThreadPermissionString, + constructThreadPermissionString, +} from './prefixes.js'; +import { + type ThreadPermission, + type ThreadPermissionInfo, + type ThreadPermissionsInfo, + type ThreadRolePermissionsBlob, + threadPermissions, + assertThreadPermission, + assertThreadPermissionPropagationPrefix, + assertThreadPermissionFilterPrefix, + assertThreadPermissionMembershipPrefix, } from '../types/thread-permission-types.js'; -import { threadPermissions } from '../types/thread-permission-types.js'; import { entries, invertObjectToMap } from '../utils/objects.js'; import type { TRegex } from '../utils/validation-utils.js'; import { tRegex } from '../utils/validation-utils.js'; @@ -183,37 +190,43 @@ const tHexEncodedRolePermission: TRegex = tRegex(/^[0-9a-fA-F]{3,}$/); const decodeRolePermissionBitmask = (bitmask: string): string => { const bitmaskInt = BigInt(`0x${bitmask}`); - const basePermission = (bitmaskInt >> BigInt(4)) & BigInt(63); - const propagationPrefix = (bitmaskInt >> BigInt(2)) & BigInt(3); - const filterPrefix = bitmaskInt & BigInt(3); - const membershipPrefix = (bitmaskInt >> BigInt(10)) & BigInt(1); - - const basePermissionString = - inverseBaseRolePermissionEncoding.get(basePermission); - const propagationPrefixString = - inversePropagationPrefixes.get(propagationPrefix) ?? ''; - const filterPrefixString = inverseFilterPrefixes.get(filterPrefix) ?? ''; - const membershipPrefixString = - inverseMembershipPrefixes.get(membershipPrefix) ?? ''; + const basePermissionBits = (bitmaskInt >> BigInt(4)) & BigInt(63); + const permissionString = + inverseBaseRolePermissionEncoding.get(basePermissionBits); invariant( - basePermissionString !== null && - basePermissionString !== undefined && - propagationPrefixString !== null && - propagationPrefixString !== undefined && - filterPrefixString !== null && - filterPrefixString !== undefined && - membershipPrefixString !== null && - membershipPrefixString !== undefined, + permissionString !== null && permissionString !== undefined, 'invalid bitmask', ); + const permission = assertThreadPermission(permissionString); - return ( - propagationPrefixString + - filterPrefixString + - membershipPrefixString + - basePermissionString + const propagationPrefixBits = (bitmaskInt >> BigInt(2)) & BigInt(3); + const propagationPrefixString = inversePropagationPrefixes.get( + propagationPrefixBits, ); + const propagationPrefix = propagationPrefixString + ? assertThreadPermissionPropagationPrefix(propagationPrefixString) + : undefined; + + const filterPrefixBits = bitmaskInt & BigInt(3); + const filterPrefixString = inverseFilterPrefixes.get(filterPrefixBits); + const filterPrefix = filterPrefixString + ? assertThreadPermissionFilterPrefix(filterPrefixString) + : undefined; + + const membershipPrefixBits = (bitmaskInt >> BigInt(10)) & BigInt(1); + const membershipPrefixString = + inverseMembershipPrefixes.get(membershipPrefixBits); + const membershipPrefix = membershipPrefixString + ? assertThreadPermissionMembershipPrefix(membershipPrefixString) + : undefined; + + return constructThreadPermissionString({ + permission, + propagationPrefix, + filterPrefix, + membershipPrefix, + }); }; const threadRolePermissionsBlobToBitmaskArray = ( diff --git a/lib/permissions/prefixes.js b/lib/permissions/prefixes.js --- a/lib/permissions/prefixes.js +++ b/lib/permissions/prefixes.js @@ -5,7 +5,7 @@ type ThreadPermissionFilterPrefix, type ThreadPermissionPropagationPrefix, type ThreadPermissionMembershipPrefix, - assertThreadPermissions, + assertThreadPermission, threadPermissionFilterPrefixes, threadPermissionPropagationPrefixes, threadPermissionMembershipPrefixes, @@ -60,7 +60,7 @@ break; } - const permission = assertThreadPermissions(remainingString); + const permission = assertThreadPermission(remainingString); return { permission, propagationPrefix, filterPrefix, membershipPrefix }; } 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 @@ -49,36 +49,36 @@ }); export type ThreadPermission = $Values; -export function assertThreadPermissions( - ourThreadPermissions: string, +export function assertThreadPermission( + ourThreadPermission: string, ): ThreadPermission { invariant( - ourThreadPermissions === 'know_of' || - ourThreadPermissions === 'visible' || - ourThreadPermissions === 'voiced' || - ourThreadPermissions === 'edit_entries' || - ourThreadPermissions === 'edit_thread' || - ourThreadPermissions === 'edit_thread_description' || - ourThreadPermissions === 'edit_thread_color' || - ourThreadPermissions === 'delete_thread' || - ourThreadPermissions === 'create_subthreads' || - ourThreadPermissions === 'create_sidebars' || - ourThreadPermissions === 'join_thread' || - ourThreadPermissions === 'edit_permissions' || - ourThreadPermissions === 'add_members' || - ourThreadPermissions === 'remove_members' || - ourThreadPermissions === 'change_role' || - ourThreadPermissions === 'leave_thread' || - ourThreadPermissions === 'react_to_message' || - ourThreadPermissions === 'edit_message' || - ourThreadPermissions === 'edit_thread_avatar' || - ourThreadPermissions === 'manage_pins' || - ourThreadPermissions === 'manage_invite_links' || - ourThreadPermissions === 'voiced_in_announcement_channels' || - ourThreadPermissions === 'manage_farcaster_channel_tags', + ourThreadPermission === 'know_of' || + ourThreadPermission === 'visible' || + ourThreadPermission === 'voiced' || + ourThreadPermission === 'edit_entries' || + ourThreadPermission === 'edit_thread' || + ourThreadPermission === 'edit_thread_description' || + ourThreadPermission === 'edit_thread_color' || + ourThreadPermission === 'delete_thread' || + ourThreadPermission === 'create_subthreads' || + ourThreadPermission === 'create_sidebars' || + ourThreadPermission === 'join_thread' || + ourThreadPermission === 'edit_permissions' || + ourThreadPermission === 'add_members' || + ourThreadPermission === 'remove_members' || + ourThreadPermission === 'change_role' || + ourThreadPermission === 'leave_thread' || + ourThreadPermission === 'react_to_message' || + ourThreadPermission === 'edit_message' || + ourThreadPermission === 'edit_thread_avatar' || + ourThreadPermission === 'manage_pins' || + ourThreadPermission === 'manage_invite_links' || + ourThreadPermission === 'voiced_in_announcement_channels' || + ourThreadPermission === 'manage_farcaster_channel_tags', 'string is not threadPermissions enum', ); - return ourThreadPermissions; + return ourThreadPermission; } const threadPermissionValidator = t.enums.of(values(threadPermissions)); @@ -90,6 +90,15 @@ export type ThreadPermissionPropagationPrefix = $Values< typeof threadPermissionPropagationPrefixes, >; +export function assertThreadPermissionPropagationPrefix( + ourPrefix: string, +): ThreadPermissionPropagationPrefix { + invariant( + ourPrefix === 'descendant_' || ourPrefix === 'child_', + 'string is not ThreadPermissionPropagationPrefix', + ); + return ourPrefix; +} export const threadPermissionFilterPrefixes = Object.freeze({ // includes only SIDEBAR, COMMUNITY_OPEN_SUBTHREAD, @@ -104,6 +113,17 @@ export type ThreadPermissionFilterPrefix = $Values< typeof threadPermissionFilterPrefixes, >; +export function assertThreadPermissionFilterPrefix( + ourPrefix: string, +): ThreadPermissionFilterPrefix { + invariant( + ourPrefix === 'open_' || + ourPrefix === 'toplevel_' || + ourPrefix === 'opentoplevel_', + 'string is not ThreadPermissionFilterPrefix', + ); + return ourPrefix; +} export const threadPermissionMembershipPrefixes = Object.freeze({ MEMBER: 'member_', @@ -111,6 +131,15 @@ export type ThreadPermissionMembershipPrefix = $Values< typeof threadPermissionMembershipPrefixes, >; +export function assertThreadPermissionMembershipPrefix( + ourPrefix: string, +): ThreadPermissionMembershipPrefix { + invariant( + ourPrefix === 'member_', + 'string is not ThreadPermissionMembershipPrefix', + ); + return ourPrefix; +} // These are the set of user-facing permissions that we display as configurable // to the user when they are creating a custom role for their given community.