mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-10 21:55:19 +00:00
Say outright whether the profile is ours to change
Whether to offer the profile settings was inferred from whether the host could close Element Call. For a component with no host bridge — the default — nothing could, so an embedded Element Call let the user edit the profile of an account that belongs to the host application. `HostBridge.supportsProfileChanges` states it directly: true standalone, where Element Call signed the user in itself; false for a widget's host and for anything embedding the component (which sets it itself, since the client it hands over is its own). The profile tab and the profile shortcut follow that. What a host's ability to close us still decides — what to show after the call ends — is a question about who owns our lifetime, and stays keyed on `close`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
f0359259f9
commit
7cd21476f7
@@ -10,7 +10,7 @@ import { NEVER, Subject } from "rxjs";
|
||||
import {
|
||||
type DeviceMuteRequest,
|
||||
type DeviceMuteState,
|
||||
type HostBridge,
|
||||
type ElementCallHostBridge,
|
||||
type HostRequest,
|
||||
} from "../index";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
* so that the harness can watch both directions of the conversation between
|
||||
* Element Call and its host.
|
||||
*/
|
||||
export interface DevHostBridge extends HostBridge {
|
||||
export interface DevHostBridge extends ElementCallHostBridge {
|
||||
/** Tells Element Call the host has changed theme. */
|
||||
requestTheme(name: string): void;
|
||||
/** Tells Element Call to leave the call. */
|
||||
|
||||
+21
-2
@@ -100,6 +100,14 @@ export {
|
||||
*/
|
||||
export type ElementCallConfiguration = Partial<UrlParams>;
|
||||
|
||||
/**
|
||||
* What a host embedding Element Call implements to talk to it. This is the
|
||||
* {@link HostBridge} less what Element Call already knows about such a host:
|
||||
* the account is the host's, since the client is, so the profile is not
|
||||
* Element Call's to change.
|
||||
*/
|
||||
export type ElementCallHostBridge = Omit<HostBridge, "supportsProfileChanges">;
|
||||
|
||||
export interface ElementCallProps {
|
||||
/**
|
||||
* The client to place the call with. Element Call does not authenticate
|
||||
@@ -132,7 +140,7 @@ export interface ElementCallProps {
|
||||
* joined or hung up, to be asked to keep the call on screen, and so on.
|
||||
* Without one, Element Call assumes it has no host to talk to.
|
||||
*/
|
||||
hostBridge?: HostBridge;
|
||||
hostBridge?: ElementCallHostBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,8 +189,19 @@ export const ElementCall: FC<ElementCallProps> = ({
|
||||
roomId,
|
||||
intent = UserIntent.JoinExistingCall,
|
||||
config,
|
||||
hostBridge = nullHostBridge,
|
||||
hostBridge: suppliedHostBridge = nullHostBridge,
|
||||
}): ReactNode => {
|
||||
// Whatever the host says or does not say, the account is its own: it signed
|
||||
// the user in and handed us the client. So Element Call never offers to edit
|
||||
// the profile from inside a component.
|
||||
const hostBridge = useMemo(
|
||||
(): HostBridge => ({
|
||||
...suppliedHostBridge,
|
||||
supportsProfileChanges: false,
|
||||
}),
|
||||
[suppliedHostBridge],
|
||||
);
|
||||
|
||||
// The container is what Element Call decorates and portals into, so nothing
|
||||
// inside can render until we have it.
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -247,6 +247,11 @@ describe("createWidgetHostBridge", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("does not offer profile changes, since the host signed the user in", () => {
|
||||
const bridge = createWidgetHostBridge(mockWidget({}));
|
||||
expect(bridge.supportsProfileChanges).toBe(false);
|
||||
});
|
||||
|
||||
describe("supportsReactions", () => {
|
||||
const capabilities = [
|
||||
"org.matrix.msc2762.send.event:m.reaction",
|
||||
@@ -278,6 +283,10 @@ describe("nullHostBridge", () => {
|
||||
expect(nullHostBridge.close).toBeUndefined();
|
||||
});
|
||||
|
||||
test("supports profile changes, since Element Call signed the user in itself", () => {
|
||||
expect(nullHostBridge.supportsProfileChanges).toBe(true);
|
||||
});
|
||||
|
||||
test("offers no media download, so Element Call uses its own client", () => {
|
||||
expect(nullHostBridge.downloadMedia).toBeUndefined();
|
||||
});
|
||||
|
||||
+15
-1
@@ -96,8 +96,16 @@ export interface HostBridge {
|
||||
/** The host wants to change, or read back, the device mute state. */
|
||||
deviceMute$: Observable<HostRequest<DeviceMuteRequest, DeviceMuteState>>;
|
||||
|
||||
// What the host is capable of.
|
||||
// What the host is, and is capable of.
|
||||
|
||||
/**
|
||||
* Whether Element Call may offer to change the user's profile — their
|
||||
* display name and avatar. Only when the account is Element Call's own,
|
||||
* which is to say standalone: a widget's host and an application embedding
|
||||
* Element Call both signed the user in themselves, so the profile is theirs
|
||||
* to manage and Element Call must not offer to edit it.
|
||||
*/
|
||||
readonly supportsProfileChanges: boolean;
|
||||
/** Whether the host permits Element Call to send and receive reactions. */
|
||||
readonly supportsReactions: boolean;
|
||||
/**
|
||||
@@ -122,6 +130,9 @@ export const nullHostBridge: HostBridge = {
|
||||
join$: NEVER,
|
||||
hangUp$: NEVER,
|
||||
deviceMute$: NEVER,
|
||||
// Standalone, the account is Element Call's own: it signed the user in, so
|
||||
// it may offer to change the profile.
|
||||
supportsProfileChanges: true,
|
||||
// Standalone Element Call reaches the homeserver itself, so nothing is
|
||||
// withholding these from it.
|
||||
supportsReactions: true,
|
||||
@@ -176,6 +187,9 @@ export function createWidgetHostBridge(widget: WidgetHelpers): HostBridge {
|
||||
join$: requests(ElementWidgetActions.JoinCall),
|
||||
hangUp$: requests(ElementWidgetActions.HangupCall),
|
||||
deviceMute$: requests(ElementWidgetActions.DeviceMute),
|
||||
// The client we are a widget of signed the user in, so the profile is its
|
||||
// to manage
|
||||
supportsProfileChanges: false,
|
||||
// Element Call needs the host's permission to send reactions on its behalf.
|
||||
// Read on access rather than up front: the widget API negotiates its
|
||||
// capabilities asynchronously, and the bridge is built before that settles.
|
||||
|
||||
@@ -116,10 +116,8 @@ export const GroupCallView: FC<Props> = ({
|
||||
const hostBridge = useHostBridge();
|
||||
// A host that can close us is a host that decides when we stop existing, so
|
||||
// we neither show our own post-call screens nor assume we have time to
|
||||
// finish what we are doing.
|
||||
// TODO: this reads a capability as a proxy for who owns our lifetime. Worth
|
||||
// finding a more direct way to express it — see the guidance in UrlParams.ts
|
||||
// on naming behaviours rather than situations.
|
||||
// finish what we are doing. (Whose account the user's is, by contrast, is
|
||||
// stated outright: see `HostBridge.supportsProfileChanges`.)
|
||||
const hostControlsLifetime = hostBridge.close !== undefined;
|
||||
|
||||
const muteAllAudio = useBehavior(muteAllAudio$);
|
||||
|
||||
@@ -329,11 +329,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
|
||||
const openProfile = useMemo(
|
||||
() =>
|
||||
// A host that can dismiss us is a host that owns the user's account, so
|
||||
// their profile is not ours to edit.
|
||||
// TODO: another use of the close capability as a proxy — see the note in
|
||||
// GroupCallView.
|
||||
hostBridge.close === undefined
|
||||
// The profile is only ours to edit when the account is ours
|
||||
hostBridge.supportsProfileChanges
|
||||
? (): void => {
|
||||
setSettingsTab("profile");
|
||||
setSettingsOpen(true);
|
||||
|
||||
@@ -235,9 +235,8 @@ export const SettingsModal: FC<Props> = ({
|
||||
};
|
||||
|
||||
const tabs = [audioTab, videoTab];
|
||||
// A host that can dismiss us is a host that owns the user's account, so their
|
||||
// profile is not ours to edit.
|
||||
if (hostBridge.close === undefined) tabs.push(profileTab);
|
||||
// The profile is only ours to edit when the account is ours
|
||||
if (hostBridge.supportsProfileChanges) tabs.push(profileTab);
|
||||
tabs.push(preferencesTab);
|
||||
if (isRageshakeAvailable || import.meta.env.VITE_PACKAGE === "full") {
|
||||
// for full package we want to show the analytics consent checkbox
|
||||
|
||||
Reference in New Issue
Block a user