From 553c95351da1de46747652376ee398b56a1c6617 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jan 2026 00:42:04 +1300 Subject: [PATCH 1/3] Maybe fix getting invite state This SSS API call should work on Synapse, Tuwunel, and Continuwuity. A fallback via hierarchy is provided for Conduit. --- src/m2d/event-dispatcher.js | 34 +++++------------ src/matrix/api.js | 74 +++++++++++++++++++++++++++++++++---- src/types.d.ts | 1 + 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index eb1fba0..48bff11 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -357,15 +357,7 @@ async event => { await api.ackEvent(event) })) -function getFromInviteRoomState(inviteRoomState, nskey, key) { - if (!Array.isArray(inviteRoomState)) return null - for (const event of inviteRoomState) { - if (event.type === nskey && event.state_key === "") { - return event.content[key] - } - } - return null -} + sync.addTemporaryListener(as, "type:m.space.child", guard("m.space.child", /** @@ -398,24 +390,16 @@ async event => { } // We were invited to a room. We should join, and register the invite details for future reference in web. - let attemptedApiMessage = "According to unsigned invite data." - let inviteRoomState = event.unsigned?.invite_room_state - if (!Array.isArray(inviteRoomState) || inviteRoomState.length === 0) { - try { - inviteRoomState = await api.getInviteState(event.room_id) - attemptedApiMessage = "According to SSS API." - } catch (e) { - attemptedApiMessage = "According to unsigned invite data. SSS API unavailable: " + e.toString() - } + try { + var inviteRoomState = await api.getInviteState(event.room_id, event) + } catch (e) { + console.error(e) + return await api.leaveRoomWithReason(event.room_id, `I wasn't able to find out what this room is. Please report this as a bug. Check console for more details. (${e.toString()})`) } - const name = getFromInviteRoomState(inviteRoomState, "m.room.name", "name") - const topic = getFromInviteRoomState(inviteRoomState, "m.room.topic", "topic") - const avatar = getFromInviteRoomState(inviteRoomState, "m.room.avatar", "url") - const creationType = getFromInviteRoomState(inviteRoomState, "m.room.create", "type") - if (!name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite! (${attemptedApiMessage})`) + if (!inviteRoomState?.name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite.`) await api.joinRoom(event.room_id) - db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, creationType, name, topic, avatar) - if (avatar) utils.getPublicUrlForMxc(avatar) // make sure it's available in the media_proxy allowed URLs + db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar) + if (inviteRoomState.avatar) utils.getPublicUrlForMxc(inviteRoomState.avatar) // make sure it's available in the media_proxy allowed URLs } if (utils.eventSenderIsFromDiscord(event.state_key)) return diff --git a/src/matrix/api.js b/src/matrix/api.js index b71c068..ddb77b5 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -158,20 +158,80 @@ function getStateEventOuter(roomID, type, key) { /** * @param {string} roomID - * @returns {Promise} + * @param {{unsigned?: {invite_room_state?: Ty.Event.InviteStrippedState[]}}} [event] + * @returns {Promise<{name: string?, topic: string?, avatar: string?, type: string?}>} */ -async function getInviteState(roomID) { +async function getInviteState(roomID, event) { + function getFromInviteRoomState(strippedState, nskey, key) { + if (!Array.isArray(strippedState)) return null + for (const event of strippedState) { + if (event.type === nskey && event.state_key === "") { + return event.content[key] + } + } + return null + } + + // Try extracting from event (if passed) + if (Array.isArray(event?.unsigned?.invite_room_state) && event.unsigned.invite_room_state.length) { + return { + name: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.name", "name"), + topic: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.topic", "topic"), + avatar: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.avatar", "url"), + type: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.create", "type") + } + } + + // Try calling sliding sync API and extracting from stripped state /** @type {Ty.R.SSS} */ const root = await mreq.mreq("POST", path("/client/unstable/org.matrix.simplified_msc3575/sync", `@${reg.sender_localpart}:${reg.ooye.server_name}`, {timeout: "0"}), { - room_subscriptions: { - [roomID]: { + lists: { + a: { + ranges: [[0, 999]], timeline_limit: 0, - required_state: [] + required_state: [], + filters: { + is_invite: true + } } } }) - const roomResponse = root.rooms[roomID] - return "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state + + // Extract from sliding sync response if valid (seems to be okay on Synapse, Tuwunel and Continuwuity at time of writing) + if ("lists" in root) { + if (!root.rooms?.[roomID]) { + const e = new Error("Room data unavailable via SSS") + e["data_sss"] = root + throw e + } + + const roomResponse = root.rooms[roomID] + const strippedState = "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state + + return { + name: getFromInviteRoomState(strippedState, "m.room.name", "name"), + topic: getFromInviteRoomState(strippedState, "m.room.topic", "topic"), + avatar: getFromInviteRoomState(strippedState, "m.room.avatar", "url"), + type: getFromInviteRoomState(strippedState, "m.room.create", "type") + } + } + + // Invalid sliding sync response, try alternative (required for Conduit at time of writing) + const hierarchy = await getHierarchy(roomID, {limit: 1}) + if (hierarchy?.rooms?.[0]?.room_id === roomID) { + const room = hierarchy?.rooms?.[0] + return { + name: room.name ?? null, + topic: room.topic ?? null, + avatar: room.avatar_url ?? null, + type: room.room_type + } + } + + const e = new Error("Room data unavailable via SSS/hierarchy") + e["data_sss"] = root + e["data_hierarchy"] = hierarchy + throw e } /** diff --git a/src/types.d.ts b/src/types.d.ts index 951d93c..6ee2eb1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -433,6 +433,7 @@ export namespace R { guest_can_join: boolean join_rule?: string name?: string + topic?: string num_joined_members: number room_id: string room_type?: string From cd2ff3ef6bb4d1b4fddf3e396662979dc02ec556 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jan 2026 00:56:49 +1300 Subject: [PATCH 2/3] Allow SSS API to fail --- src/matrix/api.js | 54 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/matrix/api.js b/src/matrix/api.js index ddb77b5..7e503c2 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -183,38 +183,40 @@ async function getInviteState(roomID, event) { } // Try calling sliding sync API and extracting from stripped state - /** @type {Ty.R.SSS} */ - const root = await mreq.mreq("POST", path("/client/unstable/org.matrix.simplified_msc3575/sync", `@${reg.sender_localpart}:${reg.ooye.server_name}`, {timeout: "0"}), { - lists: { - a: { - ranges: [[0, 999]], - timeline_limit: 0, - required_state: [], - filters: { - is_invite: true + try { + /** @type {Ty.R.SSS} */ + var root = await mreq.mreq("POST", path("/client/unstable/org.matrix.simplified_msc3575/sync", `@${reg.sender_localpart}:${reg.ooye.server_name}`, {timeout: "0"}), { + lists: { + a: { + ranges: [[0, 999]], + timeline_limit: 0, + required_state: [], + filters: { + is_invite: true + } } } - } - }) + }) - // Extract from sliding sync response if valid (seems to be okay on Synapse, Tuwunel and Continuwuity at time of writing) - if ("lists" in root) { - if (!root.rooms?.[roomID]) { - const e = new Error("Room data unavailable via SSS") - e["data_sss"] = root - throw e - } + // Extract from sliding sync response if valid (seems to be okay on Synapse, Tuwunel and Continuwuity at time of writing) + if ("lists" in root) { + if (!root.rooms?.[roomID]) { + const e = new Error("Room data unavailable via SSS") + e["data_sss"] = root + throw e + } - const roomResponse = root.rooms[roomID] - const strippedState = "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state + const roomResponse = root.rooms[roomID] + const strippedState = "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state - return { - name: getFromInviteRoomState(strippedState, "m.room.name", "name"), - topic: getFromInviteRoomState(strippedState, "m.room.topic", "topic"), - avatar: getFromInviteRoomState(strippedState, "m.room.avatar", "url"), - type: getFromInviteRoomState(strippedState, "m.room.create", "type") + return { + name: getFromInviteRoomState(strippedState, "m.room.name", "name"), + topic: getFromInviteRoomState(strippedState, "m.room.topic", "topic"), + avatar: getFromInviteRoomState(strippedState, "m.room.avatar", "url"), + type: getFromInviteRoomState(strippedState, "m.room.create", "type") + } } - } + } catch (e) {} // Invalid sliding sync response, try alternative (required for Conduit at time of writing) const hierarchy = await getHierarchy(roomID, {limit: 1}) From d3feec18cf203092db237cfb24bb195ea6db9e0b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 30 Jan 2026 01:26:24 +1300 Subject: [PATCH 3/3] Redact Authorization header in this case --- src/matrix/mreq.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/matrix/mreq.js b/src/matrix/mreq.js index 9085add..bb59506 100644 --- a/src/matrix/mreq.js +++ b/src/matrix/mreq.js @@ -77,6 +77,7 @@ async function mreq(method, url, bodyIn, extra = {}) { /** @type {any} */ var root = JSON.parse(text) } catch (e) { + delete opts.headers?.["Authorization"] throw new MatrixServerError(text, {baseUrl, url, ...opts}) }