mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-25 22:35:49 +00:00
Export component related types.
This commit is contained in:
@@ -309,6 +309,23 @@ root. The host imports the component from
|
|||||||
`react-dom`, `matrix-js-sdk` and `livekit-client` itself, since the bundle leaves
|
`react-dom`, `matrix-js-sdk` and `livekit-client` itself, since the bundle leaves
|
||||||
them external.
|
them external.
|
||||||
|
|
||||||
|
The component is large, and a host will usually load it lazily, only once a
|
||||||
|
call is shown. Everything a host needs in order to talk about a call before
|
||||||
|
then is also exported
|
||||||
|
from `@element-hq/element-call-component/api` (a small entry point that does not
|
||||||
|
load the component).
|
||||||
|
|
||||||
|
- props and the types they are made of
|
||||||
|
- the handle
|
||||||
|
- the host bridge
|
||||||
|
- `UserIntent`
|
||||||
|
- `HeaderStyle`
|
||||||
|
- `BackgroundStyle` enums
|
||||||
|
- `configurationForIntent`, the defaults each intent implies
|
||||||
|
Import from it wherever a value such as
|
||||||
|
`BackgroundStyle.Solid` is needed on a path that must stay light; the main
|
||||||
|
entry point re-exports all of it too.
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
A docker compose file `docker-compose-dev.yml` is provided to start the
|
A docker compose file `docker-compose-dev.yml` is provided to start the
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXPERIMENTAL
|
||||||
|
*
|
||||||
|
* What a host needs in order to talk about Element Call, without Element Call:
|
||||||
|
* the component's props and the types they are made of, the handle and the
|
||||||
|
* host bridge, the intents a call can be started with and the configuration
|
||||||
|
* each implies.
|
||||||
|
*
|
||||||
|
* This is an entry point of its own, `@element-hq/element-call-component/api`,
|
||||||
|
* so that a host can import it on a path that must stay light — a call model
|
||||||
|
* that needs `BackgroundStyle.Solid` as a value, say — and load the component
|
||||||
|
* itself, and everything it brings, only when a call is shown. The main entry
|
||||||
|
* point re-exports all of it, so a host that does not care may import
|
||||||
|
* everything from there.
|
||||||
|
*
|
||||||
|
* Nothing in here may reach anything that is not a type or a constant: the
|
||||||
|
* point of this module is what it does not import.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Ref } from "react";
|
||||||
|
import type { MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
|
import type { UrlProperties } from "../src/UrlParams";
|
||||||
|
import type { ElementCallHandle, ElementCallHostBridge } from "./host";
|
||||||
|
import {
|
||||||
|
BackgroundStyle,
|
||||||
|
configurationForIntent,
|
||||||
|
HeaderStyle,
|
||||||
|
type UrlConfiguration,
|
||||||
|
UserIntent,
|
||||||
|
} from "../src/UrlConfiguration";
|
||||||
|
|
||||||
|
// How the host and Element Call talk to each other, and what they say
|
||||||
|
export type { ElementCallHandle, ElementCallHostBridge } from "./host";
|
||||||
|
export type { DeviceMuteRequest, DeviceMuteState } from "../src/HostBridge";
|
||||||
|
export type { JoinCallData } from "../src/widget";
|
||||||
|
// The deployment-wide configuration, as distinct from ElementCallConfiguration
|
||||||
|
// below, which is per call
|
||||||
|
export type { ConfigOptions } from "../src/config/ConfigOptions";
|
||||||
|
// The values that appear in ElementCallConfiguration and in the intent, and
|
||||||
|
// what each intent means by default
|
||||||
|
export {
|
||||||
|
BackgroundStyle,
|
||||||
|
configurationForIntent,
|
||||||
|
HeaderStyle,
|
||||||
|
type UrlConfiguration,
|
||||||
|
UserIntent,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How Element Call should behave. Everything is optional; anything left out
|
||||||
|
* takes the default that {@link ElementCallProps.intent} implies.
|
||||||
|
*
|
||||||
|
* This is the behaviour a widget can be configured with through its URL, plus
|
||||||
|
* the one fact about the call a host has a say in here, the background. The
|
||||||
|
* rest of what a widget's URL carries — who the user is, how to reach the
|
||||||
|
* homeserver, where to report analytics, the shared secret of a room that is
|
||||||
|
* encrypted with one — a component host supplies by other routes, or not at
|
||||||
|
* all; and what can change while the call is running, the theme and the
|
||||||
|
* language, is a prop of its own.
|
||||||
|
*/
|
||||||
|
export type ElementCallConfiguration = Partial<UrlConfiguration> &
|
||||||
|
Partial<Pick<UrlProperties, "background">>;
|
||||||
|
|
||||||
|
export interface ElementCallProps {
|
||||||
|
/**
|
||||||
|
* The client to place the call with. Element Call does not authenticate
|
||||||
|
* anyone or manage a session of its own; this one is the host's.
|
||||||
|
*/
|
||||||
|
client: MatrixClient;
|
||||||
|
/** The room to call in. The host's client must already know about it. */
|
||||||
|
roomId: string;
|
||||||
|
/**
|
||||||
|
* What the user asked for — whether they started the call or joined one that
|
||||||
|
* was already running, and whether it is a call in a group or a DM. Element
|
||||||
|
* Call decides what each of those means: whether to show the lobby first,
|
||||||
|
* whether to ring, and so on.
|
||||||
|
*
|
||||||
|
* Defaults to joining an existing group call, which is the most conservative
|
||||||
|
* reading, but a host that knows which button the user pressed should say so.
|
||||||
|
*/
|
||||||
|
intent?: UserIntent;
|
||||||
|
/**
|
||||||
|
* How Element Call should behave, overriding whatever {@link intent} implies.
|
||||||
|
* A host that finds itself setting a lot of these probably wants a different
|
||||||
|
* intent instead.
|
||||||
|
*
|
||||||
|
* Compared by value, so it is fine to write this inline; only a change to
|
||||||
|
* what it says restarts anything.
|
||||||
|
*/
|
||||||
|
config?: ElementCallConfiguration;
|
||||||
|
/**
|
||||||
|
* What Element Call tells the host while the call is running: that the user
|
||||||
|
* has joined or hung up, that it would like to be kept on screen, and so on.
|
||||||
|
* Without one, Element Call assumes nobody is listening.
|
||||||
|
*/
|
||||||
|
hostBridge?: ElementCallHostBridge;
|
||||||
|
/**
|
||||||
|
* What the host tells Element Call: to hang up, to mute, to join. Available
|
||||||
|
* once the component has rendered.
|
||||||
|
*/
|
||||||
|
ref?: Ref<ElementCallHandle>;
|
||||||
|
/**
|
||||||
|
* The theme to show Element Call in, `light` or `dark`. Left out, Element
|
||||||
|
* Call picks. Changes take effect at once, and cost nothing else.
|
||||||
|
*/
|
||||||
|
theme?: string;
|
||||||
|
/**
|
||||||
|
* The language to show Element Call in, as a BCP 47 tag: one of the
|
||||||
|
* `supportedLanguages` the main entry point exports, or something that falls
|
||||||
|
* back to one (`de-AT` to `de`). Left out, the browser's language is used.
|
||||||
|
*
|
||||||
|
* Translations are one thing shared by every Element Call on the page, so
|
||||||
|
* the most recently set language wins for all of them.
|
||||||
|
*/
|
||||||
|
language?: string;
|
||||||
|
}
|
||||||
+6
-96
@@ -36,14 +36,12 @@ import {
|
|||||||
type FC,
|
type FC,
|
||||||
type JSX,
|
type JSX,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
type Ref,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { type MatrixClient } from "matrix-js-sdk";
|
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
@@ -62,10 +60,8 @@ import { RootElementProvider, useRootElement } from "../src/RootElementContext";
|
|||||||
import {
|
import {
|
||||||
configurationForIntent,
|
configurationForIntent,
|
||||||
componentProperties,
|
componentProperties,
|
||||||
type UrlConfiguration,
|
|
||||||
type UrlParams,
|
type UrlParams,
|
||||||
UrlParamsProvider,
|
UrlParamsProvider,
|
||||||
type UrlProperties,
|
|
||||||
UserIntent,
|
UserIntent,
|
||||||
useUrlParams,
|
useUrlParams,
|
||||||
} from "../src/UrlParams";
|
} from "../src/UrlParams";
|
||||||
@@ -79,102 +75,16 @@ import { i18n } from "../src/utils/i18n";
|
|||||||
import { useTheme } from "../src/useTheme";
|
import { useTheme } from "../src/useTheme";
|
||||||
import { useStableValue } from "../src/useStableValue";
|
import { useStableValue } from "../src/useStableValue";
|
||||||
import styles from "./ElementCall.module.css";
|
import styles from "./ElementCall.module.css";
|
||||||
import {
|
import { useComponentHostBridge } from "./host";
|
||||||
type ElementCallHandle,
|
import { type ElementCallProps } from "./api";
|
||||||
type ElementCallHostBridge,
|
|
||||||
useComponentHostBridge,
|
|
||||||
} from "./host";
|
|
||||||
import { supportedLanguages, translationsBackend } from "./localization";
|
import { supportedLanguages, translationsBackend } from "./localization";
|
||||||
|
|
||||||
// The languages Element Call can be shown in
|
// The languages Element Call can be shown in
|
||||||
export { supportedLanguages } from "./localization";
|
export { supportedLanguages } from "./localization";
|
||||||
|
// Everything a host needs to talk about Element Call — the props, the handle,
|
||||||
// How the host and Element Call talk to each other, and what they say
|
// the host bridge, the intents and what each means — also available without
|
||||||
export { type ElementCallHandle, type ElementCallHostBridge } from "./host";
|
// the component itself from `@element-hq/element-call-component/api`
|
||||||
export {
|
export * from "./api";
|
||||||
type DeviceMuteRequest,
|
|
||||||
type DeviceMuteState,
|
|
||||||
} from "../src/HostBridge";
|
|
||||||
export { type JoinCallData } from "../src/widget";
|
|
||||||
// The deployment-wide configuration, as distinct from ElementCallConfiguration
|
|
||||||
// above, which is per call
|
|
||||||
export { type ConfigOptions } from "../src/config/ConfigOptions";
|
|
||||||
// The values that appear in ElementCallConfiguration and in the intent
|
|
||||||
export {
|
|
||||||
BackgroundStyle,
|
|
||||||
HeaderStyle,
|
|
||||||
UserIntent,
|
|
||||||
type UrlConfiguration,
|
|
||||||
} from "../src/UrlParams";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How Element Call should behave. Everything is optional; anything left out
|
|
||||||
* takes the default that {@link ElementCallProps.intent} implies.
|
|
||||||
*
|
|
||||||
* This is the behaviour a widget can be configured with through its URL, plus
|
|
||||||
* the one fact about the call a host has a say in here, the background. The
|
|
||||||
* rest of what a widget's URL carries — who the user is, how to reach the
|
|
||||||
* homeserver, where to report analytics, the shared secret of a room that is
|
|
||||||
* encrypted with one — a component host supplies by other routes, or not at
|
|
||||||
* all; and what can change while the call is running, the theme and the
|
|
||||||
* language, is a prop of its own.
|
|
||||||
*/
|
|
||||||
export type ElementCallConfiguration = Partial<UrlConfiguration> &
|
|
||||||
Partial<Pick<UrlProperties, "background">>;
|
|
||||||
|
|
||||||
export interface ElementCallProps {
|
|
||||||
/**
|
|
||||||
* The client to place the call with. Element Call does not authenticate
|
|
||||||
* anyone or manage a session of its own; this one is the host's.
|
|
||||||
*/
|
|
||||||
client: MatrixClient;
|
|
||||||
/** The room to call in. The host's client must already know about it. */
|
|
||||||
roomId: string;
|
|
||||||
/**
|
|
||||||
* What the user asked for — whether they started the call or joined one that
|
|
||||||
* was already running, and whether it is a call in a group or a DM. Element
|
|
||||||
* Call decides what each of those means: whether to show the lobby first,
|
|
||||||
* whether to ring, and so on.
|
|
||||||
*
|
|
||||||
* Defaults to joining an existing group call, which is the most conservative
|
|
||||||
* reading, but a host that knows which button the user pressed should say so.
|
|
||||||
*/
|
|
||||||
intent?: UserIntent;
|
|
||||||
/**
|
|
||||||
* How Element Call should behave, overriding whatever {@link intent} implies.
|
|
||||||
* A host that finds itself setting a lot of these probably wants a different
|
|
||||||
* intent instead.
|
|
||||||
*
|
|
||||||
* Compared by value, so it is fine to write this inline; only a change to
|
|
||||||
* what it says restarts anything.
|
|
||||||
*/
|
|
||||||
config?: ElementCallConfiguration;
|
|
||||||
/**
|
|
||||||
* What Element Call tells the host while the call is running: that the user
|
|
||||||
* has joined or hung up, that it would like to be kept on screen, and so on.
|
|
||||||
* Without one, Element Call assumes nobody is listening.
|
|
||||||
*/
|
|
||||||
hostBridge?: ElementCallHostBridge;
|
|
||||||
/**
|
|
||||||
* What the host tells Element Call: to hang up, to mute, to join. Available
|
|
||||||
* once the component has rendered.
|
|
||||||
*/
|
|
||||||
ref?: Ref<ElementCallHandle>;
|
|
||||||
/**
|
|
||||||
* The theme to show Element Call in, `light` or `dark`. Left out, Element
|
|
||||||
* Call picks. Changes take effect at once, and cost nothing else.
|
|
||||||
*/
|
|
||||||
theme?: string;
|
|
||||||
/**
|
|
||||||
* The language to show Element Call in, as a BCP 47 tag: one of
|
|
||||||
* {@link supportedLanguages}, or something that falls back to one (`de-AT`
|
|
||||||
* to `de`). Left out, the browser's language is used.
|
|
||||||
*
|
|
||||||
* Translations are one thing shared by every Element Call on the page, so
|
|
||||||
* the most recently set language wins for all of them.
|
|
||||||
*/
|
|
||||||
language?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the things Element Call needs before it can be shown: translations,
|
* Prepares the things Element Call needs before it can be shown: translations,
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
"types": "./dist/types/component/index.d.ts",
|
"types": "./dist/types/component/index.d.ts",
|
||||||
"default": "./dist/element-call.js"
|
"default": "./dist/element-call.js"
|
||||||
},
|
},
|
||||||
|
"./api": {
|
||||||
|
"types": "./dist/types/component/api.d.ts",
|
||||||
|
"default": "./dist/api.js"
|
||||||
|
},
|
||||||
"./style.css": "./dist/element-call.css"
|
"./style.css": "./dist/element-call.css"
|
||||||
},
|
},
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
"rootDir": "..",
|
"rootDir": "..",
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
},
|
},
|
||||||
// The entry point, plus the ambient declarations (CSS modules, `?react` SVGs,
|
// The entry points, plus the ambient declarations (CSS modules, `?react` SVGs,
|
||||||
// `import.meta.env`, …) that the sources it reaches rely on.
|
// `import.meta.env`, …) that the sources they reach rely on.
|
||||||
"include": ["./index.tsx", "../src/@types/*.d.ts"],
|
"include": ["./index.tsx", "./api.ts", "../src/@types/*.d.ts"],
|
||||||
"exclude": []
|
"exclude": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,4 +76,7 @@ Code that builds in only one is a bug.
|
|||||||
- `build:component` — `@element-hq/element-call-component`, sources in `component/`
|
- `build:component` — `@element-hq/element-call-component`, sources in `component/`
|
||||||
(its own pnpm project; run pnpm from the repo root). Host API is in the README;
|
(its own pnpm project; run pnpm from the repo root). Host API is in the README;
|
||||||
`pnpm lint:externals` rejects an import of a `react` / `react-dom` /
|
`pnpm lint:externals` rejects an import of a `react` / `react-dom` /
|
||||||
`matrix-js-sdk` / `livekit-client` subpath the externals list omits.
|
`matrix-js-sdk` / `livekit-client` subpath the externals list omits. Two entry
|
||||||
|
points: `index.tsx` (the component) and `api.ts` (types, enums and
|
||||||
|
`configurationForIntent`, for a host that must not load the component). `api.ts`
|
||||||
|
may only reach types and constants — `src/UrlConfiguration.ts`, not `UrlParams`.
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022-2024 New Vector Ltd.
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How Element Call can be told to behave, and what each intent means.
|
||||||
|
*
|
||||||
|
* Kept apart from {@link UrlParams} on purpose: everything here is constants,
|
||||||
|
* types and one function of them, so that a host of the component can import
|
||||||
|
* these — the enums it needs as values, the defaults an intent implies — without
|
||||||
|
* loading Element Call itself. UrlParams, which reads them from a URL, needs
|
||||||
|
* the router, the config and the rest of the app; this module must not.
|
||||||
|
* `component/api.ts` is built from it as an entry point of its own.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
type RTCCallIntent,
|
||||||
|
type RTCNotificationType,
|
||||||
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
|
||||||
|
import { platform } from "./Platform";
|
||||||
|
|
||||||
|
export enum UserIntent {
|
||||||
|
StartNewCall = "start_call",
|
||||||
|
JoinExistingCall = "join_existing",
|
||||||
|
StartNewCallVoice = "start_call_voice",
|
||||||
|
JoinExistingCallVoice = "join_existing_voice",
|
||||||
|
StartNewCallDM = "start_call_dm",
|
||||||
|
StartNewCallDMVoice = "start_call_dm_voice",
|
||||||
|
JoinExistingCallDM = "join_existing_dm",
|
||||||
|
JoinExistingCallDMVoice = "join_existing_dm_voice",
|
||||||
|
Unknown = "unknown",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HeaderStyle {
|
||||||
|
None = "none",
|
||||||
|
Standard = "standard",
|
||||||
|
AppBar = "app_bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BackgroundStyle {
|
||||||
|
Solid = "solid",
|
||||||
|
Gradient = "gradient",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration for the app, which can be set via URL parameters.
|
||||||
|
* Those property are different to the UrlProperties, since they are all optional
|
||||||
|
* and configure the behavior of the app. Their value is the same if EC is used in
|
||||||
|
* the same context but with different accounts/users.
|
||||||
|
*
|
||||||
|
* Their defaults can be controlled by the `intent` property.
|
||||||
|
*/
|
||||||
|
export interface UrlConfiguration {
|
||||||
|
/**
|
||||||
|
* Whether the app should keep the user confined to the current call/room.
|
||||||
|
*/
|
||||||
|
confineToRoom: boolean;
|
||||||
|
/**
|
||||||
|
* Whether the app should pause before joining the call until it sees an
|
||||||
|
* io.element.join widget action, allowing it to be preloaded.
|
||||||
|
*/
|
||||||
|
preload: boolean;
|
||||||
|
/**
|
||||||
|
* The style of headers to show. "standard" is the default arrangement, "none"
|
||||||
|
* hides the header entirely, and "app_bar" produces a header with a back
|
||||||
|
* button like you might see in mobile apps. The callback for the back button
|
||||||
|
* is window.controls.onBackButtonPressed.
|
||||||
|
*/
|
||||||
|
header: HeaderStyle;
|
||||||
|
/**
|
||||||
|
* Whether the controls should be shown. For screen recording no controls can be desired.
|
||||||
|
*/
|
||||||
|
showControls: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to hide the screen-sharing button.
|
||||||
|
*/
|
||||||
|
hideScreensharing: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
||||||
|
* user's homeserver doesn't provide any.
|
||||||
|
*/
|
||||||
|
allowIceFallback: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the app should use per participant keys for E2EE.
|
||||||
|
*/
|
||||||
|
perParticipantE2EE: boolean;
|
||||||
|
/**
|
||||||
|
* Whether the global JS controls for audio output devices should be enabled,
|
||||||
|
* allowing the list of output devices to be controlled by the app hosting
|
||||||
|
* Element Call.
|
||||||
|
*/
|
||||||
|
controlledAudioDevices: boolean;
|
||||||
|
/**
|
||||||
|
* Setting this flag skips the lobby and brings you in the call directly.
|
||||||
|
* In the widget this can be combined with preload to pass the device settings
|
||||||
|
* with the join widget action.
|
||||||
|
*/
|
||||||
|
skipLobby: boolean;
|
||||||
|
/**
|
||||||
|
* Setting this flag makes element call show the lobby after leaving a call.
|
||||||
|
* This is useful for video rooms.
|
||||||
|
*/
|
||||||
|
returnToLobby: boolean;
|
||||||
|
/**
|
||||||
|
* Whether and what type of notification EC should send, when the user joins the call.
|
||||||
|
*/
|
||||||
|
sendNotificationType?: RTCNotificationType;
|
||||||
|
/**
|
||||||
|
* Whether the app should automatically leave the call when there
|
||||||
|
* is no one left in the call.
|
||||||
|
* This is one part to make the call matrixRTC session behave like a telephone call.
|
||||||
|
*/
|
||||||
|
autoLeaveWhenOthersLeft: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the client should behave like it is awaiting an answer if a notification was sent (wait for call pick up).
|
||||||
|
* This is a no-op if not combined with sendNotificationType.
|
||||||
|
*
|
||||||
|
* This entails:
|
||||||
|
* - show ui that it is awaiting an answer
|
||||||
|
* - play a sound that indicates that it is awaiting an answer
|
||||||
|
* - auto-dismiss the call widget once the notification lifetime expires on the receivers side.
|
||||||
|
*/
|
||||||
|
waitForCallPickup: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable echo cancellation for audio capture.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
|
echoCancellation?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to enable noise suppression for audio capture.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
|
noiseSuppression?: boolean;
|
||||||
|
|
||||||
|
callIntent?: RTCCallIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you need to add a new flag to this interface, prefer a name that describes
|
||||||
|
// a specific behavior (such as 'confineToRoom'), rather than one that describes
|
||||||
|
// the situations that call for this behavior ('isEmbedded'). This makes it
|
||||||
|
// clearer what each flag means, and helps us avoid coupling Element Call's
|
||||||
|
// behavior to the needs of specific consumers.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration implied by what the user meant to do — if they pressed a
|
||||||
|
* Start Call button this would be `start_call`, and if they pressed Join Call,
|
||||||
|
* `join_existing`.
|
||||||
|
*
|
||||||
|
* These are platform-specific defaults, so that a host can start a call by
|
||||||
|
* saying what the user asked for rather than by setting every parameter itself,
|
||||||
|
* and so that what each intent means is Element Call's decision, made in one
|
||||||
|
* place. A host that wants something else states it alongside the intent.
|
||||||
|
*
|
||||||
|
* {@link UserIntent.Unknown} means no intent was stated, and gives the
|
||||||
|
* standalone app's defaults: Element Call owns the whole page, so it offers the
|
||||||
|
* way out of the room that a hosted call must not.
|
||||||
|
*/
|
||||||
|
export function configurationForIntent(intent: UserIntent): UrlConfiguration {
|
||||||
|
// Only constants and `platform` here, so that this depends on nothing but
|
||||||
|
// the intent.
|
||||||
|
let preset: UrlConfiguration = {
|
||||||
|
confineToRoom: true,
|
||||||
|
preload: false,
|
||||||
|
header: platform === "desktop" ? HeaderStyle.None : HeaderStyle.AppBar,
|
||||||
|
showControls: true,
|
||||||
|
hideScreensharing: false,
|
||||||
|
allowIceFallback: true,
|
||||||
|
perParticipantE2EE: true,
|
||||||
|
controlledAudioDevices: platform === "desktop" ? false : true,
|
||||||
|
skipLobby: true,
|
||||||
|
returnToLobby: false,
|
||||||
|
sendNotificationType: "notification",
|
||||||
|
autoLeaveWhenOthersLeft: false,
|
||||||
|
waitForCallPickup: false,
|
||||||
|
};
|
||||||
|
switch (intent) {
|
||||||
|
case UserIntent.StartNewCall:
|
||||||
|
preset.skipLobby = false;
|
||||||
|
preset.callIntent = "video";
|
||||||
|
break;
|
||||||
|
case UserIntent.JoinExistingCall:
|
||||||
|
// On desktop this will be overridden based on which button was used to join the call
|
||||||
|
preset.skipLobby = false;
|
||||||
|
preset.callIntent = "video";
|
||||||
|
break;
|
||||||
|
case UserIntent.StartNewCallVoice:
|
||||||
|
preset.skipLobby = false;
|
||||||
|
preset.callIntent = "audio";
|
||||||
|
break;
|
||||||
|
case UserIntent.JoinExistingCallVoice:
|
||||||
|
// On desktop this will be overridden based on which button was used to join the call
|
||||||
|
preset.skipLobby = false;
|
||||||
|
preset.callIntent = "audio";
|
||||||
|
break;
|
||||||
|
case UserIntent.StartNewCallDMVoice:
|
||||||
|
preset.callIntent = "audio";
|
||||||
|
// Fall through
|
||||||
|
case UserIntent.StartNewCallDM:
|
||||||
|
preset.skipLobby = true;
|
||||||
|
preset.sendNotificationType = "ring";
|
||||||
|
preset.autoLeaveWhenOthersLeft = true;
|
||||||
|
preset.waitForCallPickup = true;
|
||||||
|
preset.callIntent = preset.callIntent ?? "video";
|
||||||
|
break;
|
||||||
|
case UserIntent.JoinExistingCallDMVoice:
|
||||||
|
preset.callIntent = "audio";
|
||||||
|
// Fall through
|
||||||
|
case UserIntent.JoinExistingCallDM:
|
||||||
|
// On desktop this will be overridden based on which button was used to join the call
|
||||||
|
preset.skipLobby = true;
|
||||||
|
preset.autoLeaveWhenOthersLeft = true;
|
||||||
|
preset.callIntent = preset.callIntent ?? "video";
|
||||||
|
break;
|
||||||
|
// Non widget usecase defaults
|
||||||
|
default:
|
||||||
|
preset = {
|
||||||
|
confineToRoom: false,
|
||||||
|
preload: false,
|
||||||
|
header: HeaderStyle.Standard,
|
||||||
|
showControls: true,
|
||||||
|
hideScreensharing: false,
|
||||||
|
allowIceFallback: false,
|
||||||
|
perParticipantE2EE: false,
|
||||||
|
controlledAudioDevices: false,
|
||||||
|
skipLobby: false,
|
||||||
|
returnToLobby: false,
|
||||||
|
sendNotificationType: undefined,
|
||||||
|
autoLeaveWhenOthersLeft: false,
|
||||||
|
waitForCallPickup: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return preset;
|
||||||
|
}
|
||||||
+17
-222
@@ -9,47 +9,36 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { createContext, use, useMemo } from "react";
|
import { createContext, use, useMemo } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
|
||||||
type RTCCallIntent,
|
|
||||||
type RTCNotificationType,
|
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
|
||||||
import { pickBy } from "lodash-es";
|
import { pickBy } from "lodash-es";
|
||||||
|
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { type EncryptionSystem } from "./e2ee/sharedKeyManagement";
|
import { type EncryptionSystem } from "./e2ee/sharedKeyManagement";
|
||||||
import { E2eeType } from "./e2ee/e2eeType";
|
import { E2eeType } from "./e2ee/e2eeType";
|
||||||
import { platform } from "./Platform";
|
import {
|
||||||
|
BackgroundStyle,
|
||||||
|
configurationForIntent,
|
||||||
|
HeaderStyle,
|
||||||
|
type UrlConfiguration,
|
||||||
|
UserIntent,
|
||||||
|
} from "./UrlConfiguration";
|
||||||
import { redact } from "./utils/redact";
|
import { redact } from "./utils/redact";
|
||||||
|
|
||||||
|
// Moved to their own module, so that a host can import them without the rest
|
||||||
|
// of this one; re-exported here as they always were
|
||||||
|
export {
|
||||||
|
BackgroundStyle,
|
||||||
|
configurationForIntent,
|
||||||
|
HeaderStyle,
|
||||||
|
type UrlConfiguration,
|
||||||
|
UserIntent,
|
||||||
|
} from "./UrlConfiguration";
|
||||||
|
|
||||||
interface RoomIdentifier {
|
interface RoomIdentifier {
|
||||||
roomAlias: string | null;
|
roomAlias: string | null;
|
||||||
roomId: string | null;
|
roomId: string | null;
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserIntent {
|
|
||||||
StartNewCall = "start_call",
|
|
||||||
JoinExistingCall = "join_existing",
|
|
||||||
StartNewCallVoice = "start_call_voice",
|
|
||||||
JoinExistingCallVoice = "join_existing_voice",
|
|
||||||
StartNewCallDM = "start_call_dm",
|
|
||||||
StartNewCallDMVoice = "start_call_dm_voice",
|
|
||||||
JoinExistingCallDM = "join_existing_dm",
|
|
||||||
JoinExistingCallDMVoice = "join_existing_dm_voice",
|
|
||||||
Unknown = "unknown",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum HeaderStyle {
|
|
||||||
None = "none",
|
|
||||||
Standard = "standard",
|
|
||||||
AppBar = "app_bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BackgroundStyle {
|
|
||||||
Solid = "solid",
|
|
||||||
Gradient = "gradient",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UrlProperties are used to pass required data to the widget.
|
* The UrlProperties are used to pass required data to the widget.
|
||||||
* Those are different in different rooms, users, devices. They do not configure the behavior of the
|
* Those are different in different rooms, users, devices. They do not configure the behavior of the
|
||||||
@@ -166,109 +155,6 @@ export interface UrlProperties {
|
|||||||
*/
|
*/
|
||||||
background: BackgroundStyle;
|
background: BackgroundStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration for the app, which can be set via URL parameters.
|
|
||||||
* Those property are different to the UrlProperties, since they are all optional
|
|
||||||
* and configure the behavior of the app. Their value is the same if EC is used in
|
|
||||||
* the same context but with different accounts/users.
|
|
||||||
*
|
|
||||||
* Their defaults can be controlled by the `intent` property.
|
|
||||||
*/
|
|
||||||
export interface UrlConfiguration {
|
|
||||||
/**
|
|
||||||
* Whether the app should keep the user confined to the current call/room.
|
|
||||||
*/
|
|
||||||
confineToRoom: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the app should pause before joining the call until it sees an
|
|
||||||
* io.element.join widget action, allowing it to be preloaded.
|
|
||||||
*/
|
|
||||||
preload: boolean;
|
|
||||||
/**
|
|
||||||
* The style of headers to show. "standard" is the default arrangement, "none"
|
|
||||||
* hides the header entirely, and "app_bar" produces a header with a back
|
|
||||||
* button like you might see in mobile apps. The callback for the back button
|
|
||||||
* is window.controls.onBackButtonPressed.
|
|
||||||
*/
|
|
||||||
header: HeaderStyle;
|
|
||||||
/**
|
|
||||||
* Whether the controls should be shown. For screen recording no controls can be desired.
|
|
||||||
*/
|
|
||||||
showControls: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to hide the screen-sharing button.
|
|
||||||
*/
|
|
||||||
hideScreensharing: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
|
||||||
* user's homeserver doesn't provide any.
|
|
||||||
*/
|
|
||||||
allowIceFallback: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the app should use per participant keys for E2EE.
|
|
||||||
*/
|
|
||||||
perParticipantE2EE: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the global JS controls for audio output devices should be enabled,
|
|
||||||
* allowing the list of output devices to be controlled by the app hosting
|
|
||||||
* Element Call.
|
|
||||||
*/
|
|
||||||
controlledAudioDevices: boolean;
|
|
||||||
/**
|
|
||||||
* Setting this flag skips the lobby and brings you in the call directly.
|
|
||||||
* In the widget this can be combined with preload to pass the device settings
|
|
||||||
* with the join widget action.
|
|
||||||
*/
|
|
||||||
skipLobby: boolean;
|
|
||||||
/**
|
|
||||||
* Setting this flag makes element call show the lobby after leaving a call.
|
|
||||||
* This is useful for video rooms.
|
|
||||||
*/
|
|
||||||
returnToLobby: boolean;
|
|
||||||
/**
|
|
||||||
* Whether and what type of notification EC should send, when the user joins the call.
|
|
||||||
*/
|
|
||||||
sendNotificationType?: RTCNotificationType;
|
|
||||||
/**
|
|
||||||
* Whether the app should automatically leave the call when there
|
|
||||||
* is no one left in the call.
|
|
||||||
* This is one part to make the call matrixRTC session behave like a telephone call.
|
|
||||||
*/
|
|
||||||
autoLeaveWhenOthersLeft: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the client should behave like it is awaiting an answer if a notification was sent (wait for call pick up).
|
|
||||||
* This is a no-op if not combined with sendNotificationType.
|
|
||||||
*
|
|
||||||
* This entails:
|
|
||||||
* - show ui that it is awaiting an answer
|
|
||||||
* - play a sound that indicates that it is awaiting an answer
|
|
||||||
* - auto-dismiss the call widget once the notification lifetime expires on the receivers side.
|
|
||||||
*/
|
|
||||||
waitForCallPickup: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to enable echo cancellation for audio capture.
|
|
||||||
* Defaults to true.
|
|
||||||
*/
|
|
||||||
echoCancellation?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to enable noise suppression for audio capture.
|
|
||||||
* Defaults to true.
|
|
||||||
*/
|
|
||||||
noiseSuppression?: boolean;
|
|
||||||
|
|
||||||
callIntent?: RTCCallIntent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you need to add a new flag to this interface, prefer a name that describes
|
|
||||||
// a specific behavior (such as 'confineToRoom'), rather than one that describes
|
|
||||||
// the situations that call for this behavior ('isEmbedded'). This makes it
|
|
||||||
// clearer what each flag means, and helps us avoid coupling Element Call's
|
|
||||||
// behavior to the needs of specific consumers.
|
|
||||||
export interface UrlParams extends UrlProperties, UrlConfiguration {}
|
export interface UrlParams extends UrlProperties, UrlConfiguration {}
|
||||||
|
|
||||||
class ParamParser {
|
class ParamParser {
|
||||||
@@ -354,97 +240,6 @@ export const getUrlParams = (
|
|||||||
return params;
|
return params;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* The configuration implied by what the user meant to do — if they pressed a
|
|
||||||
* Start Call button this would be `start_call`, and if they pressed Join Call,
|
|
||||||
* `join_existing`.
|
|
||||||
*
|
|
||||||
* These are platform-specific defaults, so that a host can start a call by
|
|
||||||
* saying what the user asked for rather than by setting every parameter itself,
|
|
||||||
* and so that what each intent means is Element Call's decision, made in one
|
|
||||||
* place. A host that wants something else states it alongside the intent.
|
|
||||||
*
|
|
||||||
* {@link UserIntent.Unknown} means no intent was stated, and gives the
|
|
||||||
* standalone app's defaults: Element Call owns the whole page, so it offers the
|
|
||||||
* way out of the room that a hosted call must not.
|
|
||||||
*/
|
|
||||||
export function configurationForIntent(intent: UserIntent): UrlConfiguration {
|
|
||||||
// Only constants and `platform` here, so that this depends on nothing but
|
|
||||||
// the intent.
|
|
||||||
let preset: UrlConfiguration = {
|
|
||||||
confineToRoom: true,
|
|
||||||
preload: false,
|
|
||||||
header: platform === "desktop" ? HeaderStyle.None : HeaderStyle.AppBar,
|
|
||||||
showControls: true,
|
|
||||||
hideScreensharing: false,
|
|
||||||
allowIceFallback: true,
|
|
||||||
perParticipantE2EE: true,
|
|
||||||
controlledAudioDevices: platform === "desktop" ? false : true,
|
|
||||||
skipLobby: true,
|
|
||||||
returnToLobby: false,
|
|
||||||
sendNotificationType: "notification",
|
|
||||||
autoLeaveWhenOthersLeft: false,
|
|
||||||
waitForCallPickup: false,
|
|
||||||
};
|
|
||||||
switch (intent) {
|
|
||||||
case UserIntent.StartNewCall:
|
|
||||||
preset.skipLobby = false;
|
|
||||||
preset.callIntent = "video";
|
|
||||||
break;
|
|
||||||
case UserIntent.JoinExistingCall:
|
|
||||||
// On desktop this will be overridden based on which button was used to join the call
|
|
||||||
preset.skipLobby = false;
|
|
||||||
preset.callIntent = "video";
|
|
||||||
break;
|
|
||||||
case UserIntent.StartNewCallVoice:
|
|
||||||
preset.skipLobby = false;
|
|
||||||
preset.callIntent = "audio";
|
|
||||||
break;
|
|
||||||
case UserIntent.JoinExistingCallVoice:
|
|
||||||
// On desktop this will be overridden based on which button was used to join the call
|
|
||||||
preset.skipLobby = false;
|
|
||||||
preset.callIntent = "audio";
|
|
||||||
break;
|
|
||||||
case UserIntent.StartNewCallDMVoice:
|
|
||||||
preset.callIntent = "audio";
|
|
||||||
// Fall through
|
|
||||||
case UserIntent.StartNewCallDM:
|
|
||||||
preset.skipLobby = true;
|
|
||||||
preset.sendNotificationType = "ring";
|
|
||||||
preset.autoLeaveWhenOthersLeft = true;
|
|
||||||
preset.waitForCallPickup = true;
|
|
||||||
preset.callIntent = preset.callIntent ?? "video";
|
|
||||||
break;
|
|
||||||
case UserIntent.JoinExistingCallDMVoice:
|
|
||||||
preset.callIntent = "audio";
|
|
||||||
// Fall through
|
|
||||||
case UserIntent.JoinExistingCallDM:
|
|
||||||
// On desktop this will be overridden based on which button was used to join the call
|
|
||||||
preset.skipLobby = true;
|
|
||||||
preset.autoLeaveWhenOthersLeft = true;
|
|
||||||
preset.callIntent = preset.callIntent ?? "video";
|
|
||||||
break;
|
|
||||||
// Non widget usecase defaults
|
|
||||||
default:
|
|
||||||
preset = {
|
|
||||||
confineToRoom: false,
|
|
||||||
preload: false,
|
|
||||||
header: HeaderStyle.Standard,
|
|
||||||
showControls: true,
|
|
||||||
hideScreensharing: false,
|
|
||||||
allowIceFallback: false,
|
|
||||||
perParticipantE2EE: false,
|
|
||||||
controlledAudioDevices: false,
|
|
||||||
skipLobby: false,
|
|
||||||
returnToLobby: false,
|
|
||||||
sendNotificationType: undefined,
|
|
||||||
autoLeaveWhenOthersLeft: false,
|
|
||||||
waitForCallPickup: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return preset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link UrlProperties} for Element Call running as a component inside a
|
* The {@link UrlProperties} for Element Call running as a component inside a
|
||||||
* host application.
|
* host application.
|
||||||
|
|||||||
@@ -49,8 +49,19 @@ export default defineConfig(({ mode }) => {
|
|||||||
cssCodeSplit: false,
|
cssCodeSplit: false,
|
||||||
lib: {
|
lib: {
|
||||||
formats: ["es" as const],
|
formats: ["es" as const],
|
||||||
entry: "./component/index.tsx",
|
entry: {
|
||||||
fileName: "element-call",
|
// The component
|
||||||
|
"element-call": "./component/index.tsx",
|
||||||
|
// What a host needs to talk about it, without it (see component/api.ts).
|
||||||
|
// Built as an entry point of its own so that importing it does not
|
||||||
|
// load the component: the bundler gives it a chunk that reaches only
|
||||||
|
// what it imports.
|
||||||
|
api: "./component/api.ts",
|
||||||
|
},
|
||||||
|
fileName: (_format, entryName) => `${entryName}.js`,
|
||||||
|
// The one stylesheet (see cssCodeSplit) keeps the name it had when the
|
||||||
|
// component was the only entry
|
||||||
|
cssFileName: "element-call",
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
// The host already has these, and a second copy of any of them does not
|
// The host already has these, and a second copy of any of them does not
|
||||||
|
|||||||
Reference in New Issue
Block a user