Remove the unused USE_VOICE_ACTIVITY permission bit (was bit 25) and shift STREAM to bit 25, DISCONNECT_MEMBERS to bit 26. Add a database migration to remap stored permission values. Gate camera and screen share buttons in VoiceControlBar behind canSpeak/canStream, matching VoiceControls behavior.
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
// ─── Bitwise Permission Engine ──────────────────────────────────────────────
|
|
// Single source of truth for all permission bits. Used by both server and client.
|
|
// SQLite stores as TEXT (decimal string). Never put raw bigint into JSON.
|
|
|
|
export const PermissionBits = {
|
|
ADMINISTRATOR: 1n << 0n,
|
|
VIEW_CHANNEL: 1n << 1n,
|
|
MANAGE_CHANNELS: 1n << 2n,
|
|
MANAGE_ROLES: 1n << 3n,
|
|
MANAGE_SPACE: 1n << 4n,
|
|
CREATE_INVITE: 1n << 5n,
|
|
KICK_MEMBERS: 1n << 6n,
|
|
BAN_MEMBERS: 1n << 7n,
|
|
SEND_MESSAGES: 1n << 10n,
|
|
MANAGE_MESSAGES: 1n << 11n,
|
|
ATTACH_FILES: 1n << 12n,
|
|
READ_MESSAGE_HISTORY: 1n << 13n,
|
|
ADD_REACTIONS: 1n << 14n,
|
|
CONNECT: 1n << 20n,
|
|
SPEAK: 1n << 21n,
|
|
MUTE_MEMBERS: 1n << 22n,
|
|
DEAFEN_MEMBERS: 1n << 23n,
|
|
MOVE_MEMBERS: 1n << 24n,
|
|
STREAM: 1n << 25n,
|
|
DISCONNECT_MEMBERS: 1n << 26n,
|
|
} as const;
|
|
|
|
export type PermissionBit = (typeof PermissionBits)[keyof typeof PermissionBits];
|
|
|
|
export const ALL_PERMISSIONS = Object.values(PermissionBits).reduce((a, b) => a | b, 0n);
|
|
|
|
export const DEFAULT_EVERYONE_PERMISSIONS =
|
|
PermissionBits.VIEW_CHANNEL |
|
|
PermissionBits.SEND_MESSAGES |
|
|
PermissionBits.CREATE_INVITE |
|
|
PermissionBits.CONNECT |
|
|
PermissionBits.SPEAK |
|
|
PermissionBits.ATTACH_FILES |
|
|
PermissionBits.READ_MESSAGE_HISTORY |
|
|
PermissionBits.ADD_REACTIONS |
|
|
PermissionBits.STREAM;
|
|
|
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
|
|
/** Check if a permissions value has a specific bit set. Accepts bigint or decimal string. */
|
|
export function hasPermissionBit(perms: bigint | string | undefined | null, bit: bigint): boolean {
|
|
if (perms === undefined || perms === null) return false;
|
|
const p = typeof perms === 'string' ? BigInt(perms) : perms;
|
|
// ADMINISTRATOR grants everything
|
|
if ((p & PermissionBits.ADMINISTRATOR) !== 0n) return true;
|
|
return (p & bit) === bit;
|
|
}
|
|
|
|
/** Convert a bigint to a decimal string safe for JSON serialization. */
|
|
export function permissionsToString(perms: bigint): string {
|
|
return perms.toString();
|
|
}
|
|
|
|
/** Convert a decimal string back to bigint. Returns 0n for falsy/invalid input. */
|
|
export function stringToPermissions(str: string | undefined | null): bigint {
|
|
if (!str) return 0n;
|
|
try {
|
|
return BigInt(str);
|
|
} catch {
|
|
// Fallback: legacy JSON array format (e.g. '["VIEW_CHANNEL","SEND_MESSAGES"]')
|
|
try {
|
|
const parsed = JSON.parse(str);
|
|
if (Array.isArray(parsed)) {
|
|
let result = 0n;
|
|
for (const key of parsed) {
|
|
const bit = PermissionBits[key as keyof typeof PermissionBits];
|
|
if (bit !== undefined) result |= bit;
|
|
}
|
|
return result;
|
|
}
|
|
} catch { /* not JSON either */ }
|
|
return 0n;
|
|
}
|
|
}
|