Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow join unmuted in widget mode #2848

Open
wants to merge 13 commits into
base: livekit
Choose a base branch
from
2 changes: 1 addition & 1 deletion docs/url-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ There are two formats for Element Call urls.
| `returnToLobby` | `true` or `false` | No, defaults to `false` | Not applicable | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. |
| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. |
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
| `skipLobby` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
| `skipLobby` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with `preload` in widget. When `true` the audio and video inputs will be muted by default unless running as a widget. |
| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. |
| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | Not applicable | The Matrix user ID. |
| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the user’s default homeserver. |
Expand Down
12 changes: 6 additions & 6 deletions src/analytics/PosthogAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Buffer } from "buffer";

import { widget } from "../widget";
import { isRunningAsWidget } from "../widget";
import {
CallEndedTracker,
CallStartedTracker,
Expand Down Expand Up @@ -183,9 +183,9 @@ export class PosthogAnalytics {
const appVersion = import.meta.env.VITE_APP_VERSION || "dev";
return {
appVersion,
matrixBackend: widget ? "embedded" : "jssdk",
matrixBackend: isRunningAsWidget ? "embedded" : "jssdk",
callBackend: "livekit",
cryptoVersion: widget
cryptoVersion: isRunningAsWidget
? undefined
: window.matrixclient?.getCrypto()?.getVersion(),
};
Expand Down Expand Up @@ -237,7 +237,7 @@ export class PosthogAnalytics {
// different devices to send the same ID.
let analyticsID = await this.getAnalyticsId();
try {
if (!analyticsID && !widget) {
if (!analyticsID && !isRunningAsWidget) {
// only try setting up a new analytics ID in the standalone app.

// Couldn't retrieve an analytics ID from user settings, so create one and set it on the server.
Expand Down Expand Up @@ -269,7 +269,7 @@ export class PosthogAnalytics {
private async getAnalyticsId(): Promise<string | null> {
const client: MatrixClient = window.matrixclient;
let accountAnalyticsId;
if (widget) {
if (isRunningAsWidget) {
accountAnalyticsId = getUrlParams().analyticsID;
} else {
const accountData = await client.getAccountDataFromServer(
Expand Down Expand Up @@ -302,7 +302,7 @@ export class PosthogAnalytics {
}

private async setAccountAnalyticsId(analyticsID: string): Promise<void> {
if (!widget) {
if (!isRunningAsWidget) {
const client = window.matrixclient;

// the analytics ID only needs to be set in the standalone version.
Expand Down
4 changes: 2 additions & 2 deletions src/auth/useInteractiveRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { initClient } from "../utils/matrix";
import { Session } from "../ClientContext";
import { Config } from "../config/Config";
import { widget } from "../widget";
import { isRunningAsWidget } from "../widget";

export const useInteractiveRegistration = (
oldClient?: MatrixClient,
Expand Down Expand Up @@ -47,7 +47,7 @@ export const useInteractiveRegistration = (
}

useEffect(() => {
if (widget) return;
if (isRunningAsWidget) return;
// An empty registerRequest is used to get the privacy policy and recaptcha key.
authClient.current!.registerRequest({}).catch((error) => {
setPrivacyPolicyUrl(
Expand Down
25 changes: 19 additions & 6 deletions src/room/MuteStates.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import React, { ReactNode } from "react";
import { beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
Expand All @@ -18,6 +18,7 @@ import {
MediaDevicesContext,
} from "../livekit/MediaDevicesContext";
import { mockConfig } from "../utils/test";
import * as widget from "../widget";

function TestComponent(): ReactNode {
const muteStates = useMuteStates();
Expand Down Expand Up @@ -98,10 +99,7 @@ describe("useMuteStates", () => {

afterEach(() => {
vi.restoreAllMocks();
});

afterAll(() => {
vi.clearAllMocks();
vi.unmock("../widget");
});

it("disabled when no input devices", () => {
Expand Down Expand Up @@ -156,7 +154,7 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});

it("skipLobby mutes inputs", () => {
it("skipLobby mutes inputs on SPA", () => {
mockConfig();

render(
Expand All @@ -169,4 +167,19 @@ describe("useMuteStates", () => {
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
expect(screen.getByTestId("video-enabled").textContent).toBe("false");
});

it("skipLobby does not mute inputs in widget mode", () => {
mockConfig();
vi.spyOn(widget, "isRunningAsWidget", "get").mockImplementation(() => true);

render(
<MemoryRouter initialEntries={["/room/?skipLobby=true"]}>
<MediaDevicesContext.Provider value={mockMediaDevices()}>
<TestComponent />
</MediaDevicesContext.Provider>
</MemoryRouter>,
);
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
expect(screen.getByTestId("video-enabled").textContent).toBe("true");
});
});
15 changes: 8 additions & 7 deletions src/room/MuteStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import {
useEffect,
useMemo,
} from "react";
import { IWidgetApiRequest } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";

import type { IWidgetApiRequest } from "matrix-widget-api";
import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
import { useReactiveState } from "../useReactiveState";
import { ElementWidgetActions, widget } from "../widget";
import { ElementWidgetActions, isRunningAsWidget, widget } from "../widget";
import { Config } from "../config/Config";
import { useUrlParams } from "../UrlParams";

/**
* If there already are this many participants in the call, we automatically mute
* the user.
* the user when they join a call.
*/
export const MUTE_PARTICIPANT_COUNT = 8;

Expand Down Expand Up @@ -74,13 +74,14 @@ export function useMuteStates(): MuteStates {
const devices = useMediaDevices();

const { skipLobby } = useUrlParams();

// In SPA without lobby we need to protect from unmuted joins for privacy.
const allowStartUnmuted = !skipLobby || isRunningAsWidget;
const audio = useMuteState(devices.audioInput, () => {
return Config.get().media_devices.enable_audio && !skipLobby;
return Config.get().media_devices.enable_audio && allowStartUnmuted;
});
const video = useMuteState(
devices.videoInput,
() => Config.get().media_devices.enable_video && !skipLobby,
() => Config.get().media_devices.enable_video && allowStartUnmuted,
);

useEffect(() => {
Expand All @@ -90,7 +91,7 @@ export function useMuteStates(): MuteStates {
video_enabled: video.enabled,
})
.catch((e) =>
logger.warn("Could not send DeviceMute action to widget", e),
logger.warn("Could not send DeviceMute action to widget host", e),
);
}, [audio, video]);

Expand Down
4 changes: 2 additions & 2 deletions src/room/useLoadGroupCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
import { JoinRule, MatrixError } from "matrix-js-sdk/src/matrix";
import { useTranslation } from "react-i18next";

import { widget } from "../widget";
import { isRunningAsWidget } from "../widget";

export type GroupCallLoaded = {
kind: "loaded";
Expand Down Expand Up @@ -238,7 +238,7 @@ export const useLoadGroupCall = (
// room already joined so we are done here already.
return room!;
}
if (widget)
if (isRunningAsWidget)
// in widget mode we never should reach this point. (getRoom should return the room.)
throw new Error(
"Room not found. The widget-api did not pass over the relevant room events/information.",
Expand Down
4 changes: 2 additions & 2 deletions src/settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
useMediaDevices,
useMediaDeviceNames,
} from "../livekit/MediaDevicesContext";
import { widget } from "../widget";
import { isRunningAsWidget } from "../widget";
import {
useSetting,
developerSettingsTab as developerSettingsTabSetting,
Expand Down Expand Up @@ -236,7 +236,7 @@ export const SettingsModal: FC<Props> = ({
};

const tabs = [audioTab, videoTab];
if (widget === null) tabs.push(profileTab);
if (!isRunningAsWidget) tabs.push(profileTab);
tabs.push(preferencesTab, feedbackTab, moreTab);
if (developerSettingsTab) tabs.push(developerTab);

Expand Down
7 changes: 7 additions & 0 deletions src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,10 @@ export const widget = ((): WidgetHelpers | null => {
return null;
}
})();

/**
* Whether or not we are running as a widget.
*
* @returns true if widget, false if SPA
*/
export const isRunningAsWidget: boolean = !!widget;