Derive the component's defaults from an intent, not the host's URL

The component seeded its parameters with `computeUrlParams()`, which reads
`window.location`. Element Call is not the page any more, so what it found
there was the host's URL: no `widgetId`, therefore not a widget, therefore no
intent, therefore the standalone app's preset.

The visible symptom was the Element Call logo in the footer of a call embedded
in someone else's application, since `showLogo` follows `header ===
HeaderStyle.Standard`. The rest of that preset mattered more. A hosted call
defaulted to `perParticipantE2EE: false` and `confineToRoom: false` — so
unencrypted, and willing to take the user out of the room a host had put them
in — and a host could not correct either without naming every parameter
itself, nor even name `header`, since the enums were not exported.

So the intent presets move out of `computeUrlParams` into
`configurationForIntent`, and the component builds its parameters from an
intent plus the properties a host has no URL to supply. `intent` becomes a
prop, defaulting to joining an existing group call: the lobby first, confined
to the room, encrypted per participant, and no Element Call branding in
someone else's interface. A host that knows which button the user pressed
should say which.

One deliberate difference from the widget presets: the background defaults to
solid rather than the gradient, which is drawn by a `position: fixed`
pseudo-element and would escape the container to cover the host.

`UserIntent.Unknown` still means the standalone preset, so the app and widget
are unchanged — including a widget that names no intent, which has always been
given those defaults.
This commit is contained in:
Valere
2026-09-03 16:39:14 +02:00
parent 9f5fa6049f
commit c0a54dcaf9
3 changed files with 215 additions and 78 deletions
+48
View File
@@ -11,10 +11,14 @@ import { logger } from "matrix-js-sdk/lib/logger";
import * as PlatformMod from "../src/Platform";
import {
BackgroundStyle,
configurationForIntent,
getRoomIdentifierFromUrl,
computeUrlParams,
HeaderStyle,
getUrlParams,
hostedProperties,
UserIntent,
} from "../src/UrlParams";
import { mockConfig } from "./utils/test";
@@ -424,4 +428,48 @@ describe("UrlParams", () => {
);
});
});
// What Element Call runs with when a host embeds it as a component, which
// has no URL of its own for any of this to come from
describe("hosted defaults", () => {
it("assume nothing about a session or a page", () => {
expect(hostedProperties).toMatchObject({
// The host is not a widget host, and supplies the client itself, so
// none of the widget or session plumbing applies
isWidget: false,
widgetId: null,
parentUrl: null,
userId: null,
deviceId: null,
baseUrl: null,
homeserver: null,
// The gradient is drawn by a `position: fixed` pseudo-element, which
// would escape the container and cover the host's own interface
background: BackgroundStyle.Solid,
});
});
it("keep a hosted call inside its room", () => {
const hosted = configurationForIntent(UserIntent.JoinExistingCall);
expect(hosted).toMatchObject({
// A host owns navigation, so Element Call must not offer a way out of
// the room
confineToRoom: true,
perParticipantE2EE: true,
// The lobby first, so that the user picks their devices rather than
// being thrown into the call by the act of being rendered
skipLobby: false,
});
// No Element Call branding inside someone else's application
expect(hosted.header).not.toBe(HeaderStyle.Standard);
});
it("fall back to the standalone app's when no intent is stated", () => {
expect(configurationForIntent(UserIntent.Unknown)).toMatchObject({
confineToRoom: false,
header: HeaderStyle.Standard,
perParticipantE2EE: false,
});
});
});
});