Merge branch 'livekit' into toger5/test-with-new-membershipmanager

This commit is contained in:
Timo
2025-03-07 11:21:30 +01:00
9 changed files with 79 additions and 33 deletions

17
.github/workflows/blocked.yaml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Prevent blocked
on:
pull_request:
types: [opened, labeled, unlabeled]
jobs:
prevent-blocked:
name: Prevent blocked
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Add notice
uses: actions/github-script@v7
if: contains(github.event.pull_request.labels.*.name, 'X-Blocked')
with:
script: |
core.setFailed("PR has been labeled with X-Blocked; it cannot be merged.");

View File

@@ -8,26 +8,26 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
/> />
<title><%- title %></title> <title><%- brand %></title>
<script> <script>
window.global = window; window.global = window;
</script> </script>
<!-- Open graph meta tags --> <!-- Open graph meta tags -->
<meta property="og:title" content="<%- title %>" /> <meta property="og:title" content="<%- brand %>" />
<meta <meta
property="og:description" property="og:description"
content="You're invited to join a call on Element Call" content="You're invited to join a call on <%- brand %>"
/> />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="favicon.png" /> <meta property="og:image" content="favicon.png" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="<%- title %>" /> <meta name="twitter:title" content="<%- brand %>" />
<meta <meta
name="twitter:description" name="twitter:description"
content="You're invited to join a call on Element Call" content="You're invited to join a call on <%- brand %>"
/> />
<meta name="twitter:image" content="favicon.png" /> <meta name="twitter:image" content="favicon.png" />
</head> </head>

View File

