Compare commits

...

24 Commits

Author SHA1 Message Date
Hugh Nimmo-Smith
d0c869ceb2 Update to element-call-embedded v0.9.0-rc.4 2025-03-26 11:29:02 +00:00
Michael Telatynski
a6151fec12 Gate Sentry params behind analytics consent
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-21 12:32:59 +00:00
Hugh Nimmo-Smith
303f7fc33f Update to element-call-embedded@ v0.9.0-rc.1 2025-03-21 12:16:13 +00:00
Michael Telatynski
c08503c672 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 13:12:49 +00:00
Michael Telatynski
6a7a252635 yarn.lock
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 12:59:26 +00:00
Michael Telatynski
988f8e9a65 Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/embed-ec
# Conflicts:
#	package.json
#	yarn.lock
2025-03-17 12:56:25 +00:00
Michael Telatynski
90ba0b4894 Update to latest element-call package
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 12:55:00 +00:00
Michael Telatynski
ba8f5a11df Update EC params to match https://github.com/element-hq/element-call/pull/3089
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 09:36:23 +00:00
Michael Telatynski
b3de784519 Fix test mock
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 09:00:12 +00:00
Michael Telatynski
4bd41f9310 Merge remote-tracking branch 'origin/t3chguy/embed-ec' into t3chguy/embed-ec 2025-03-17 08:45:23 +00:00
Michael Telatynski
99c5d656b6 Only pass posthog params to EC if Analytics is enabled
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 08:45:00 +00:00
Hugh Nimmo-Smith
d4f8a754de Use @vector-im/element-call-embedded 2025-03-14 16:14:13 +00:00
Michael Telatynski
4226db32b8 Update snapshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-13 08:41:12 +00:00
Michael Telatynski
d8bc28868c Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/embed-ec 2025-03-13 08:38:39 +00:00
Michael Telatynski
7b376831be Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/embed-ec
# Conflicts:
#	yarn.lock
2025-03-06 09:38:27 +00:00
Michael Telatynski
712d52c58c Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 16:33:36 +00:00
Michael Telatynski
a8cc0ca125 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 15:59:22 +00:00
Michael Telatynski
b1d6fd5be6 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 14:55:40 +00:00
Michael Telatynski
47bfe096c5 Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-19 12:38:31 +00:00
Michael Telatynski
6a5d436098 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 17:30:26 +00:00
Michael Telatynski
42818530cd Pass rageshakeSubmitUrl & posthogApiHost to EC widget
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 17:24:01 +00:00
Michael Telatynski
513d42f726 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 17:10:16 +00:00
Michael Telatynski
de1c9c8603 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 17:06:54 +00:00
Michael Telatynski
a91777eaff Embed Element Call into Element Web packages
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-18 17:01:48 +00:00
22 changed files with 264 additions and 33 deletions

View File

@@ -51,6 +51,7 @@ jobs:
error|invalid_json
error|misconfigured
welcome_to_element
devtools|settings|elementCallUrl
rethemendex_lint:
name: "Rethemendex Check"

View File

@@ -384,8 +384,6 @@ The VoIP and Jitsi options are:
5. `audio_stream_url`: Optional URL to pass to Jitsi to enable live streaming. This option is considered experimental and may be removed
at any time without notice.
6. `element_call`: Optional configuration for native group calls using Element Call, with the following subkeys:
- `url`: The URL of the Element Call instance to use for native group calls. This option is considered experimental
and may be removed at any time without notice. Defaults to `https://call.element.io`.
- `use_exclusively`: A boolean specifying whether Element Call should be used exclusively as the only VoIP stack in
the app, removing the ability to start legacy 1:1 calls or Jitsi calls. Defaults to `false`.
- `participant_limit`: The maximum number of users who can join a call; if

View File

