Bundle Element Call with Element Web packages (#29309)
* Embed Element Call into Element Web packages Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Pass rageshakeSubmitUrl & posthogApiHost to EC widget Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Use @vector-im/element-call-embedded * Only pass posthog params to EC if Analytics is enabled Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix test mock Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update EC params to match https://github.com/element-hq/element-call/pull/3089 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update to latest element-call package Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * yarn.lock Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update to element-call-embedded@ v0.9.0-rc.1 * Gate Sentry params behind analytics consent Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update to element-call-embedded v0.9.0-rc.4 * Update Element Call embedded to 0.9.0 release Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
This commit is contained in:
committed by
GitHub
parent
209ab59978
commit
8bb4d44532
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
59
src/components/views/elements/SettingsField.tsx
Normal file
59
src/components/views/elements/SettingsField.tsx
Normal 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;
|
||||
@@ -846,6 +846,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": {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -349,11 +349,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": {
|
||||
@@ -1384,4 +1386,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: "",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user