Files
cinny/src/app/components/create-room/utils.ts
T
Lotus CIandClaude Opus 5.5 a6c7333ea3
CI / Build & Quality Checks (pull_request) Successful in 2m58s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 14m30s
fix(rooms): members can start and join calls in new private rooms (#328)
createRoom granted org.matrix.msc3401.call.member at user level only for
voice rooms. Every other room fell back to Synapse's state_default (50),
so in a private group room (where Lotus shows the call button and rings)
only admins could start a call; members saw "1 Live · Join" and then
"You don't have permission to join!". All new non-space rooms now get
call.member: 0 (spaces keep their own override).

Verified headless through Create Room → Private: before, no call.member
entry (state_default 50); now 0. Existing rooms are unchanged; an admin
can set it in room permissions.

Fixes #328

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-17 18:09:33 -04:00

172 lines
4.2 KiB
TypeScript

import {
ICreateRoomOpts,
ICreateRoomStateEvent,
JoinRule,
MatrixClient,
RestrictedAllowType,
Room,
} from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { RoomType, StateEvent } from '../../../types/matrix/room';
import { sendStateEvent } from '../../utils/room';
import { getViaServers } from '../../plugins/via-servers';
import { getMxIdServer } from '../../utils/matrix';
import { CreateRoomAccess } from './types';
export const createRoomCreationContent = (
type: RoomType | undefined,
allowFederation: boolean,
additionalCreators: string[] | undefined,
): object => {
const content: Record<string, any> = {};
if (typeof type === 'string') {
content.type = type;
}
if (allowFederation === false) {
content['m.federate'] = false;
}
if (Array.isArray(additionalCreators)) {
content.additional_creators = additionalCreators;
}
return content;
};
export const createRoomJoinRulesState = (
access: CreateRoomAccess,
parent: Room | undefined,
knock: boolean,
) => {
let content: RoomJoinRulesEventContent = {
join_rule: knock ? JoinRule.Knock : JoinRule.Invite,
};
if (access === CreateRoomAccess.Public) {
content = {
join_rule: JoinRule.Public,
};
}
if (access === CreateRoomAccess.Restricted && parent) {
content = {
join_rule: knock ? ('knock_restricted' as JoinRule) : JoinRule.Restricted,
allow: [
{
type: RestrictedAllowType.RoomMembership,
room_id: parent.roomId,
},
],
};
}
return {
type: StateEvent.RoomJoinRules,
state_key: '',
content,
};
};
export const createRoomParentState = (parent: Room) => ({
type: StateEvent.SpaceParent,
state_key: parent.roomId,
content: {
canonical: true,
via: getViaServers(parent),
},
});
const createSpacePowerLevelsOverride = () => ({
events_default: 50,
});
export const createRoomEncryptionState = () => ({
type: 'm.room.encryption',
state_key: '',
content: {
algorithm: 'm.megolm.v1.aes-sha2',
},
});
export const createRoomCallState = () => ({
type: 'org.matrix.msc3401.call',
state_key: '',
content: {},
});
export const createVoiceRoomPowerLevelsOverride = () => ({
events: {
[StateEvent.GroupCallMemberPrefix]: 0,
},
});
export type CreateRoomData = {
version: string;
type?: RoomType;
parent?: Room;
access: CreateRoomAccess;
name: string;
topic?: string;
aliasLocalPart?: string;
encryption?: boolean;
knock: boolean;
allowFederation: boolean;
additionalCreators?: string[];
};
export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promise<string> => {
const initialState: ICreateRoomStateEvent[] = [];
if (data.encryption) {
initialState.push(createRoomEncryptionState());
}
if (data.parent) {
initialState.push(createRoomParentState(data.parent));
}
if (data.type === RoomType.Call) {
initialState.push(createRoomCallState());
}
initialState.push(createRoomJoinRulesState(data.access, data.parent, data.knock));
const options: ICreateRoomOpts = {
room_version: data.version,
name: data.name,
topic: data.topic,
room_alias_name: data.aliasLocalPart,
creation_content: createRoomCreationContent(
data.type,
data.allowFederation,
data.additionalCreators,
),
// [Gitea #328] Every non-space room gets call.member at user level, not just
// voice rooms: the call button is shown in private group rooms too, and with
// Synapse's state_default (50) only admins could start or join a call there
// ("You don't have permission to join!").
power_level_content_override: createVoiceRoomPowerLevelsOverride(),
initial_state: initialState,
};
if (data.type === RoomType.Space) {
options.power_level_content_override = createSpacePowerLevelsOverride();
}
const result = await mx.createRoom(options);
if (data.parent) {
await sendStateEvent(
mx,
data.parent.roomId,
StateEvent.SpaceChild,
{
auto_join: false,
suggested: false,
via: [getMxIdServer(mx.getUserId() ?? '') ?? ''],
},
result.room_id,
);
}
return result.room_id;
};