@@ -40,6 +40,8 @@ export default {
// Used by webpack
"process",
"util",
// Embedded into webapp
"@element-hq/element-call-embedded",
],
ignoreBinaries: [
// Used in scripts & workflows

View File

@@ -177,6 +177,7 @@
"@babel/preset-typescript": "^7.12.7",
"@babel/runtime": "^7.12.5",
"@casualbot/jest-sonar-reporter": "2.2.7",
"@element-hq/element-call-embedded": "^0.9.0-rc.4",
"@element-hq/element-web-playwright-common": "^1.1.5",
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "^1.50.1",

View File

@@ -117,7 +117,6 @@ export interface IConfigOptions {
obey_asserted_identity?: boolean; // MSC3086
};
element_call: {
url?: string;
guest_spa_url?: string;
use_exclusively?: boolean;
participant_limit?: number;

View File

@@ -30,7 +30,6 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
preferred_domain: "meet.element.io",
},
element_call: {
url: "https://call.element.io",
use_exclusively: false,
participant_limit: 8,
brand: "Element Call",

View File

@@ -25,6 +25,7 @@ import ServerInfo from "./devtools/ServerInfo";
import CopyableText from "../elements/CopyableText";
import RoomNotifications from "./devtools/RoomNotifications";
import { Crypto } from "./devtools/Crypto";
import SettingsField from "../elements/SettingsField.tsx";
enum Category {
Room,
@@ -101,6 +102,7 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished })
<SettingsFlag name="developerMode" level={SettingLevel.ACCOUNT} />
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
<SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} />
</div>
</BaseTool>
);

View File

@@ -0,0 +1,59 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type ChangeEvent, type JSX, useCallback, useState } from "react";
import { EditInPlace } from "@vector-im/compound-web";
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import { type SettingLevel } from "../../../settings/SettingLevel";
import { type StringSettingKey } from "../../../settings/Settings";
interface Props {
settingKey: StringSettingKey;
level: SettingLevel;
roomId?: string; // for per-room settings
label?: string;
isExplicit?: boolean;
onChange?(value: string): void;
}
const SettingsField = ({ settingKey, level, roomId, isExplicit, label, onChange: _onSave }: Props): JSX.Element => {
const settingsValue = SettingsStore.getValueAt(level, settingKey, roomId, isExplicit);
const [value, setValue] = useState(settingsValue);
const [busy, setBusy] = useState(false);
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
}, []);
const onCancel = useCallback(() => {
setValue(settingsValue);
}, [settingsValue]);
const onSave = useCallback(async () => {
setBusy(true);
await SettingsStore.setValue(settingKey, roomId ?? null, level, value);
setBusy(false);
_onSave?.(value);
}, [level, roomId, settingKey, value, _onSave]);
return (
<EditInPlace
label={label ?? SettingsStore.getDisplayName(settingKey, level) ?? ""}
value={value}
saveButtonLabel={_t("common|save")}
cancelButtonLabel={_t("common|cancel")}
savedLabel={_t("common|saved")}
savingLabel={_t("common|updating")}
onChange={onChange}
onCancel={onCancel}
onSave={onSave}
disabled={busy}
/>
);
};
export default SettingsField;

View File

