Remove unsound participant casts

By tagging participant behaviors with a type (local vs. remote) we can now tell what kind of participant it will be in a completely type-safe manner.
This commit is contained in:
Robin
2025-12-08 23:06:19 -05:00
parent 47cd343d44
commit a7a3d4e93c
5 changed files with 100 additions and 80 deletions

View File

@@ -5,10 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import {
type LocalParticipant as LocalLivekitParticipant,
type RemoteParticipant as RemoteLivekitParticipant,
} from "livekit-client";
import { type LocalParticipant, type RemoteParticipant } from "livekit-client";
import {
type LivekitTransport,
type CallMembership,
@@ -24,16 +21,24 @@ import { generateItemsWithEpoch } from "../../../utils/observable";
const logger = rootLogger.getChild("[MatrixLivekitMembers]");
/**
* A dynamic participant value with a static tag to tell what kind of
* participant it can be (local vs. remote).
*/
export type TaggedParticipant =
| { type: "local"; value$: Behavior<LocalParticipant | null> }
| { type: "remote"; value$: Behavior<RemoteParticipant | null> };
/**
* Represents a Matrix call member and their associated LiveKit participation.
* `livekitParticipant` can be undefined if the member is not yet connected to the livekit room
* or if it has no livekit transport at all.
*/
export interface MatrixLivekitMember {
export interface MatrixLivekitMember<
ParticipantType extends TaggedParticipant["type"],
> {
membership$: Behavior<CallMembership>;
participant$: Behavior<
LocalLivekitParticipant | RemoteLivekitParticipant | null
>;
participant: TaggedParticipant & { type: ParticipantType };
connection$: Behavior<Connection | null>;
// participantId: string; We do not want a participantId here since it will be generated by the jwt
// TODO decide if we can also drop the userId. Its in the matrix membership anyways.
@@ -61,7 +66,7 @@ export function createMatrixLivekitMembers$({
scope,
membershipsWithTransport$,
connectionManager,
}: Props): Behavior<Epoch<MatrixLivekitMember[]>> {
}: Props): Behavior<Epoch<MatrixLivekitMember<"remote">[]>> {
/**
* Stream of all the call members and their associated livekit data (if available).
*/
@@ -110,12 +115,14 @@ export function createMatrixLivekitMembers$({
logger.debug(
`Updating data$ for participantId: ${participantId}, userId: ${userId}`,
);
const { participant$, ...rest } = scope.splitBehavior(data$);
// will only get called once per `participantId, userId` pair.
// updates to data$ and as a result to displayName$ and mxcAvatarUrl$ are more frequent.
return {
participantId,
userId,
...scope.splitBehavior(data$),
participant: { type: "remote" as const, value$: participant$ },
...rest,
};
},
),