mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Join the call as soon as a request to join is accepted
An invite that replaced a request to join was read as somebody else being mid-join. Nobody always is: a host without auto-join, a join that failed, or a fresh load already at that membership left the user waiting with nothing to press but "Cancel request". The lobby now takes such an invite up itself, showing a joining state, and offers a join of the user's own if that fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d78c204ef4
commit
1c8930daa0
@@ -156,7 +156,9 @@
|
|||||||
"invite_only_body": "You need an invite to join this call.",
|
"invite_only_body": "You need an invite to join this call.",
|
||||||
"join_as_guest": "Join as guest",
|
"join_as_guest": "Join as guest",
|
||||||
"join_button": "Join call",
|
"join_button": "Join call",
|
||||||
|
"joining": "Joining…",
|
||||||
"leave_button": "Back to recents",
|
"leave_button": "Back to recents",
|
||||||
|
"request_accepted": "Your request to join was accepted.",
|
||||||
"request_sent": "Request to join sent",
|
"request_sent": "Request to join sent",
|
||||||
"request_sent_body": "You will receive an invite to join the call if your request is accepted."
|
"request_sent_body": "You will receive an invite to join the call if your request is accepted."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
* the lobby offers them. Everything the lobby needs to know about membership.
|
* the lobby offers them. Everything the lobby needs to know about membership.
|
||||||
*/
|
*/
|
||||||
export type LobbyJoinState =
|
export type LobbyJoinState =
|
||||||
/** The user may enter the call right away. */
|
/**
|
||||||
| { kind: "can-join"; join: () => void }
|
* The user may enter the call right away. `notice` is set when they got here
|
||||||
|
* by having a request accepted, but the join it entitles them to failed.
|
||||||
|
*/
|
||||||
|
| { kind: "can-join"; join: () => void; notice?: "request_accepted" }
|
||||||
/**
|
/**
|
||||||
* The room only takes knocks. `error` is set when a previous request failed
|
* The room only takes knocks. `error` is set when a previous request failed
|
||||||
* to send.
|
* to send.
|
||||||
@@ -23,6 +26,8 @@ export type LobbyJoinState =
|
|||||||
}
|
}
|
||||||
/** The request is on its way to the server. */
|
/** The request is on its way to the server. */
|
||||||
| { kind: "sending-request" }
|
| { kind: "sending-request" }
|
||||||
|
/** The user's join is on its way. */
|
||||||
|
| { kind: "joining" }
|
||||||
/**
|
/**
|
||||||
* The request is with the room's moderators. `cancelRequest` is absent while
|
* The request is with the room's moderators. `cancelRequest` is absent while
|
||||||
* a withdrawal is on its way, and where withdrawing is not supported.
|
* a withdrawal is on its way, and where withdrawing is not supported.
|
||||||
|
|||||||
@@ -203,6 +203,22 @@ describe("LobbyView", () => {
|
|||||||
disabled: true,
|
disabled: true,
|
||||||
message: null,
|
message: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
joinState: { kind: "joining" },
|
||||||
|
button: "Joining",
|
||||||
|
disabled: true,
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
joinState: {
|
||||||
|
kind: "can-join",
|
||||||
|
join: () => {},
|
||||||
|
notice: "request_accepted",
|
||||||
|
},
|
||||||
|
button: "Join call",
|
||||||
|
disabled: false,
|
||||||
|
message: "Your request to join was accepted.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
joinState: { kind: "waiting-for-approval" },
|
joinState: { kind: "waiting-for-approval" },
|
||||||
button: "Request to join sent",
|
button: "Request to join sent",
|
||||||
@@ -270,6 +286,16 @@ describe("LobbyView", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does nothing while the join is on its way", async () => {
|
||||||
|
const { getByTestId } = renderLobbyView({
|
||||||
|
joinState: { kind: "joining" },
|
||||||
|
});
|
||||||
|
const button = getByTestId("lobby_joinCall");
|
||||||
|
expect(button).toHaveAttribute("aria-busy", "true");
|
||||||
|
await userEvent.click(button);
|
||||||
|
expect(button).toHaveAttribute("aria-disabled", "true");
|
||||||
|
});
|
||||||
|
|
||||||
it("joins when the join button is pressed", async () => {
|
it("joins when the join button is pressed", async () => {
|
||||||
const join = vi.fn();
|
const join = vi.fn();
|
||||||
const { getByTestId } = renderLobbyView({
|
const { getByTestId } = renderLobbyView({
|
||||||
|
|||||||
+11
-2
@@ -236,6 +236,7 @@ export const LobbyView: FC<Props> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
case "sending-request":
|
case "sending-request":
|
||||||
|
case "joining":
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={classNames(styles.join, buttonStyles.rotate)}
|
className={classNames(styles.join, buttonStyles.rotate)}
|
||||||
@@ -245,7 +246,11 @@ export const LobbyView: FC<Props> = ({
|
|||||||
aria-busy
|
aria-busy
|
||||||
data-testid="lobby_joinCall"
|
data-testid="lobby_joinCall"
|
||||||
>
|
>
|
||||||
{t("lobby.ask_to_join")}
|
{t(
|
||||||
|
joinState.kind === "joining"
|
||||||
|
? "lobby.joining"
|
||||||
|
: "lobby.ask_to_join",
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
case "waiting-for-approval":
|
case "waiting-for-approval":
|
||||||
@@ -269,6 +274,10 @@ export const LobbyView: FC<Props> = ({
|
|||||||
|
|
||||||
const joinMessage = ((): ReactNode => {
|
const joinMessage = ((): ReactNode => {
|
||||||
switch (joinState.kind) {
|
switch (joinState.kind) {
|
||||||
|
case "can-join":
|
||||||
|
return joinState.notice === undefined ? null : (
|
||||||
|
<Text size="sm">{t("lobby.request_accepted")}</Text>
|
||||||
|
);
|
||||||
case "can-ask-to-join":
|
case "can-ask-to-join":
|
||||||
return joinState.error === undefined ? null : (
|
return joinState.error === undefined ? null : (
|
||||||
<Text size="sm">{t("error.generic")}</Text>
|
<Text size="sm">{t("error.generic")}</Text>
|
||||||
@@ -317,8 +326,8 @@ export const LobbyView: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
case "not-allowed":
|
case "not-allowed":
|
||||||
return <Text size="sm">{t("lobby.invite_only_body")}</Text>;
|
return <Text size="sm">{t("lobby.invite_only_body")}</Text>;
|
||||||
case "can-join":
|
|
||||||
case "sending-request":
|
case "sending-request":
|
||||||
|
case "joining":
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -236,6 +236,53 @@ describe("useLoadGroupCall in the standalone app", () => {
|
|||||||
expect(client.waitUntilRoomReadyForGroupCalls).toHaveBeenCalledWith(roomId);
|
expect(client.waitUntilRoomReadyForGroupCalls).toHaveBeenCalledWith(roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("joins straight away when a request was accepted before this load", async () => {
|
||||||
|
const spec: RoomSpec = {
|
||||||
|
membership: KnownMembership.Invite,
|
||||||
|
prevMembership: KnownMembership.Knock,
|
||||||
|
};
|
||||||
|
const room = mockRoom(spec);
|
||||||
|
const client = mockClient({
|
||||||
|
getRoom: vi.fn().mockReturnValue(room),
|
||||||
|
joinRoom: vi.fn().mockResolvedValue(room),
|
||||||
|
});
|
||||||
|
const { result } = renderLoad(client);
|
||||||
|
await waitForJoinState(result, "joining");
|
||||||
|
expect(client.joinRoom).toHaveBeenCalledWith(roomId, { viaServers });
|
||||||
|
spec.membership = KnownMembership.Join;
|
||||||
|
emitMembership(client, room, KnownMembership.Join, KnownMembership.Invite);
|
||||||
|
await waitFor(() => expect(result.current.kind).toBe("loaded"));
|
||||||
|
expect(client.getRoomSummary).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("offers a join of our own when an accepted request cannot be taken up", async () => {
|
||||||
|
const error = vi.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
const room = mockRoom({
|
||||||
|
membership: KnownMembership.Invite,
|
||||||
|
prevMembership: KnownMembership.Knock,
|
||||||
|
});
|
||||||
|
const client = mockClient({
|
||||||
|
getRoom: vi.fn().mockReturnValue(room),
|
||||||
|
joinRoom: vi.fn().mockRejectedValue(new Error("offline")),
|
||||||
|
});
|
||||||
|
const { result } = renderLoad(client);
|
||||||
|
expect(await waitForJoinState(result, "can-join")).toMatchObject({
|
||||||
|
notice: "request_accepted",
|
||||||
|
});
|
||||||
|
expect(error).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("joins an invite that answers no request without a lobby", async () => {
|
||||||
|
const room = mockRoom({ membership: KnownMembership.Invite });
|
||||||
|
const client = mockClient({
|
||||||
|
getRoom: vi.fn().mockReturnValue(room),
|
||||||
|
joinRoom: vi.fn().mockResolvedValue(room),
|
||||||
|
});
|
||||||
|
const { result } = renderLoad(client);
|
||||||
|
await waitFor(() => expect(result.current.kind).toBe("loaded"));
|
||||||
|
expect(client.joinRoom).toHaveBeenCalledWith(roomId, { viaServers });
|
||||||
|
});
|
||||||
|
|
||||||
it("shows a declined request in the lobby", async () => {
|
it("shows a declined request in the lobby", async () => {
|
||||||
const spec: RoomSpec = { membership: KnownMembership.Knock };
|
const spec: RoomSpec = { membership: KnownMembership.Knock };
|
||||||
const room = mockRoom(spec);
|
const room = mockRoom(spec);
|
||||||
@@ -366,7 +413,7 @@ describe("useLoadGroupCall as a widget", () => {
|
|||||||
membership: KnownMembership.Invite,
|
membership: KnownMembership.Invite,
|
||||||
prevMembership: KnownMembership.Knock,
|
prevMembership: KnownMembership.Knock,
|
||||||
},
|
},
|
||||||
"waiting-for-approval",
|
"joining",
|
||||||
],
|
],
|
||||||
["a plain invite", { membership: KnownMembership.Invite }, "can-join"],
|
["a plain invite", { membership: KnownMembership.Invite }, "can-join"],
|
||||||
[
|
[
|
||||||
@@ -416,7 +463,12 @@ describe("useLoadGroupCall as a widget", () => {
|
|||||||
const client = mockClient({
|
const client = mockClient({
|
||||||
getRoom: vi.fn().mockReturnValue(mockRoom(spec)),
|
getRoom: vi.fn().mockReturnValue(mockRoom(spec)),
|
||||||
});
|
});
|
||||||
const { result } = renderLoad(client, host());
|
// A request the host never answers, so that the state a lobby opens on is
|
||||||
|
// the one under test.
|
||||||
|
const { result } = renderLoad(
|
||||||
|
client,
|
||||||
|
host(vi.fn().mockReturnValue(new Promise(() => {}))),
|
||||||
|
);
|
||||||
await waitForJoinState(result, expected as LobbyJoinState["kind"]);
|
await waitForJoinState(result, expected as LobbyJoinState["kind"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -493,6 +545,90 @@ describe("useLoadGroupCall as a widget", () => {
|
|||||||
await waitForJoinState(result, "not-allowed");
|
await waitForJoinState(result, "not-allowed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("asks the host to join us as soon as a request is accepted", async () => {
|
||||||
|
const changeMembership = vi.fn().mockReturnValue(new Promise(() => {}));
|
||||||
|
const spec: RoomSpec = { membership: KnownMembership.Knock };
|
||||||
|
const room = mockRoom(spec);
|
||||||
|
const client = mockClient({ getRoom: vi.fn().mockReturnValue(room) });
|
||||||
|
const { result } = renderLoad(client, host(changeMembership));
|
||||||
|
await waitForJoinState(result, "waiting-for-approval");
|
||||||
|
spec.membership = KnownMembership.Invite;
|
||||||
|
emitMembership(client, room, KnownMembership.Invite, KnownMembership.Knock);
|
||||||
|
await waitForJoinState(result, "joining");
|
||||||
|
expect(changeMembership).toHaveBeenCalledWith({ action: "join" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enters the call when the host reports us joined", async () => {
|
||||||
|
const spec: RoomSpec = {
|
||||||
|
membership: KnownMembership.Invite,
|
||||||
|
prevMembership: KnownMembership.Knock,
|
||||||
|
};
|
||||||
|
const room = mockRoom(spec);
|
||||||
|
const client = mockClient({ getRoom: vi.fn().mockReturnValue(room) });
|
||||||
|
// The host joins us as it answers, so no membership event of our own
|
||||||
|
// follows the reply.
|
||||||
|
const joinAsHost = vi.fn(async (): Promise<Membership> => {
|
||||||
|
spec.membership = KnownMembership.Join;
|
||||||
|
return await Promise.resolve(KnownMembership.Join);
|
||||||
|
});
|
||||||
|
const { result } = renderLoad(client, host(joinAsHost));
|
||||||
|
await waitFor(() => expect(result.current.kind).toBe("loaded"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stays in the call when the host answers our join late", async () => {
|
||||||
|
const error = vi.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
let refuse!: (error: Error) => void;
|
||||||
|
const spec: RoomSpec = {
|
||||||
|
membership: KnownMembership.Invite,
|
||||||
|
prevMembership: KnownMembership.Knock,
|
||||||
|
};
|
||||||
|
const room = mockRoom(spec);
|
||||||
|
const client = mockClient({ getRoom: vi.fn().mockReturnValue(room) });
|
||||||
|
const { result } = renderLoad(
|
||||||
|
client,
|
||||||
|
host(
|
||||||
|
vi.fn().mockReturnValue(
|
||||||
|
new Promise((_resolve, reject) => {
|
||||||
|
refuse = reject;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await waitForJoinState(result, "joining");
|
||||||
|
spec.membership = KnownMembership.Join;
|
||||||
|
emitMembership(client, room, KnownMembership.Join, KnownMembership.Invite);
|
||||||
|
await waitFor(() => expect(result.current.kind).toBe("loaded"));
|
||||||
|
const loaded = result.current;
|
||||||
|
refuse(new Error("Request timed out"));
|
||||||
|
await waitFor(() => expect(error).toHaveBeenCalled());
|
||||||
|
expect(result.current).toBe(loaded);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("offers a join of our own when the host cannot answer", async () => {
|
||||||
|
const error = vi.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
const changeMembership = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue(new Error("Request timed out"));
|
||||||
|
const client = mockClient({
|
||||||
|
getRoom: vi.fn().mockReturnValue(
|
||||||
|
mockRoom({
|
||||||
|
membership: KnownMembership.Invite,
|
||||||
|
prevMembership: KnownMembership.Knock,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const { result } = renderLoad(client, host(changeMembership));
|
||||||
|
const canJoin = await waitForJoinState(result, "can-join");
|
||||||
|
expect(canJoin).toMatchObject({ notice: "request_accepted" });
|
||||||
|
expect(error).toHaveBeenCalled();
|
||||||
|
act(() => canJoin.join());
|
||||||
|
await waitForJoinState(result, "joining");
|
||||||
|
expect(await waitForJoinState(result, "can-join")).toMatchObject({
|
||||||
|
notice: "request_accepted",
|
||||||
|
});
|
||||||
|
expect(changeMembership).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves when the host joined us before we could listen", async () => {
|
it("resolves when the host joined us before we could listen", async () => {
|
||||||
const room = mockRoom({ membership: KnownMembership.Knock });
|
const room = mockRoom({ membership: KnownMembership.Knock });
|
||||||
(room as { getMyMembership: () => Membership }).getMyMembership = vi
|
(room as { getMyMembership: () => Membership }).getMyMembership = vi
|
||||||
|
|||||||
@@ -97,10 +97,18 @@ export class CallTerminatedMessage extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The membership the local user's current one replaced, if any. */
|
||||||
|
function previousMembership(room: Room): Membership | undefined {
|
||||||
|
return room.currentState
|
||||||
|
.getStateEvents(EventType.RoomMember, room.myUserId)
|
||||||
|
?.getPrevContent().membership as Membership | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** The join state a lobby opens on, before the user acts on it. */
|
/** The join state a lobby opens on, before the user acts on it. */
|
||||||
type LobbyEntry =
|
type LobbyEntry =
|
||||||
| "can-join"
|
| "can-join"
|
||||||
| "can-ask-to-join"
|
| "can-ask-to-join"
|
||||||
|
| "joining"
|
||||||
| "waiting-for-approval"
|
| "waiting-for-approval"
|
||||||
| "denied"
|
| "denied"
|
||||||
| "banned"
|
| "banned"
|
||||||
@@ -196,22 +204,31 @@ export const useLoadGroupCall = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve once the local user has joined the room, including when they
|
* Watch for the local user joining the room. `check` re-tests the room as
|
||||||
* already have: the host may have joined us before we could listen.
|
* it stands, for a join that happened without an event we saw, such as one
|
||||||
|
* the host had already applied.
|
||||||
*/
|
*/
|
||||||
const waitForJoin = async (roomId: string): Promise<Room> =>
|
const watchForJoin = (
|
||||||
await new Promise<Room>((resolve) => {
|
roomId: string,
|
||||||
const reached = (room: Room | null): boolean => {
|
): { reached: Promise<Room>; check: () => void } => {
|
||||||
|
let check = (): void => {};
|
||||||
|
const reached = new Promise<Room>((resolve) => {
|
||||||
|
const test = (room: Room | null): boolean => {
|
||||||
if (room?.getMyMembership() !== KnownMembership.Join) return false;
|
if (room?.getMyMembership() !== KnownMembership.Join) return false;
|
||||||
activeRoom.current = room;
|
activeRoom.current = room;
|
||||||
resolve(room);
|
resolve(room);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const off = onMyMembership((room) => {
|
const off = onMyMembership((room) => {
|
||||||
if (room.roomId === roomId && reached(room)) off();
|
if (room.roomId === roomId && test(room)) off();
|
||||||
});
|
});
|
||||||
if (reached(client.getRoom(roomId))) off();
|
check = (): void => {
|
||||||
|
if (test(client.getRoom(roomId))) off();
|
||||||
|
};
|
||||||
|
check();
|
||||||
});
|
});
|
||||||
|
return { reached, check };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the lobby and resolve once the local user has joined. Denial, ban
|
* Show the lobby and resolve once the local user has joined. Denial, ban
|
||||||
@@ -232,9 +249,16 @@ export const useLoadGroupCall = (
|
|||||||
changeMembershipWithClient(client, roomId, viaServers);
|
changeMembershipWithClient(client, roomId, viaServers);
|
||||||
let requestInFlight = false;
|
let requestInFlight = false;
|
||||||
let withdrawing = false;
|
let withdrawing = false;
|
||||||
|
let entered = false;
|
||||||
|
|
||||||
|
// Listening before anything is sent, so that a join landing while a
|
||||||
|
// request is in flight is not missed.
|
||||||
|
const { reached, check } = watchForJoin(roomId);
|
||||||
|
|
||||||
|
/** Sets the lobby state, unless the user is already through the lobby. */
|
||||||
const setLobby = (joinState: LobbyJoinState): void => {
|
const setLobby = (joinState: LobbyJoinState): void => {
|
||||||
if (!signal.aborted) setState({ kind: "lobby", room, joinState });
|
if (!entered && !signal.aborted)
|
||||||
|
setState({ kind: "lobby", room, joinState });
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForApproval = (): void =>
|
const waitForApproval = (): void =>
|
||||||
@@ -245,15 +269,24 @@ export const useLoadGroupCall = (
|
|||||||
|
|
||||||
const canJoin = (): void => setLobby({ kind: "can-join", join });
|
const canJoin = (): void => setLobby({ kind: "can-join", join });
|
||||||
|
|
||||||
|
/** The request was accepted, but the join it entitles us to failed. */
|
||||||
|
const acceptedButNotJoined = (): void =>
|
||||||
|
setLobby({
|
||||||
|
kind: "can-join",
|
||||||
|
join: joinOnceAccepted,
|
||||||
|
notice: "request_accepted",
|
||||||
|
});
|
||||||
|
|
||||||
const onRequestResult = (
|
const onRequestResult = (
|
||||||
membership: Membership,
|
membership: Membership,
|
||||||
operation: string,
|
operation: string,
|
||||||
onFailure: () => void,
|
onFailure: () => void,
|
||||||
): void => {
|
): void => {
|
||||||
requestInFlight = false;
|
requestInFlight = false;
|
||||||
// A join resolves the promise this lobby is parked on, so there is
|
// A membership the room already holds reaches us in the reply rather
|
||||||
// nothing left to show.
|
// than in an event of its own, so the join this lobby is parked on is
|
||||||
if (membership === KnownMembership.Join) return;
|
// re-tested here.
|
||||||
|
if (membership === KnownMembership.Join) return check();
|
||||||
if (membership === KnownMembership.Knock) return waitForApproval();
|
if (membership === KnownMembership.Knock) return waitForApproval();
|
||||||
logger.error(
|
logger.error(
|
||||||
`${operation} on ${roomId} left us with membership ${membership}`,
|
`${operation} on ${roomId} left us with membership ${membership}`,
|
||||||
@@ -284,12 +317,34 @@ export const useLoadGroupCall = (
|
|||||||
const join = (): void => {
|
const join = (): void => {
|
||||||
if (requestInFlight) return;
|
if (requestInFlight) return;
|
||||||
requestInFlight = true;
|
requestInFlight = true;
|
||||||
|
setLobby({ kind: "joining" });
|
||||||
changeMembership({ action: "join" }).then(
|
changeMembership({ action: "join" }).then(
|
||||||
(membership) => onRequestResult(membership, "Joining", canJoin),
|
(membership) => onRequestResult(membership, "Joining", canJoin),
|
||||||
(error) => onRequestError(error, "Joining", canJoin),
|
(error) => onRequestError(error, "Joining", canJoin),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Takes up the invite an accepted request has earned us. */
|
||||||
|
const joinOnceAccepted = (): void => {
|
||||||
|
if (requestInFlight) return;
|
||||||
|
requestInFlight = true;
|
||||||
|
setLobby({ kind: "joining" });
|
||||||
|
changeMembership({ action: "join" }).then(
|
||||||
|
(membership) =>
|
||||||
|
onRequestResult(
|
||||||
|
membership,
|
||||||
|
"Joining once accepted",
|
||||||
|
acceptedButNotJoined,
|
||||||
|
),
|
||||||
|
(error) =>
|
||||||
|
onRequestError(
|
||||||
|
error,
|
||||||
|
"Joining once accepted",
|
||||||
|
acceptedButNotJoined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const askToJoin = (reason?: string): void => {
|
const askToJoin = (reason?: string): void => {
|
||||||
if (requestInFlight) return;
|
if (requestInFlight) return;
|
||||||
requestInFlight = true;
|
requestInFlight = true;
|
||||||
@@ -320,18 +375,10 @@ export const useLoadGroupCall = (
|
|||||||
activeRoom.current = changed;
|
activeRoom.current = changed;
|
||||||
switch (membership) {
|
switch (membership) {
|
||||||
case KnownMembership.Invite:
|
case KnownMembership.Invite:
|
||||||
// A host that changes memberships on our behalf also performs the
|
// An invite that replaced a request is ours to take up: nothing
|
||||||
// join an accepted request entitles us to.
|
// else is going to turn it into a join.
|
||||||
if (prevMembership !== KnownMembership.Knock) canJoin();
|
if (prevMembership === KnownMembership.Knock) joinOnceAccepted();
|
||||||
else if (hostBridge.changeMembership === undefined)
|
else canJoin();
|
||||||
changeMembership({ action: "join" }).then(
|
|
||||||
() => logger.info(`Joined ${roomId} once accepted`),
|
|
||||||
(error: unknown) =>
|
|
||||||
logger.error(
|
|
||||||
`Joining ${roomId} once accepted failed`,
|
|
||||||
error,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case KnownMembership.Ban:
|
case KnownMembership.Ban:
|
||||||
setLobby({ kind: "banned", reason: leaveReason() });
|
setLobby({ kind: "banned", reason: leaveReason() });
|
||||||
@@ -357,6 +404,9 @@ export const useLoadGroupCall = (
|
|||||||
case "can-ask-to-join":
|
case "can-ask-to-join":
|
||||||
canAskToJoin();
|
canAskToJoin();
|
||||||
break;
|
break;
|
||||||
|
case "joining":
|
||||||
|
joinOnceAccepted();
|
||||||
|
break;
|
||||||
case "waiting-for-approval":
|
case "waiting-for-approval":
|
||||||
waitForApproval();
|
waitForApproval();
|
||||||
break;
|
break;
|
||||||
@@ -371,7 +421,8 @@ export const useLoadGroupCall = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const joined = await waitForJoin(roomId);
|
const joined = await reached;
|
||||||
|
entered = true;
|
||||||
offTransitions();
|
offTransitions();
|
||||||
return joined;
|
return joined;
|
||||||
};
|
};
|
||||||
@@ -384,16 +435,13 @@ export const useLoadGroupCall = (
|
|||||||
room: Room,
|
room: Room,
|
||||||
membership: Membership | undefined,
|
membership: Membership | undefined,
|
||||||
): LobbyEntry => {
|
): LobbyEntry => {
|
||||||
const prevMembership = room.currentState
|
const prevMembership = previousMembership(room);
|
||||||
.getStateEvents(EventType.RoomMember, room.myUserId)
|
|
||||||
?.getPrevContent().membership as Membership | undefined;
|
|
||||||
|
|
||||||
if (membership === KnownMembership.Ban) return "banned";
|
if (membership === KnownMembership.Ban) return "banned";
|
||||||
if (membership === KnownMembership.Knock) return "waiting-for-approval";
|
if (membership === KnownMembership.Knock) return "waiting-for-approval";
|
||||||
if (membership === KnownMembership.Invite)
|
if (membership === KnownMembership.Invite)
|
||||||
// An invite that replaced a request means the host is mid-join.
|
|
||||||
return prevMembership === KnownMembership.Knock
|
return prevMembership === KnownMembership.Knock
|
||||||
? "waiting-for-approval"
|
? "joining"
|
||||||
: "can-join";
|
: "can-join";
|
||||||
if (prevMembership === KnownMembership.Knock) return "denied";
|
if (prevMembership === KnownMembership.Knock) return "denied";
|
||||||
|
|
||||||
@@ -457,9 +505,11 @@ export const useLoadGroupCall = (
|
|||||||
|
|
||||||
if (room && membership === KnownMembership.Ban)
|
if (room && membership === KnownMembership.Ban)
|
||||||
return await enterFromLobby(preJoinRoomInfoFromRoom(room), "banned");
|
return await enterFromLobby(preJoinRoomInfoFromRoom(room), "banned");
|
||||||
if (membership === KnownMembership.Invite)
|
if (room && membership === KnownMembership.Invite)
|
||||||
return await readyForGroupCalls(
|
return await readyForGroupCalls(
|
||||||
await client.joinRoom(roomId, { viaServers }),
|
previousMembership(room) === KnownMembership.Knock
|
||||||
|
? await enterFromLobby(preJoinRoomInfoFromRoom(room), "joining")
|
||||||
|
: await client.joinRoom(roomId, { viaServers }),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the room does not exist we first search for it with viaServers
|
// If the room does not exist we first search for it with viaServers
|
||||||
|
|||||||
Reference in New Issue
Block a user