@@ -834,6 +834,9 @@
"setting_colon": "Setting:",
"setting_definition": "Setting definition:",
"setting_id": "Setting ID",
"settings": {
"elementCallUrl": "Element Call URL"
},
"settings_explorer": "Settings explorer",
"show_hidden_events": "Show hidden events in timeline",
"spaces": {

View File

@@ -32,7 +32,6 @@ import {
import type EventEmitter from "events";
import type { IApp } from "../stores/WidgetStore";
import SdkConfig, { DEFAULTS } from "../SdkConfig";
import SettingsStore from "../settings/SettingsStore";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../MediaDeviceHandler";
import { timeout } from "../utils/promise";
@@ -43,12 +42,13 @@ import WidgetStore from "../stores/WidgetStore";
import { WidgetMessagingStore, WidgetMessagingStoreEvent } from "../stores/widgets/WidgetMessagingStore";
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidgetStore";
import { getCurrentLanguage } from "../languageHandler";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { Anonymity, PosthogAnalytics } from "../PosthogAnalytics";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
import { isVideoRoom } from "../utils/video-rooms";
import { FontWatcher } from "../settings/watchers/FontWatcher";
import { type JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
import SdkConfig from "../SdkConfig.ts";
const TIMEOUT_MS = 16000;
@@ -676,14 +676,12 @@ export class ElementCall extends Call {
}
private static generateWidgetUrl(client: MatrixClient, roomId: string): URL {
const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
// The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
// We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
// This is prohibited in EC where a hashed version of the analyticsID is used for the actual posthog identification.
// We can pass the raw EW analyticsID here since we need to trust EC with not sending sensitive data to posthog (EC has access to more sensible data than the analyticsID e.g. the username)
const analyticsID: string = accountAnalyticsData?.getContent().pseudonymousAnalyticsOptIn
? accountAnalyticsData?.getContent().id
: "";
const baseUrl = window.location.href;
let url = new URL("./widgets/element-call/index.html#", baseUrl); // this strips hash fragment from baseUrl
const elementCallUrl = SettingsStore.getValue("Developer.elementCallUrl");
if (elementCallUrl) url = new URL(elementCallUrl);
// Splice together the Element Call URL for this call
const params = new URLSearchParams({
embed: "true", // We're embedding EC within another application
@@ -700,12 +698,44 @@ export class ElementCall extends Call {
lang: getCurrentLanguage().replace("_", "-"),
fontScale: (FontWatcher.getRootFontSize() / FontWatcher.getBrowserDefaultFontSize()).toString(),
theme: "$org.matrix.msc2873.client_theme",
analyticsID,
});
if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", "true");
if (SettingsStore.getValue("feature_allow_screen_share_only_mode"))
const rageshakeSubmitUrl = SdkConfig.get("bug_report_endpoint_url");
if (rageshakeSubmitUrl) {
params.append("rageshakeSubmitUrl", rageshakeSubmitUrl);
}
const posthogConfig = SdkConfig.get("posthog");
if (posthogConfig && PosthogAnalytics.instance.getAnonymity() !== Anonymity.Disabled) {
const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE)?.getContent();
// The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
// We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
// This is prohibited in EC where a hashed version of the analyticsID is used for the actual posthog identification.
// We can pass the raw EW analyticsID here since we need to trust EC with not sending sensitive data to posthog (EC has access to more sensible data than the analyticsID e.g. the username)
const analyticsID: string = accountAnalyticsData?.pseudonymousAnalyticsOptIn
? accountAnalyticsData?.id
: "";
params.append("analyticsID", analyticsID); // Legacy, deprecated in favour of posthogUserId
params.append("posthogUserId", analyticsID);
params.append("posthogApiHost", posthogConfig.api_host);
params.append("posthogApiKey", posthogConfig.project_api_key);
// We gate passing sentry behind analytics consent as EC shares data automatically without user-consent,
// unlike EW where data is shared upon an intentional user action (rageshake).
const sentryConfig = SdkConfig.get("sentry");
if (sentryConfig) {
params.append("sentryDsn", sentryConfig.dsn);
params.append("sentryEnvironment", sentryConfig.environment ?? "");
}
}
if (SettingsStore.getValue("fallbackICEServerAllowed")) {
params.append("allowIceFallback", "true");
}
if (SettingsStore.getValue("feature_allow_screen_share_only_mode")) {
params.append("allowVoipWithNoMedia", "true");
}
// Set custom fonts
if (SettingsStore.getValue("useSystemFont")) {
@@ -720,8 +750,6 @@ export class ElementCall extends Call {
.forEach((font) => params.append("font", font));
}
const url = new URL(SdkConfig.get("element_call").url ?? DEFAULTS.element_call.url!);
url.pathname = "/room";
const replacedUrl = params.toString().replace(/%24/g, "$");
url.hash = `#?${replacedUrl}`;
return url;

View File

@@ -347,11 +347,13 @@ export interface Settings {
"Electron.alwaysShowMenuBar": IBaseSetting<boolean>;
"Electron.showTrayIcon": IBaseSetting<boolean>;
"Electron.enableHardwareAcceleration": IBaseSetting<boolean>;
"Developer.elementCallUrl": IBaseSetting<string>;
}
export type SettingKey = keyof Settings;
export type FeatureSettingKey = Assignable<Settings, IFeature>;
export type BooleanSettingKey = Assignable<Settings, IBaseSetting<boolean>> | FeatureSettingKey;
export type StringSettingKey = Assignable<Settings, IBaseSetting<string>>;
export const SETTINGS: Settings = {
"feature_video_rooms": {
@@ -1371,4 +1373,10 @@ export const SETTINGS: Settings = {
displayName: _td("settings|preferences|enable_hardware_acceleration"),
default: true,
},
"Developer.elementCallUrl": {
supportedLevels: [SettingLevel.DEVICE],
displayName: _td("devtools|settings|elementCallUrl"),
default: "",
},
};

View File

@@ -48,7 +48,6 @@ import {
WidgetLifecycle,
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
import SdkConfig, { DEFAULTS } from "../../SdkConfig";
import { iterableDiff, iterableIntersection } from "../../utils/iterables";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import Modal from "../../Modal";
@@ -116,10 +115,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
// Auto-approve the legacy visibility capability. We send it regardless of capability.
// Widgets don't technically need to request this capability, but Scalar still does.
this.allowedCapabilities.add("visibility");
} else if (
virtual &&
new URL(SdkConfig.get("element_call").url ?? DEFAULTS.element_call.url!).origin === this.forWidget.origin
) {
} else if (virtual && WidgetType.CALL.matches(this.forWidget.type) && forWidgetKind === WidgetKind.Room) {
// This is a trusted Element Call widget that we control
this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen);
this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers);

View File

@@ -190,6 +190,31 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
/>
</div>
</div>
<form
class="_root_19upo_16"
>
<div
class="_field_19upo_26"
>
<label
class="_label_19upo_59"
for="radix-:r4:"
>
Element Call URL
</label>
<div
class="_controls_17lij_8"
>
<input
class="_control_sqdq4_10"
id="radix-:r4:"
name="input"
title=""
value=""
/>
</div>
</div>
</form>
</div>
</div>
<div

View File

@@ -0,0 +1,36 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import SettingsField from "../../../../../src/components/views/elements/SettingsField";
import { SettingLevel } from "../../../../../src/settings/SettingLevel.ts";
describe("<SettingsField />", () => {
it("should render with the default label", () => {
const component = render(<SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} />);
expect(screen.getByText("Element Call URL")).toBeTruthy();
expect(component.asFragment()).toMatchSnapshot();
});
it("should call onChange when saving a change", async () => {
const fn = jest.fn();
render(<SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} onChange={fn} />);
const input = screen.getByRole("textbox");
await userEvent.type(input, "https://call.element.dev");
expect(input).toHaveValue("https://call.element.dev");
screen.getByLabelText("Save").click();
await waitFor(() => {
expect(fn).toHaveBeenCalledWith("https://call.element.dev");
});
});
});

View File

@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SettingsField /> should render with the default label 1`] = `
<DocumentFragment>
<form
class="_root_19upo_16"
>
<div
class="_field_19upo_26"
>
<label
class="_label_19upo_59"
for="radix-:r0:"
>
Element Call URL
</label>
<div
class="_controls_17lij_8"
>
<input
class="_control_sqdq4_10"
id="radix-:r0:"
name="input"
title=""
value=""
/>
</div>
</div>
</form>
</DocumentFragment>
`;

View File

@@ -49,8 +49,9 @@ import { WidgetMessagingStore } from "../../../src/stores/widgets/WidgetMessagin
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../../src/stores/ActiveWidgetStore";
import { ElementWidgetActions } from "../../../src/stores/widgets/ElementWidgetActions";
import SettingsStore from "../../../src/settings/SettingsStore";
import { PosthogAnalytics } from "../../../src/PosthogAnalytics";
import { Anonymity, PosthogAnalytics } from "../../../src/PosthogAnalytics";
import { type SettingKey } from "../../../src/settings/Settings.tsx";
import SdkConfig from "../../../src/SdkConfig.ts";
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [
@@ -664,6 +665,7 @@ describe("ElementCall", () => {
beforeEach(() => {
jest.useFakeTimers();
({ client, room, alice } = setUpClientRoomAndStores());
SdkConfig.reset();
});
afterEach(() => {
@@ -683,6 +685,23 @@ describe("ElementCall", () => {
Call.get(room)?.destroy();
});
it("should use element call URL from developer settings if present", async () => {
const originalGetValue = SettingsStore.getValue;
SettingsStore.getValue = (name: SettingKey, roomId: string | null = null, excludeDefault = false): any => {
if (name === "Developer.elementCallUrl") {
return "https://call.element.dev";
}
return excludeDefault
? originalGetValue(name, roomId, excludeDefault)
: originalGetValue(name, roomId, excludeDefault);
};
await ElementCall.create(room);
const call = ElementCall.get(room);
expect(call?.widget.url.startsWith("https://call.element.dev/")).toBeTruthy();
SettingsStore.getValue = originalGetValue;
call?.destroy();
});
it("finds ongoing calls that are created by the session manager", async () => {
// There is an existing session created by another user in this room.
client.matrixRTC.getRoomSession.mockReturnValue({
@@ -758,7 +777,14 @@ describe("ElementCall", () => {
SettingsStore.getValue = originalGetValue;
});
it("passes analyticsID through widget URL", async () => {
it("passes analyticsID and posthog params through widget URL", async () => {
SdkConfig.put({
posthog: {
api_host: "https://posthog",
project_api_key: "DEADBEEF",
},
});
jest.spyOn(PosthogAnalytics.instance, "getAnonymity").mockReturnValue(Anonymity.Pseudonymous);
client.getAccountData.mockImplementation((eventType: string) => {
if (eventType === PosthogAnalytics.ANALYTICS_EVENT_TYPE) {
return new MatrixEvent({ content: { id: "123456789987654321", pseudonymousAnalyticsOptIn: true } });
@@ -771,6 +797,9 @@ describe("ElementCall", () => {
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("123456789987654321");
expect(urlParams.get("posthogUserId")).toBe("123456789987654321");
expect(urlParams.get("posthogApiHost")).toBe("https://posthog");
expect(urlParams.get("posthogApiKey")).toBe("DEADBEEF");
call.destroy();
});
@@ -788,7 +817,7 @@ describe("ElementCall", () => {
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("");
expect(urlParams.get("analyticsID")).toBeFalsy();
call.destroy();
});
@@ -827,7 +856,7 @@ describe("ElementCall", () => {
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("");
expect(urlParams.get("analyticsID")).toBeFalsy();
});
});

View File

@@ -196,6 +196,8 @@ describe("RoomViewStore", function () {
stores.client = mockClient;
stores._SlidingSyncManager = slidingSyncManager;
stores._PosthogAnalytics = new MockPosthogAnalytics();
// @ts-expect-error
MockPosthogAnalytics.instance = stores._PosthogAnalytics;
stores._SpaceStore = new MockSpaceStore();
roomViewStore = new RoomViewStore(dis, stores);
stores._RoomViewStore = roomViewStore;

View File

@@ -21,7 +21,6 @@ import {
} from "matrix-js-sdk/src/matrix";
import {
Widget,
MatrixWidgetType,
WidgetKind,
type WidgetDriver,
type ITurnServer,
@@ -44,6 +43,7 @@ import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
import dis from "../../../../src/dispatcher/dispatcher";
import Modal from "../../../../src/Modal";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { WidgetType } from "../../../../src/widgets/WidgetType.ts";
describe("StopGapWidgetDriver", () => {
let client: MockedObject<MatrixClient>;
@@ -79,7 +79,7 @@ describe("StopGapWidgetDriver", () => {
new Widget({
id: "group_call",
creatorUserId: "@alice:example.org",
type: MatrixWidgetType.Custom,
type: WidgetType.CALL.preferred,
url: "https://call.element.io",
}),
WidgetKind.Room,

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { mocked } from "jest-mock";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixWidgetType, Widget, WidgetKind } from "matrix-widget-api";
import { Widget, WidgetKind } from "matrix-widget-api";
import { OIDCState, WidgetPermissionStore } from "../../../../src/stores/widgets/WidgetPermissionStore";
import SettingsStore from "../../../../src/settings/SettingsStore";
@@ -17,6 +17,7 @@ import { type SettingLevel } from "../../../../src/settings/SettingLevel";
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
import { stubClient } from "../../../test-utils";
import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver";
import { WidgetType } from "../../../../src/widgets/WidgetType.ts";
jest.mock("../../../../src/settings/SettingsStore");
@@ -34,7 +35,7 @@ describe("WidgetPermissionStore", () => {
const elementCallWidget = new Widget({
id: "group_call",
creatorUserId: "@alice:example.org",
type: MatrixWidgetType.Custom,
type: WidgetType.CALL.preferred,
url: "https://call.element.io",
});
let settings: Record<string, any> = {}; // key value store

View File

@@ -28,7 +28,7 @@ describe("recordClientInformation()", () => {
const sdkConfig: DeepReadonly<IConfigOptions> = {
...DEFAULTS,
brand: "Test Brand",
element_call: { url: "", use_exclusively: false, brand: "Element Call" },
element_call: { use_exclusively: false, brand: "Element Call" },
};
const platform = {

View File

@@ -674,6 +674,12 @@ module.exports = (env, argv) => {
{ from: "media/**", context: path.resolve(__dirname, "res/") },
{ from: "config.json", noErrorOnMissing: true },
"contribute.json",
// Element Call embedded widget
{
from: "**",
context: path.resolve(__dirname, "node_modules/@element-hq/element-call-embedded/dist"),
to: path.join(__dirname, "webapp", "widgets", "element-call"),
},
],
}),

View File

@@ -1546,6 +1546,11 @@
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
"@element-hq/element-call-embedded@^0.9.0-rc.4":
version "0.9.0-rc.4"
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.9.0-rc.4.tgz#de9326cccd746175bf47a6cdd0c5b65d5ad0df86"
integrity sha512-oiF7TQltYM2l3u7/8O2j8Hq7I+ROSB3+o1FDUNKkhgOisHf7A1P108WSOVXMS9GCzus1/ZwbweRgF977EQLXQA==
"@element-hq/element-web-module-api@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-0.1.1.tgz#e2b24aa38aa9f7b6af3c4993e6402a8b7e2f3cb5"