Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
d3feec18cf Redact Authorization header in this case 2026-01-30 01:26:24 +13:00
cd2ff3ef6b Allow SSS API to fail 2026-01-30 00:56:49 +13:00
553c95351d Maybe fix getting invite state
This SSS API call should work on Synapse, Tuwunel, and Continuwuity.
A fallback via hierarchy is provided for Conduit.
2026-01-30 00:42:04 +13:00
4 changed files with 84 additions and 36 deletions

View file

@ -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."
var inviteRoomState = await api.getInviteState(event.room_id, event)
} catch (e) {
attemptedApiMessage = "According to unsigned invite data. SSS API unavailable: " + e.toString()
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

View file

@ -158,20 +158,82 @@ function getStateEventOuter(roomID, type, key) {
/**
* @param {string} roomID
* @returns {Promise<Ty.Event.InviteStrippedState[]>}
* @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
try {
/** @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]: {
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: []
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
}
const roomResponse = root.rooms[roomID]
return "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state
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")
}
}
} catch (e) {}
// 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
}
/**

View file

@ -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})
}

1
src/types.d.ts vendored
View file

@ -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