mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
refactor UrlParams to use a preset intent system
This commit is contained in:
323
src/UrlParams.ts
323
src/UrlParams.ts
@@ -9,10 +9,12 @@ import { 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 RTCNotificationType } from "matrix-js-sdk/lib/matrixrtc";
|
import { type RTCNotificationType } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
|
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";
|
||||||
|
|
||||||
interface RoomIdentifier {
|
interface RoomIdentifier {
|
||||||
roomAlias: string | null;
|
roomAlias: string | null;
|
||||||
@@ -32,12 +34,12 @@ export enum HeaderStyle {
|
|||||||
AppBar = "app_bar",
|
AppBar = "app_bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 UrlProperties are used to pass required data to the widget.
|
||||||
// the situations that call for this behavior ('isEmbedded'). This makes it
|
* Those are different in different rooms, users, devices. They do not configure the behavior of the
|
||||||
// clearer what each flag means, and helps us avoid coupling Element Call's
|
* widget but provide the required data to the widget.
|
||||||
// behavior to the needs of specific consumers.
|
*/
|
||||||
export interface UrlParams {
|
export interface UrlProperties {
|
||||||
// Widget api related params
|
// Widget api related params
|
||||||
widgetId: string | null;
|
widgetId: string | null;
|
||||||
parentUrl: string | null;
|
parentUrl: string | null;
|
||||||
@@ -49,45 +51,11 @@ export interface UrlParams {
|
|||||||
* is also not validated, where it is in useRoomIdentifier().
|
* is also not validated, where it is in useRoomIdentifier().
|
||||||
*/
|
*/
|
||||||
roomId: string | null;
|
roomId: string | null;
|
||||||
/**
|
|
||||||
* Whether the app should keep the user confined to the current call/room.
|
|
||||||
*/
|
|
||||||
confineToRoom: boolean;
|
|
||||||
/**
|
|
||||||
* Whether upon entering a room, the user should be prompted to launch the
|
|
||||||
* native mobile app. (Affects only Android and iOS.)
|
|
||||||
*
|
|
||||||
* The app prompt must also be enabled in the config for this to take effect.
|
|
||||||
*/
|
|
||||||
appPrompt: 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 to use end-to-end encryption.
|
|
||||||
*/
|
|
||||||
e2eEnabled: boolean;
|
|
||||||
/**
|
/**
|
||||||
* The user's ID (only used in matryoshka mode).
|
* The user's ID (only used in matryoshka mode).
|
||||||
*/
|
*/
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The display name to use for auto-registration.
|
* The display name to use for auto-registration.
|
||||||
*/
|
*/
|
||||||
@@ -125,14 +93,96 @@ export interface UrlParams {
|
|||||||
*/
|
*/
|
||||||
posthogApiKey: string | null;
|
posthogApiKey: string | null;
|
||||||
/**
|
/**
|
||||||
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
* Whether to use end-to-end encryption.
|
||||||
* user's homeserver doesn't provide any.
|
|
||||||
*/
|
*/
|
||||||
allowIceFallback: boolean;
|
e2eEnabled: boolean;
|
||||||
/**
|
/**
|
||||||
* E2EE password
|
* E2EE password
|
||||||
*/
|
*/
|
||||||
password: string | null;
|
password: string | null;
|
||||||
|
/** This defines the homeserver that is going to be used when joining a room.
|
||||||
|
* It has to be set to a non default value for links to rooms
|
||||||
|
* that are not on the default homeserver,
|
||||||
|
* that is in use for the current user.
|
||||||
|
*/
|
||||||
|
viaServers: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This defines the homeserver that is going to be used when registering
|
||||||
|
* a new (guest) user.
|
||||||
|
* This can be user to configure a non default guest user server when
|
||||||
|
* creating a spa link.
|
||||||
|
*/
|
||||||
|
homeserver: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rageshake submit URL. This is only used in the embedded package of Element Call.
|
||||||
|
*/
|
||||||
|
rageshakeSubmitUrl: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Sentry DSN. This is only used in the embedded package of Element Call.
|
||||||
|
*/
|
||||||
|
sentryDsn: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Sentry environment. This is only used in the embedded package of Element Call.
|
||||||
|
*/
|
||||||
|
sentryEnvironment: string | null;
|
||||||
|
/**
|
||||||
|
* The theme to use for element call.
|
||||||
|
* can be "light", "dark", "light-high-contrast" or "dark-high-contrast".
|
||||||
|
*/
|
||||||
|
theme: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration for the app. It can be set via URL parameters.
|
||||||
|
* Those parameters are different to the UrlProperties, since they are all optional
|
||||||
|
* and configure the behavior of the app. There value is the same if EC is used in
|
||||||
|
* the same context but different accoutns/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 upon entering a room, the user should be prompted to launch the
|
||||||
|
* native mobile app. (Affects only Android and iOS.)
|
||||||
|
*
|
||||||
|
* The app prompt must also be enabled in the config for this to take effect.
|
||||||
|
*/
|
||||||
|
appPrompt: 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.
|
* Whether the app should use per participant keys for E2EE.
|
||||||
*/
|
*/
|
||||||
@@ -154,52 +204,19 @@ export interface UrlParams {
|
|||||||
* This is useful for video rooms.
|
* This is useful for video rooms.
|
||||||
*/
|
*/
|
||||||
returnToLobby: boolean;
|
returnToLobby: boolean;
|
||||||
/**
|
|
||||||
* The theme to use for element call.
|
|
||||||
* can be "light", "dark", "light-high-contrast" or "dark-high-contrast".
|
|
||||||
*/
|
|
||||||
theme: string | null;
|
|
||||||
/** This defines the homeserver that is going to be used when joining a room.
|
|
||||||
* It has to be set to a non default value for links to rooms
|
|
||||||
* that are not on the default homeserver,
|
|
||||||
* that is in use for the current user.
|
|
||||||
*/
|
|
||||||
viaServers: string | null;
|
|
||||||
/**
|
|
||||||
* This defines the homeserver that is going to be used when registering
|
|
||||||
* a new (guest) user.
|
|
||||||
* This can be user to configure a non default guest user server when
|
|
||||||
* creating a spa link.
|
|
||||||
*/
|
|
||||||
homeserver: string | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user's intent with respect to the call.
|
|
||||||
* e.g. if they clicked a Start Call button, this would be `start_call`.
|
|
||||||
* If it was a Join Call button, it would be `join_existing`.
|
|
||||||
*/
|
|
||||||
intent: string | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The rageshake submit URL. This is only used in the embedded package of Element Call.
|
|
||||||
*/
|
|
||||||
rageshakeSubmitUrl: string | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Sentry DSN. This is only used in the embedded package of Element Call.
|
|
||||||
*/
|
|
||||||
sentryDsn: string | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Sentry environment. This is only used in the embedded package of Element Call.
|
|
||||||
*/
|
|
||||||
sentryEnvironment: string | null;
|
|
||||||
/**
|
/**
|
||||||
* Whether and what type of notification EC should send, when the user joins the call.
|
* Whether and what type of notification EC should send, when the user joins the call.
|
||||||
*/
|
*/
|
||||||
sendNotificationType?: RTCNotificationType;
|
sendNotificationType?: RTCNotificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {}
|
||||||
|
|
||||||
// This is here as a stopgap, but what would be far nicer is a function that
|
// This is here as a stopgap, but what would be far nicer is a function that
|
||||||
// takes a UrlParams and returns a query string. That would enable us to
|
// takes a UrlParams and returns a query string. That would enable us to
|
||||||
// consolidate all the data about URL parameters and their meanings to this one
|
// consolidate all the data about URL parameters and their meanings to this one
|
||||||
@@ -251,6 +268,10 @@ class ParamParser {
|
|||||||
const param = this.getParam(name);
|
const param = this.getParam(name);
|
||||||
return param === null ? defaultValue : param !== "false";
|
return param === null ? defaultValue : param !== "false";
|
||||||
}
|
}
|
||||||
|
public getFlag(name: string): boolean | undefined {
|
||||||
|
const param = this.getParam(name);
|
||||||
|
return param !== null ? param !== "false" : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,18 +288,70 @@ export const getUrlParams = (
|
|||||||
|
|
||||||
const fontScale = parseFloat(parser.getParam("fontScale") ?? "");
|
const fontScale = parseFloat(parser.getParam("fontScale") ?? "");
|
||||||
|
|
||||||
let intent = parser.getParam("intent");
|
/**
|
||||||
|
* The user's intent with respect to the call.
|
||||||
|
* e.g. if they clicked a Start Call button, this would be `start_call`.
|
||||||
|
* If it was a Join Call button, it would be `join_existing`.
|
||||||
|
* This is a platform specific default set of parameters, that allows to minize the configuration
|
||||||
|
* needed to start a call. And empowers the EC codebase to control the platform/intent behavior in
|
||||||
|
* a central place.
|
||||||
|
*
|
||||||
|
* In short: either provide url query parameters of UrlConfiguration or set the intent
|
||||||
|
* (or the global defaults will be used).
|
||||||
|
*/
|
||||||
|
let intent = parser.getParam("intent") as UserIntent | null;
|
||||||
|
|
||||||
if (!intent || !Object.values(UserIntent).includes(intent as UserIntent)) {
|
if (!intent || !Object.values(UserIntent).includes(intent as UserIntent)) {
|
||||||
intent = UserIntent.Unknown;
|
intent = UserIntent.Unknown;
|
||||||
}
|
}
|
||||||
|
// Here we only use constants and `platform` to determine the intent preset.
|
||||||
// Check hideHeader for backwards compatibility. If header is set, hideHeader
|
let intentPreset: UrlConfiguration;
|
||||||
// is ignored.
|
switch (intent) {
|
||||||
const header =
|
case UserIntent.StartNewCall:
|
||||||
parser.getParam("header") ??
|
intentPreset = {
|
||||||
(parser.getFlagParam("hideHeader")
|
confineToRoom: true,
|
||||||
? HeaderStyle.None
|
appPrompt: false,
|
||||||
: HeaderStyle.Standard);
|
preload: true,
|
||||||
|
header: HeaderStyle.None,
|
||||||
|
showControls: true,
|
||||||
|
hideScreensharing: false,
|
||||||
|
allowIceFallback: true,
|
||||||
|
perParticipantE2EE: true,
|
||||||
|
controlledAudioDevices: platform === "desktop" ? false : true,
|
||||||
|
skipLobby: true,
|
||||||
|
returnToLobby: false,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case UserIntent.JoinExistingCall:
|
||||||
|
intentPreset = {
|
||||||
|
confineToRoom: true,
|
||||||
|
appPrompt: false,
|
||||||
|
preload: true,
|
||||||
|
header: HeaderStyle.None,
|
||||||
|
showControls: true,
|
||||||
|
hideScreensharing: false,
|
||||||
|
allowIceFallback: true,
|
||||||
|
perParticipantE2EE: true,
|
||||||
|
controlledAudioDevices: platform === "desktop" ? false : true,
|
||||||
|
skipLobby: false,
|
||||||
|
returnToLobby: false,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
intentPreset = {
|
||||||
|
confineToRoom: false,
|
||||||
|
appPrompt: true,
|
||||||
|
preload: false,
|
||||||
|
header: HeaderStyle.Standard,
|
||||||
|
showControls: true,
|
||||||
|
hideScreensharing: false,
|
||||||
|
allowIceFallback: false,
|
||||||
|
perParticipantE2EE: false,
|
||||||
|
controlledAudioDevices: false,
|
||||||
|
skipLobby: false,
|
||||||
|
returnToLobby: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const sendNotificationType = ["ring", "notification"].includes(
|
const sendNotificationType = ["ring", "notification"].includes(
|
||||||
parser.getParam("sendNotificationType") ?? "",
|
parser.getParam("sendNotificationType") ?? "",
|
||||||
@@ -288,25 +361,14 @@ export const getUrlParams = (
|
|||||||
const widgetId = parser.getParam("widgetId");
|
const widgetId = parser.getParam("widgetId");
|
||||||
const parentUrl = parser.getParam("parentUrl");
|
const parentUrl = parser.getParam("parentUrl");
|
||||||
const isWidget = !!widgetId && !!parentUrl;
|
const isWidget = !!widgetId && !!parentUrl;
|
||||||
|
const properties: UrlProperties = {
|
||||||
return {
|
|
||||||
widgetId,
|
widgetId,
|
||||||
parentUrl,
|
parentUrl,
|
||||||
|
|
||||||
// NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl:
|
// NB. we don't validate roomId here as we do in getRoomIdentifierFromUrl:
|
||||||
// what would we do if it were invalid? If the widget API says that's what
|
// what would we do if it were invalid? If the widget API says that's what
|
||||||
// the room ID is, then that's what it is.
|
// the room ID is, then that's what it is.
|
||||||
roomId: parser.getParam("roomId"),
|
roomId: parser.getParam("roomId"),
|
||||||
password: parser.getParam("password"),
|
password: parser.getParam("password"),
|
||||||
// This flag has 'embed' as an alias for historical reasons
|
|
||||||
confineToRoom:
|
|
||||||
parser.getFlagParam("confineToRoom") || parser.getFlagParam("embed"),
|
|
||||||
appPrompt: parser.getFlagParam("appPrompt", true),
|
|
||||||
preload: isWidget ? parser.getFlagParam("preload") : false,
|
|
||||||
header: header as HeaderStyle,
|
|
||||||
showControls: parser.getFlagParam("showControls", true),
|
|
||||||
hideScreensharing: parser.getFlagParam("hideScreensharing"),
|
|
||||||
e2eEnabled: parser.getFlagParam("enableE2EE", true),
|
|
||||||
userId: isWidget ? parser.getParam("userId") : null,
|
userId: isWidget ? parser.getParam("userId") : null,
|
||||||
displayName: parser.getParam("displayName"),
|
displayName: parser.getParam("displayName"),
|
||||||
deviceId: isWidget ? parser.getParam("deviceId") : null,
|
deviceId: isWidget ? parser.getParam("deviceId") : null,
|
||||||
@@ -314,24 +376,9 @@ export const getUrlParams = (
|
|||||||
lang: parser.getParam("lang"),
|
lang: parser.getParam("lang"),
|
||||||
fonts: parser.getAllParams("font"),
|
fonts: parser.getAllParams("font"),
|
||||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||||
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
|
||||||
perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"),
|
|
||||||
controlledAudioDevices: parser.getFlagParam(
|
|
||||||
"controlledAudioDevices",
|
|
||||||
// the deprecated property name
|
|
||||||
parser.getFlagParam("controlledMediaDevices"),
|
|
||||||
),
|
|
||||||
skipLobby: parser.getFlagParam(
|
|
||||||
"skipLobby",
|
|
||||||
isWidget && intent === UserIntent.StartNewCall,
|
|
||||||
),
|
|
||||||
// In SPA mode the user should always exit to the home screen when hanging
|
|
||||||
// up, rather than being sent back to the lobby
|
|
||||||
returnToLobby: isWidget ? parser.getFlagParam("returnToLobby") : false,
|
|
||||||
theme: parser.getParam("theme"),
|
theme: parser.getParam("theme"),
|
||||||
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
viaServers: !isWidget ? parser.getParam("viaServers") : null,
|
||||||
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
homeserver: !isWidget ? parser.getParam("homeserver") : null,
|
||||||
intent,
|
|
||||||
posthogApiHost: parser.getParam("posthogApiHost"),
|
posthogApiHost: parser.getParam("posthogApiHost"),
|
||||||
posthogApiKey: parser.getParam("posthogApiKey"),
|
posthogApiKey: parser.getParam("posthogApiKey"),
|
||||||
posthogUserId:
|
posthogUserId:
|
||||||
@@ -339,8 +386,44 @@ export const getUrlParams = (
|
|||||||
rageshakeSubmitUrl: parser.getParam("rageshakeSubmitUrl"),
|
rageshakeSubmitUrl: parser.getParam("rageshakeSubmitUrl"),
|
||||||
sentryDsn: parser.getParam("sentryDsn"),
|
sentryDsn: parser.getParam("sentryDsn"),
|
||||||
sentryEnvironment: parser.getParam("sentryEnvironment"),
|
sentryEnvironment: parser.getParam("sentryEnvironment"),
|
||||||
|
e2eEnabled: parser.getFlagParam("enableE2EE", true),
|
||||||
|
};
|
||||||
|
|
||||||
|
const configuration: Partial<UrlConfiguration> = {
|
||||||
|
// This flag has 'embed' as an alias for historical reasons
|
||||||
|
confineToRoom: parser.getFlag("confineToRoom") ?? parser.getFlag("embed"),
|
||||||
|
appPrompt: parser.getFlag("appPrompt"),
|
||||||
|
preload: isWidget ? parser.getFlag("preload") : undefined,
|
||||||
|
// Check hideHeader for backwards compatibility. If header is set, hideHeader
|
||||||
|
// is ignored.
|
||||||
|
header:
|
||||||
|
(parser.getParam("header") as HeaderStyle) ??
|
||||||
|
(parser.getFlag("hideHeader") !== undefined
|
||||||
|
? parser.getFlagParam("hideHeader")
|
||||||
|
? HeaderStyle.None
|
||||||
|
: HeaderStyle.Standard
|
||||||
|
: undefined),
|
||||||
|
showControls: parser.getFlag("showControls"),
|
||||||
|
hideScreensharing: parser.getFlag("hideScreensharing"),
|
||||||
|
allowIceFallback: parser.getFlag("allowIceFallback"),
|
||||||
|
perParticipantE2EE: parser.getFlag("perParticipantE2EE"),
|
||||||
|
controlledAudioDevices:
|
||||||
|
parser.getFlag(
|
||||||
|
"controlledAudioDevices",
|
||||||
|
// the deprecated property name
|
||||||
|
) ?? parser.getFlag("controlledMediaDevices"),
|
||||||
|
skipLobby: isWidget ? parser.getFlag("skipLobby") : false,
|
||||||
|
// In SPA mode the user should always exit to the home screen when hanging
|
||||||
|
// up, rather than being sent back to the lobby
|
||||||
|
returnToLobby: isWidget ? parser.getFlag("returnToLobby") : false,
|
||||||
sendNotificationType,
|
sendNotificationType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...properties,
|
||||||
|
...intentPreset,
|
||||||
|
...pickBy(configuration, (v) => v !== undefined),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user