make the transport discovery lk edgecase a EC exclusive workaround

Dont add it to the sdk.
This commit is contained in:
Timo K.
2026-10-14 16:40:27 +02:00
parent 8638d4f159
commit 3b763d87f2
12 changed files with 206 additions and 201 deletions
@@ -8,8 +8,7 @@ Please see LICENSE in the repository root for full details.
import { type Logger } from "matrix-js-sdk/lib/logger";
import {
type CallMembership,
getRemoteLivekitPublishCandidates,
type LivekitPublishCandidate,
isUnstableLivekitTransport,
type Transport,
} from "matrix-js-sdk/lib/matrixrtc";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
@@ -30,15 +29,15 @@ export interface RemotePublishTransportParams {
/**
* The SFU of another member that its homeserver lets us publish on, as the
* transport to advertise, or null where no member's SFU will do. Candidates
* are tried oldest membership first, and each one is checked by asking for a
* transport to advertise, or null where none does. Candidates are tried
* oldest membership first, each SFU once, and each is checked by asking for a
* publisher token through our homeserver (MSC4195): a homeserver may restrict
* remote publishers, which shows as a token without the publish grant.
*/
export async function findRemotePublishTransport({
export async function findRemotePublishLivekitTransport({
client,
memberships,
ownMembershipIdentity,
ownMembershipIdentity: own,
roomId,
slotId,
logger,
@@ -46,45 +45,51 @@ export async function findRemotePublishTransport({
logger.info(
"No local transport available, looking for another member's SFU to publish on",
);
const candidates = getRemoteLivekitPublishCandidates(
memberships,
ownMembershipIdentity,
);
for (const candidate of candidates) {
const outcome = await tryCandidate(candidate);
if (outcome === "usable") return candidate.transport;
if (outcome === "noCsApi") return null;
const tried = new Set<string>();
const byAge = [...memberships].sort((a, b) => a.createdTs() - b.createdTs());
for (const membership of byAge) {
if (
membership.userId === own.userId &&
membership.deviceId === own.deviceId
)
continue;
for (const transport of membership.transports) {
// Publishing remotely goes through the client-server API, which a JWT
// service cannot stand in for, so only a `url` qualifies
if (!isUnstableLivekitTransport(transport) || !("url" in transport))
continue;
const { url } = transport;
const serverName = membership.getTransportServerName(transport);
const key = JSON.stringify([url, serverName]);
if (tried.has(key)) continue;
tried.add(key);
try {
const { jwt } = await csApi.getSFUConfig({
client,
membership: own,
url,
serverName,
roomId,
slotId,
});
if (decodeSFUJWTPayload(jwt).video.canPublish) {
logger.info(`Publishing on remote SFU ${url} of ${serverName}`);
return { type: "livekit", url, server_name: serverName };
}
logger.info(
`${serverName} does not let us publish on ${url}, skipping`,
);
} catch (e) {
if (isCsApiUnavailable(e)) {
// Without the endpoint no further candidate can work
logger.warn(
`Cannot use the MSC4195 get_token endpoint (${e}), so no remote SFU can be used`,
);
return null;
}
logger.warn(`Failed to get a token for ${url} of ${serverName}:`, e);
}
}
}
return null;
/** "noCsApi" ends the search: without the endpoint no candidate can work. */
async function tryCandidate({
url,
serverName,
}: LivekitPublishCandidate): Promise<"usable" | "unusable" | "noCsApi"> {
try {
const { jwt } = await csApi.getSFUConfig({
client,
membership: ownMembershipIdentity,
url,
serverName,
roomId,
slotId,
});
if (decodeSFUJWTPayload(jwt).video.canPublish) {
logger.info(`Publishing on remote SFU ${url} of ${serverName}`);
return "usable";
}
logger.info(`${serverName} does not let us publish on ${url}, skipping`);
} catch (e) {
if (isCsApiUnavailable(e)) {
logger.warn(
`Cannot use the MSC4195 get_token endpoint (${e}), so no remote SFU can be used`,
);
return "noCsApi";
}
logger.warn(`Failed to get a token for ${url} of ${serverName}:`, e);
}
return "unusable";
}
}