fix: resolve Matrix room aliases and encode room IDs in the send path

Matrix notifications failed for anyone who entered a room alias. The send
endpoint only accepts a room ID, and an unresolved alias broke the request
outright: the leading "#" opens a URI fragment, so the PUT landed on
/_matrix/client/v3/rooms/ instead of the send endpoint. Element shows users
the alias, so this is the value most people paste in.

- Resolve "#alias:server" through GET /_matrix/client/v3/directory/room/{alias}
  before sending, per the client-server spec
- Percent-encode the room ID in the send path; ":" was previously sent raw
- Strip trailing slashes from the homeserver URL to avoid "//_matrix/..."

Alias resolution failures return false and log, matching how the provider
already handles send failures.

Closes #3478
This commit is contained in:
egeoztass committed 2026-08-14 17:55:36 +03:00
1 parent f97ca8565a
commit 099eaa38c3
2 files changed
+106 -5

No files matched your search

@@ -38,10 +38,13 @@ export class MatrixProvider extends NotificationProvider {
private sendMessageWithBody = async (notification: Partial<Notification>, body: unknown): Promise<boolean> => {
const { homeserverUrl, accessToken, roomId } = notification;
const txnId = randomUUID();
const url = `${homeserverUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message/${txnId}`;
const baseUrl = this.normalizeHomeserverUrl(homeserverUrl as string);
try {
const resolvedRoomId = await this.resolveRoomId(baseUrl, accessToken as string, roomId as string);
const txnId = randomUUID();
const url = `${baseUrl}/_matrix/client/v3/rooms/${encodeURIComponent(resolvedRoomId)}/send/m.room.message/${txnId}`;
await got.put(url, {
json: body,
headers: {
@@ -63,6 +66,35 @@ export class MatrixProvider extends NotificationProvider {
}
};
private normalizeHomeserverUrl(homeserverUrl: string): string {
return homeserverUrl.replace(/\/+$/, "");
}
/**
* The send endpoint only accepts a room ID (`!id:server`). Element shows users the room
* alias (`#name:server`) instead, and an unresolved alias breaks the request outright:
* the `#` opens a URI fragment, so the PUT lands on /rooms/ rather than the send
* endpoint. Resolve aliases through the directory first, per the client-server spec.
*/
private resolveRoomId = async (baseUrl: string, accessToken: string, roomIdOrAlias: string): Promise<string> => {
if (!roomIdOrAlias.startsWith("#")) {
return roomIdOrAlias;
}
const url = `${baseUrl}/_matrix/client/v3/directory/room/${encodeURIComponent(roomIdOrAlias)}`;
const response = await got.get(url, {
headers: { Authorization: `Bearer ${accessToken}` },
responseType: "json",
...this.gotRequestOptions(),
});
const roomId = (response.body as { room_id?: string })?.room_id;
if (!roomId) {
throw new Error(`Matrix directory returned no room_id for alias ${roomIdOrAlias}`);
}
return roomId;
};
/**
* Build Matrix message from NotificationMessage
* Returns both plain text and HTML formatted versions