diff --git a/.eslintrc.json b/.eslintrc.json --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "root": true, "env": { - "es6": true + "es6": true, + "es2020": true }, "extends": [ "eslint:recommended", diff --git a/lib/permissions/minimally-encoded-thread-permissions.js b/lib/permissions/minimally-encoded-thread-permissions.js new file mode 100644 --- /dev/null +++ b/lib/permissions/minimally-encoded-thread-permissions.js @@ -0,0 +1,79 @@ +// @flow + +import invariant from 'invariant'; + +import type { + ThreadPermission, + ThreadPermissionsInfo, +} from '../types/thread-permission-types.js'; +import { entries } from '../utils/objects.js'; + +const minimallyEncodedThreadPermissions = Object.freeze({ + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue bigint-unsupported + know_of: BigInt(1) << BigInt(0), + membership: BigInt(1) << BigInt(1), // DEPRECATED + visible: BigInt(1) << BigInt(2), + voiced: BigInt(1) << BigInt(3), + edit_entries: BigInt(1) << BigInt(4), + edit_thread: BigInt(1) << BigInt(5), // EDIT_THREAD_NAME + edit_thread_description: BigInt(1) << BigInt(6), + edit_thread_color: BigInt(1) << BigInt(7), + delete_thread: BigInt(1) << BigInt(8), + create_subthreads: BigInt(1) << BigInt(9), // CREATE_SUBCHANNELS + create_sidebars: BigInt(1) << BigInt(10), + join_thread: BigInt(1) << BigInt(11), + edit_permissions: BigInt(1) << BigInt(12), + add_members: BigInt(1) << BigInt(13), + remove_members: BigInt(1) << BigInt(14), + change_role: BigInt(1) << BigInt(15), + leave_thread: BigInt(1) << BigInt(16), + react_to_message: BigInt(1) << BigInt(17), + edit_message: BigInt(1) << BigInt(18), + edit_thread_avatar: BigInt(1) << BigInt(19), + manage_pins: BigInt(1) << BigInt(20), + manage_invite_links: BigInt(1) << BigInt(21), +}); + +// TODO (atul): Update flow to `194.0.0` for bigint support +// $FlowIssue bigint-unsupported +const permissionsToBitmask = (permissions: ThreadPermissionsInfo): bigint => { + let bitmask = BigInt(0); + for (const [key, permission] of entries(permissions)) { + if (permission.value && key in minimallyEncodedThreadPermissions) { + invariant( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue illegal-typeof + typeof minimallyEncodedThreadPermissions[key] === 'bigint', + 'must be bigint', + ); + bitmask |= minimallyEncodedThreadPermissions[key]; + } + } + return bitmask; +}; + +const hasPermission = ( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue bigint-unsupported + permissionsBitmask: bigint, + permission: ThreadPermission, +): boolean => { + if (!(permission in minimallyEncodedThreadPermissions)) { + return false; + } + const permissionBitmask = minimallyEncodedThreadPermissions[permission]; + invariant( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue illegal-typeof + typeof permissionBitmask === 'bigint', + 'permissionBitmask must be of type bigint', + ); + return (permissionsBitmask & permissionBitmask) !== BigInt(0); +}; + +export { + minimallyEncodedThreadPermissions, + permissionsToBitmask, + hasPermission, +}; diff --git a/lib/permissions/minimally-encoded-thread-permissions.test.js b/lib/permissions/minimally-encoded-thread-permissions.test.js new file mode 100644 --- /dev/null +++ b/lib/permissions/minimally-encoded-thread-permissions.test.js @@ -0,0 +1,55 @@ +// @flow + +import { + hasPermission, + permissionsToBitmask, +} from './minimally-encoded-thread-permissions.js'; + +describe('minimallyEncodedThreadPermissions', () => { + const permissions = { + know_of: { value: true, source: '1' }, + membership: { value: false, source: null }, + visible: { value: true, source: '1' }, + voiced: { value: true, source: '1' }, + edit_entries: { value: true, source: '1' }, + edit_thread: { value: true, source: '1' }, + edit_thread_description: { value: true, source: '1' }, + edit_thread_color: { value: true, source: '1' }, + delete_thread: { value: true, source: '1' }, + create_subthreads: { value: true, source: '1' }, + create_sidebars: { value: true, source: '1' }, + join_thread: { value: false, source: null }, + edit_permissions: { value: false, source: null }, + add_members: { value: true, source: '1' }, + remove_members: { value: true, source: '1' }, + change_role: { value: true, source: '1' }, + leave_thread: { value: false, source: null }, + react_to_message: { value: true, source: '1' }, + edit_message: { value: true, source: '1' }, + }; + + it('should encode ThreadPermissionsInfo as bitmask', () => { + const permissionsBitmask = permissionsToBitmask(permissions); + expect(permissionsBitmask.toString(2)).toBe('1101110011111111101'); + expect(hasPermission(permissionsBitmask, 'know_of')).toBe(true); + expect(hasPermission(permissionsBitmask, 'membership')).toBe(false); + expect(hasPermission(permissionsBitmask, 'visible')).toBe(true); + expect(hasPermission(permissionsBitmask, 'voiced')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_entries')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_thread')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_thread_description')).toBe( + true, + ); + expect(hasPermission(permissionsBitmask, 'edit_thread_color')).toBe(true); + expect(hasPermission(permissionsBitmask, 'delete_thread')).toBe(true); + expect(hasPermission(permissionsBitmask, 'create_subthreads')).toBe(true); + expect(hasPermission(permissionsBitmask, 'create_sidebars')).toBe(true); + expect(hasPermission(permissionsBitmask, 'join_thread')).toBe(false); + expect(hasPermission(permissionsBitmask, 'edit_permissions')).toBe(false); + expect(hasPermission(permissionsBitmask, 'remove_members')).toBe(true); + expect(hasPermission(permissionsBitmask, 'change_role')).toBe(true); + expect(hasPermission(permissionsBitmask, 'leave_thread')).toBe(false); + expect(hasPermission(permissionsBitmask, 'react_to_message')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_message')).toBe(true); + }); +});