mirror of
https://github.com/element-hq/element-call.git
synced 2026-09-10 15:56:24 +00:00
`pnpm dev:component` serves a page that stands in for a host application: it signs in twice against the development backend and shows two calls side by side, in resizable boxes, with furniture of its own around them. Two devices of one account, so a real call happens between the two components and anything Element Call keeps once per process rather than once per call shows itself. The host bridge is driven by hand and reports both directions in a log along the bottom, which is the first exercise the theme, hang-up and device-mute requests have had outside widget mode. Each pane can be unmounted and remounted to see what Element Call leaves behind, and there is a `position: fixed` dialog belonging to the host to see whether it covers the calls. The page uses none of Element Call's design tokens, so anything that looks styled outside a pane came from Element Call reaching out of its container. It reaches Element Call only through the component's public interface, which is how the exports missing from that interface came to light. Three things about the component build the harness turned up on the way, all too small to be worth their own commits: - It copied `public/` into `dist/`, including the developer's own gitignored config.json, into output we would publish. `publicDir: false`, as the embedded build already does. The sdk build has the same leak; untouched. - `pnpm lint:externals` now exists, which the build config already claimed it did. It reads the external list out of that config and fails if the source imports React, the Matrix SDK or LiveKit by a path the list does not name. Since the bundler silently ignores the pattern form of that option, an unnamed subpath is bundled with no warning at all — which is how a host would end up with a second React. - `lint:oxlint` ran over `src playwright`, so nothing in `component/` had ever been linted. Serving a page also meant the shared plugin list could no longer inject the app's HTML entry point unconditionally, so that is now optional — and off for the library build too, which never had an HTML page to inject it into.
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
/*
|
|
Copyright 2026 Element Creations Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import {
|
|
ClientEvent,
|
|
createClient,
|
|
type MatrixClient,
|
|
MemoryStore,
|
|
SyncState,
|
|
} from "matrix-js-sdk";
|
|
|
|
/**
|
|
* Logs in and brings up a client the way a host application would, so that the
|
|
* component is handed a real one rather than something Element Call built for
|
|
* itself.
|
|
*
|
|
* Everything is kept in memory and a fresh login happens on every reload. That
|
|
* costs a device on the development homeserver each time, which is harmless,
|
|
* and buys the harness two clients that cannot tread on each other's storage.
|
|
* Persisting the login to make reloads quicker would mean persisting the
|
|
* crypto store too: reusing a device ID with a fresh crypto store generates new
|
|
* device keys, and uploading them conflicts with the ones the server already
|
|
* holds.
|
|
*/
|
|
export async function createSession(
|
|
homeserver: string,
|
|
username: string,
|
|
password: string,
|
|
onProgress: (message: string) => void,
|
|
): Promise<MatrixClient> {
|
|
onProgress("Logging in");
|
|
const login = await createClient({ baseUrl: homeserver }).login(
|
|
"m.login.password",
|
|
{ identifier: { type: "m.id.user", user: username }, password },
|
|
);
|
|
|
|
const client = createClient({
|
|
baseUrl: homeserver,
|
|
accessToken: login.access_token,
|
|
userId: login.user_id,
|
|
deviceId: login.device_id,
|
|
store: new MemoryStore(),
|
|
useAuthorizationHeader: true,
|
|
fallbackICEServerAllowed: true,
|
|
});
|
|
|
|
onProgress(`Setting up crypto for ${login.device_id}`);
|
|
await client.initRustCrypto({ useIndexedDB: false });
|
|
|
|
onProgress(`Syncing ${login.device_id}`);
|
|
await client.startClient();
|
|
await new Promise<void>((resolve) => {
|
|
const onSync = (state: SyncState): void => {
|
|
if (state !== SyncState.Prepared && state !== SyncState.Syncing) return;
|
|
client.off(ClientEvent.Sync, onSync);
|
|
resolve();
|
|
};
|
|
client.on(ClientEvent.Sync, onSync);
|
|
});
|
|
|
|
return client;
|
|
}
|
|
|
|
/**
|
|
* The room to call in, joining it if this session is not in it yet — a host
|
|
* hands Element Call a room it already knows about, so the harness has to get
|
|
* itself into that position first.
|
|
*/
|
|
export async function joinRoom(
|
|
client: MatrixClient,
|
|
roomIdOrAlias: string,
|
|
): Promise<string> {
|
|
const room = await client.joinRoom(roomIdOrAlias);
|
|
return room.roomId;
|
|
}
|