@@ -110,8 +110,8 @@ describe("UrlParams", () => {
}); });
describe("returnToLobby", () => { describe("returnToLobby", () => {
it("is true in SPA mode", () => { it("is false in SPA mode", () => {
expect(getUrlParams("?returnToLobby=false").returnToLobby).toBe(true); expect(getUrlParams("?returnToLobby=true").returnToLobby).toBe(false);
}); });
it("defaults to false in widget mode", () => { it("defaults to false in widget mode", () => {

View File

@@ -264,7 +264,9 @@ export const getUrlParams = (
"skipLobby", "skipLobby",
isWidget && intent === UserIntent.StartNewCall, isWidget && intent === UserIntent.StartNewCall,
), ),
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : true, // In SPA mode the user should always exit to the home screen when hanging
// up, rather than being sent back to the lobby
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : false,
theme: parser.getParam("theme"), theme: parser.getParam("theme"),
viaServers: !isWidget ? parser.getParam("viaServers") : null, viaServers: !isWidget ? parser.getParam("viaServers") : null,
homeserver: !isWidget ? parser.getParam("homeserver") : null, homeserver: !isWidget ? parser.getParam("homeserver") : null,

View File

@@ -539,9 +539,7 @@ export const GroupCallView: FC<Props> = ({
} }
} else if (left && widget !== null) { } else if (left && widget !== null) {
// Left in widget mode: // Left in widget mode:
if (!returnToLobby) { body = returnToLobby ? lobbyView : null;
body = null;
}
} else if (preload || skipLobby) { } else if (preload || skipLobby) {
body = null; body = null;
} else { } else {

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/ */
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { expect, test, vi } from "vitest"; import { expect, onTestFinished, test, vi } from "vitest";
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
import EventEmitter from "events"; import EventEmitter from "events";
@@ -15,11 +15,17 @@ import { mockConfig } from "./utils/test";
import { ElementWidgetActions, widget } from "./widget"; import { ElementWidgetActions, widget } from "./widget";
import { ErrorCode } from "./utils/errors.ts"; import { ErrorCode } from "./utils/errors.ts";
const getUrlParams = vi.hoisted(() => vi.fn(() => ({})));
vi.mock("./UrlParams", () => ({ getUrlParams }));
const actualWidget = await vi.hoisted(async () => vi.importActual("./widget")); const actualWidget = await vi.hoisted(async () => vi.importActual("./widget"));
vi.mock("./widget", () => ({ vi.mock("./widget", () => ({
...actualWidget, ...actualWidget,
widget: { widget: {
api: { transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() } }, api: {
setAlwaysOnScreen: (): void => {},
transport: { send: vi.fn(), reply: vi.fn(), stop: vi.fn() },
},
lazyActions: new EventEmitter(), lazyActions: new EventEmitter(),
}, },
})); }));
@@ -110,34 +116,45 @@ test("It joins the correct Session", async () => {
); );
}); });
test("leaveRTCSession closes the widget on a normal hangup", async () => { async function testLeaveRTCSession(
cause: "user" | "error",
expectClose: boolean,
): Promise<void> {
vi.clearAllMocks(); vi.clearAllMocks();
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession; const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession;
await leaveRTCSession(session, "user"); await leaveRTCSession(session, cause);
expect(session.leaveRoomSession).toHaveBeenCalled(); expect(session.leaveRoomSession).toHaveBeenCalled();
expect(widget!.api.transport.send).toHaveBeenCalledWith( expect(widget!.api.transport.send).toHaveBeenCalledWith(
ElementWidgetActions.HangupCall, ElementWidgetActions.HangupCall,
expect.anything(), expect.anything(),
); );
expect(widget!.api.transport.send).toHaveBeenCalledWith( if (expectClose) {
ElementWidgetActions.Close, expect(widget!.api.transport.send).toHaveBeenCalledWith(
expect.anything(), ElementWidgetActions.Close,
); expect.anything(),
);
expect(widget!.api.transport.stop).toHaveBeenCalled();
} else {
expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
ElementWidgetActions.Close,
expect.anything(),
);
expect(widget!.api.transport.stop).not.toHaveBeenCalled();
}
}
test("leaveRTCSession closes the widget on a normal hangup", async () => {
await testLeaveRTCSession("user", true);
}); });
test("leaveRTCSession doesn't close the widget on a fatal error", async () => { test("leaveRTCSession doesn't close the widget on a fatal error", async () => {
vi.clearAllMocks(); await testLeaveRTCSession("error", false);
const session = { leaveRoomSession: vi.fn() } as unknown as MatrixRTCSession; });
await leaveRTCSession(session, "error");
expect(session.leaveRoomSession).toHaveBeenCalled(); test("leaveRTCSession doesn't close the widget when returning to lobby", async () => {
expect(widget!.api.transport.send).toHaveBeenCalledWith( getUrlParams.mockReturnValue({ returnToLobby: true });
ElementWidgetActions.HangupCall, onTestFinished(() => void getUrlParams.mockReset());
expect.anything(), await testLeaveRTCSession("user", false);
);
expect(widget!.api.transport.send).not.toHaveBeenCalledWith(
ElementWidgetActions.Close,
expect.anything(),
);
}); });
test("It fails with configuration error if no live kit url config is set in fallback", async () => { test("It fails with configuration error if no live kit url config is set in fallback", async () => {

View File

@@ -19,6 +19,7 @@ import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget"; import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
import { MatrixRTCFocusMissingError } from "./utils/errors.ts"; import { MatrixRTCFocusMissingError } from "./utils/errors.ts";
import { getUrlParams } from "./UrlParams.ts";
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
@@ -126,6 +127,13 @@ export async function enterRTCSession(
makeKeyDelay: matrixRtcSessionConfig?.key_rotation_on_leave_delay, makeKeyDelay: matrixRtcSessionConfig?.key_rotation_on_leave_delay,
}, },
); );
if (widget) {
try {
await widget.api.transport.send(ElementWidgetActions.JoinCall, {});
} catch (e) {
logger.error("Failed to send join action", e);
}
}
} }
const widgetPostHangupProcedure = async ( const widgetPostHangupProcedure = async (
@@ -151,7 +159,7 @@ const widgetPostHangupProcedure = async (
} }
// On a normal user hangup we can shut down and close the widget. But if an // On a normal user hangup we can shut down and close the widget. But if an
// error occurs we should keep the widget open until the user reads it. // error occurs we should keep the widget open until the user reads it.
if (cause === "user") { if (cause === "user" && !getUrlParams().returnToLobby) {
try { try {
await widget.api.transport.send(ElementWidgetActions.Close, {}); await widget.api.transport.send(ElementWidgetActions.Close, {});
} catch (e) { } catch (e) {

View File

@@ -496,6 +496,10 @@ export class CallViewModel extends ViewModel {
} }
return displaynameMap; return displaynameMap;
}), }),
// It turns out that doing the disambiguation above is rather expensive on Safari (10x slower
// than on Chrome/Firefox). This means it is important that we share() the result so that we
// don't do this work more times than we need to. This is achieve through the state() operator:
this.scope.state(),
); );
/** /**

View File

@@ -29,7 +29,7 @@ export default defineConfig(({ mode }) => {
}), }),
htmlTemplate.default({ htmlTemplate.default({
data: { data: {
title: env.VITE_PRODUCT_NAME || "Element Call", brand: env.VITE_PRODUCT_NAME || "Element Call",
}, },
}), }),