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,6 +2,7 @@ import invariant from 'invariant'; +import { parseThreadPermissionString } from './prefixes.js'; import type { ThreadPermission, ThreadPermissionsInfo, @@ -70,8 +71,50 @@ return (permissionsBitmask & permissionBitmask) !== BigInt(0); }; +const baseRolePermissionEncoding = Object.fromEntries( + Object.keys(minimallyEncodedThreadPermissions).map((key, idx) => [ + key, + BigInt(idx), + ]), +); +const propagationPrefixes = Object.freeze({ + '': BigInt(0), + 'descendant_': BigInt(1), + 'child_': BigInt(2), +}); +const filterPrefixes = Object.freeze({ + '': BigInt(0), + 'open_': BigInt(1), + 'toplevel_': BigInt(2), + 'opentoplevel_': BigInt(3), +}); + +// Role Permission Bitmask Structure +// [9 8 7 6 5 4 3 2 1 0] - bit positions +// [b b b b b b p p f f] - symbol representation +// b = basePermission (6 bits) +// p = propagationPrefix (2 bits) +// f = filterPrefix (2 bits) +const rolePermissionToBitmaskHex = (threadRolePermission: string): string => { + const parsed = parseThreadPermissionString(threadRolePermission); + const basePermissionBits = + baseRolePermissionEncoding[parsed.permission] & BigInt(63); + const propagationPrefixBits = + propagationPrefixes[parsed.propagationPrefix ?? ''] & BigInt(3); + const filterPrefixBits = + filterPrefixes[parsed.filterPrefix ?? ''] & BigInt(3); + + const bitmask = + (basePermissionBits << BigInt(4)) | + (propagationPrefixBits << BigInt(2)) | + filterPrefixBits; + + return bitmask.toString(16).padStart(3, '0'); +}; + export { minimallyEncodedThreadPermissions, permissionsToBitmaskHex, hasPermission, + rolePermissionToBitmaskHex, }; 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 @@ -3,6 +3,7 @@ import { hasPermission, permissionsToBitmaskHex, + rolePermissionToBitmaskHex, } from './minimally-encoded-thread-permissions.js'; describe('minimallyEncodedThreadPermissions', () => { @@ -51,3 +52,39 @@ expect(hasPermission(permissionsBitmask, 'edit_message')).toBe(true); }); }); + +describe('rolePermissionToBitmaskHex', () => { + it('should encode `child_opentoplevel_visible` successfully', () => { + expect(rolePermissionToBitmaskHex('child_opentoplevel_visible')).toBe( + '01b', + ); + }); + + it('should encode `child_opentoplevel_know_of` successfully', () => { + expect(rolePermissionToBitmaskHex('child_opentoplevel_know_of')).toBe( + '00b', + ); + }); + + it('should encode `child_toplevel_visible` successfully', () => { + expect(rolePermissionToBitmaskHex('child_toplevel_visible')).toBe('01a'); + }); + + it('should encode `child_toplevel_know_of` successfully', () => { + expect(rolePermissionToBitmaskHex('child_toplevel_know_of')).toBe('00a'); + }); + + it('should encode `child_opentoplevel_join_thread` successfully', () => { + expect(rolePermissionToBitmaskHex('child_opentoplevel_join_thread')).toBe( + '0ab', + ); + }); + + it('should encode `child_visible` successfully', () => { + expect(rolePermissionToBitmaskHex('child_visible')).toBe('018'); + }); + + it('should encode `child_know_of` successfully', () => { + expect(rolePermissionToBitmaskHex('child_know_of')).toBe('008'); + }); +});