Somewhere to put a call server, for a homeserver that names none

moho joins Element Call sessions including encrypted ones, but only where
the homeserver advertises a focus. On one that does not - which includes a
lot of small self-hosted servers - there was no way to point moho at a
media server by hand, and the call simply had nowhere to go.

A field in the Matrix account tools, seeded from the account and sent only
when it differs, so opening the page does not rewrite a setting nobody
touched. Empty means the homeserver decides again, and the button says
Clear rather than Save when that is what pressing it would do.

Closes #200.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-12 09:51:44 -04:00
co-authored by Claude Opus 5
parent 15caf81869
commit 29b1022323
3 changed files with 52 additions and 1 deletions
+1 -1
Submodule nobilis updated: 03e8bc84ef...e8cba7cae2
@@ -53,6 +53,10 @@ export function MatrixAccountTools({ account }: { account: Account }): JSX.Eleme
const [devices, setDevices] = useState<MatrixDevice[] | null>(null)
const [loading, setLoading] = useState(false)
const [recoveryKey, setRecoveryKey] = useState('')
// Where this account's room calls go when the homeserver names no media
// server of its own. Seeded from the account and only sent when it changes,
// so opening this page does not rewrite a setting nobody touched.
const [rtcFocus, setRtcFocus] = useState(account.rtcFocusUrl ?? '')
const [restoreKey, setRestoreKey] = useState('')
const [busy, setBusy] = useState('')
const [deleting, setDeleting] = useState<string | null>(null)
@@ -107,8 +111,48 @@ export function MatrixAccountTools({ account }: { account: Account }): JSX.Eleme
throw e
})
const rtcFocusChanged = rtcFocus.trim() !== (account.rtcFocusUrl ?? '')
return (
<div className="matrix-tools">
<div className="setting-row">
<div className="setting-text">
<div>Call server</div>
<div className="small muted">
Where this account&rsquo;s room calls go. Most homeservers name one and this can stay
empty; a server that does not leaves calls with nowhere to go until one is given
here.
</div>
</div>
</div>
<div className="device-row">
<Icon name="cell_tower" size={18} />
<div className="field-row setting-text">
<input
className="text-field"
placeholder="https://livekit.example.org"
value={rtcFocus}
onChange={(e) => setRtcFocus(e.target.value)}
/>
</div>
<button
type="button"
className="button subtle"
disabled={!rtcFocusChanged}
onClick={() =>
void rpc('setMatrixRtcFocus', { accountId: account.id, url: rtcFocus.trim() })
.then(() =>
store.toast(
'info',
rtcFocus.trim() ? 'Call server set' : 'Call server cleared - the homeserver decides again'
)
)
.then(() => void store.refreshAccounts())
}
>
{rtcFocus.trim() ? 'Save' : 'Clear'}
</button>
</div>
<div className="setting-row">
<div className="setting-text">
<div>Sessions</div>
+7
View File
@@ -48,6 +48,13 @@ export interface Account {
torProxy?: string
useTor: boolean
hasKeyBackup: boolean
/**
* Matrix only: a media server for room calls, where the homeserver names
* none of its own. Absent on every other service, and on a Matrix account
* that has not been given one - which is the ordinary case, since most
* homeservers do name one.
*/
rtcFocusUrl?: string
}
export interface SneedChatRoom {