Merge pull request #4605 from matrix-org/t3chguy/e2eedefault
Add .well-known option to control default e2ee behaviour
This commit is contained in:
commit
eccacb1bc7
13 changed files with 136 additions and 88 deletions
|
@ -63,4 +63,25 @@ limitations under the License.
|
|||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SecurityUserSettingsTab_warning {
|
||||
color: $notice-primary-color;
|
||||
position: relative;
|
||||
padding-left: 40px;
|
||||
margin-top: 30px;
|
||||
|
||||
&::before {
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 0 center;
|
||||
mask-size: $font-24px;
|
||||
position: absolute;
|
||||
width: $font-24px;
|
||||
height: $font-24px;
|
||||
content: "";
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $notice-primary-color;
|
||||
mask-image: url('$(res)/img/feather-customised/alert-triangle.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
res/img/feather-customised/alert-triangle.svg
Normal file
5
res/img/feather-customised/alert-triangle.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.29 3.86002L1.82002 18C1.46466 18.6154 1.46254 19.3732 1.81445 19.9905C2.16635 20.6079 2.81943 20.9922 3.53002 21H20.47C21.1806 20.9922 21.8337 20.6079 22.1856 19.9905C22.5375 19.3732 22.5354 18.6154 22.18 18L13.71 3.86002C13.3475 3.2623 12.6991 2.89728 12 2.89728C11.3009 2.89728 10.6526 3.2623 10.29 3.86002Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 9V13" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="12" cy="17" r="1" fill="#2E2F32"/>
|
||||
</svg>
|
After Width: | Height: | Size: 665 B |
|
@ -22,13 +22,13 @@ import {
|
|||
import {
|
||||
hideToast as hideSetupEncryptionToast,
|
||||
Kind as SetupKind,
|
||||
Kind,
|
||||
showToast as showSetupEncryptionToast
|
||||
} from "./toasts/SetupEncryptionToast";
|
||||
import {
|
||||
hideToast as hideUnverifiedSessionsToast,
|
||||
showToast as showUnverifiedSessionsToast
|
||||
} from "./toasts/UnverifiedSessionToast";
|
||||
import {privateShouldBeEncrypted} from "./createRoom";
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
|
@ -169,6 +169,14 @@ export default class DeviceListener {
|
|||
return this.keyBackupInfo;
|
||||
}
|
||||
|
||||
private shouldShowSetupEncryptionToast() {
|
||||
// In a default configuration, show the toasts. If the well-known config causes e2ee default to be false
|
||||
// then do not show the toasts until user is in at least one encrypted room.
|
||||
if (privateShouldBeEncrypted()) return true;
|
||||
const cli = MatrixClientPeg.get();
|
||||
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
||||
}
|
||||
|
||||
async _recheck() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
|
@ -184,7 +192,7 @@ export default class DeviceListener {
|
|||
|
||||
if (this.dismissedThisDeviceToast || crossSigningReady) {
|
||||
hideSetupEncryptionToast();
|
||||
} else {
|
||||
} else if (this.shouldShowSetupEncryptionToast()) {
|
||||
// make sure our keys are finished downloading
|
||||
await cli.downloadKeys([cli.getUserId()]);
|
||||
// cross signing isn't enabled - nag to enable it
|
||||
|
@ -196,10 +204,10 @@ export default class DeviceListener {
|
|||
const backupInfo = await this._getKeyBackupInfo();
|
||||
if (backupInfo) {
|
||||
// No cross-signing on account but key backup available (upgrade encryption)
|
||||
showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION);
|
||||
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
||||
} else {
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION);
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -622,7 +622,7 @@ async function startMatrixClient(startSyncing=true) {
|
|||
}
|
||||
|
||||
// Now that we have a MatrixClientPeg, update the Jitsi info
|
||||
await Jitsi.getInstance().update();
|
||||
await Jitsi.getInstance().start();
|
||||
|
||||
// dispatch that we finished starting up to wire up any other bits
|
||||
// of the matrix client that cannot be set prior to starting up.
|
||||
|
|
|
@ -49,6 +49,7 @@ export interface IOpts {
|
|||
initialSyncLimit?: number;
|
||||
pendingEventOrdering?: "detached" | "chronological";
|
||||
lazyLoadMembers?: boolean;
|
||||
clientWellKnownPollPeriod?: number;
|
||||
}
|
||||
|
||||
export interface IMatrixClientPeg {
|
||||
|
@ -209,6 +210,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
|||
// the react sdk doesn't work without this, so don't allow
|
||||
opts.pendingEventOrdering = "detached";
|
||||
opts.lazyLoadMembers = true;
|
||||
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
|
||||
|
||||
// Connect the matrix client to the dispatcher and setting handlers
|
||||
MatrixActionCreators.start(this.matrixClient);
|
||||
|
|
|
@ -24,6 +24,7 @@ import withValidation from '../elements/Validation';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {privateShouldBeEncrypted} from "../../../createRoom";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'CreateRoomDialog',
|
||||
|
@ -36,7 +37,7 @@ export default createReactClass({
|
|||
const config = SdkConfig.get();
|
||||
return {
|
||||
isPublic: this.props.defaultPublic || false,
|
||||
isEncrypted: true,
|
||||
isEncrypted: privateShouldBeEncrypted(),
|
||||
name: "",
|
||||
topic: "",
|
||||
alias: "",
|
||||
|
@ -193,6 +194,13 @@ export default createReactClass({
|
|||
|
||||
let e2eeSection;
|
||||
if (!this.state.isPublic) {
|
||||
let microcopy;
|
||||
if (privateShouldBeEncrypted()) {
|
||||
microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet.");
|
||||
} else {
|
||||
microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
|
||||
"in private rooms & Direct Messages.");
|
||||
}
|
||||
e2eeSection = <React.Fragment>
|
||||
<LabelledToggleSwitch
|
||||
label={ _t("Enable end-to-end encryption")}
|
||||
|
@ -200,7 +208,7 @@ export default createReactClass({
|
|||
value={this.state.isEncrypted}
|
||||
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
|
||||
/>
|
||||
<p>{ _t("You can’t disable this later. Bridges & most bots won’t work yet.") }</p>
|
||||
<p>{ microcopy }</p>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import dis from "../../../dispatcher/dispatcher";
|
|||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import Modal from "../../../Modal";
|
||||
import {humanizeTime} from "../../../utils/humanize";
|
||||
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||
import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../../createRoom";
|
||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
|
@ -575,14 +575,16 @@ export default class InviteDialog extends React.PureComponent {
|
|||
|
||||
const createRoomOptions = {inlineErrors: true};
|
||||
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
|
||||
if (!has3PidMembers) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
if (privateShouldBeEncrypted()) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
|
||||
if (!has3PidMembers) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher/dispatcher';
|
|||
import Modal from '../../../Modal';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import createRoom from '../../../createRoom';
|
||||
import createRoom, {privateShouldBeEncrypted} from '../../../createRoom';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
|
@ -108,15 +108,17 @@ async function openDMForUser(matrixClient, userId) {
|
|||
dmUserId: userId,
|
||||
};
|
||||
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const usersToDevicesMap = await matrixClient.downloadKeys([userId]);
|
||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
||||
return Object.keys(devices).length > 0;
|
||||
});
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
if (privateShouldBeEncrypted()) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const usersToDevicesMap = await matrixClient.downloadKeys([userId]);
|
||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
||||
return Object.keys(devices).length > 0;
|
||||
});
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
}
|
||||
|
||||
createRoom(createRoomOptions);
|
||||
|
|
|
@ -26,6 +26,7 @@ import Modal from "../../../../../Modal";
|
|||
import * as sdk from "../../../../..";
|
||||
import {sleep} from "../../../../../utils/promise";
|
||||
import dis from "../../../../../dispatcher/dispatcher";
|
||||
import {privateShouldBeEncrypted} from "../../../../../createRoom";
|
||||
|
||||
export class IgnoredUser extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -317,8 +318,17 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel');
|
||||
|
||||
let warning;
|
||||
if (!privateShouldBeEncrypted()) {
|
||||
warning = <div className="mx_SecurityUserSettingsTab_warning">
|
||||
{ _t("Your server admin has disabled end-to-end encryption by default " +
|
||||
"in private rooms & Direct Messages.") }
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
{warning}
|
||||
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Where you’re logged in")}</span>
|
||||
|
|
|
@ -24,6 +24,8 @@ import * as Rooms from "./Rooms";
|
|||
import DMRoomMap from "./utils/DMRoomMap";
|
||||
import {getAddressType} from "./UserAddress";
|
||||
|
||||
const E2EE_WK_KEY = "im.vector.riot.e2ee";
|
||||
|
||||
/**
|
||||
* Create a new room, and switch to it.
|
||||
*
|
||||
|
@ -225,9 +227,22 @@ export async function ensureDMExists(client, userId) {
|
|||
if (existingDMRoom) {
|
||||
roomId = existingDMRoom.roomId;
|
||||
} else {
|
||||
const encryption = canEncryptToAllUsers(client, [userId]);
|
||||
let encryption;
|
||||
if (privateShouldBeEncrypted()) {
|
||||
encryption = canEncryptToAllUsers(client, [userId]);
|
||||
}
|
||||
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
|
||||
await _waitForMember(client, roomId, userId);
|
||||
}
|
||||
return roomId;
|
||||
}
|
||||
|
||||
export function privateShouldBeEncrypted() {
|
||||
const clientWellKnown = MatrixClientPeg.get().getClientWellKnown();
|
||||
if (clientWellKnown && clientWellKnown[E2EE_WK_KEY]) {
|
||||
const defaultDisabled = clientWellKnown[E2EE_WK_KEY]["default"] === false;
|
||||
return !defaultDisabled;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -871,6 +871,7 @@
|
|||
"Key backup": "Key backup",
|
||||
"Message search": "Message search",
|
||||
"Cross-signing": "Cross-signing",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
"Security & Privacy": "Security & Privacy",
|
||||
"Where you’re logged in": "Where you’re logged in",
|
||||
"Manage the names of and sign out of your sessions below or <a>verify them in your User Profile</a>.": "Manage the names of and sign out of your sessions below or <a>verify them in your User Profile</a>.",
|
||||
|
@ -1559,8 +1560,8 @@
|
|||
"Please enter a name for the room": "Please enter a name for the room",
|
||||
"Set a room address to easily share your room with other people.": "Set a room address to easily share your room with other people.",
|
||||
"This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.",
|
||||
"Enable end-to-end encryption": "Enable end-to-end encryption",
|
||||
"You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.",
|
||||
"Enable end-to-end encryption": "Enable end-to-end encryption",
|
||||
"Create a public room": "Create a public room",
|
||||
"Create a private room": "Create a private room",
|
||||
"Name": "Name",
|
||||
|
|
|
@ -21,10 +21,8 @@ import {IntegrationManagerInstance, KIND_ACCOUNT, KIND_CONFIG, KIND_HOMESERVER}
|
|||
import type {MatrixClient, MatrixEvent, Room} from "matrix-js-sdk";
|
||||
import WidgetUtils from "../utils/WidgetUtils";
|
||||
import {MatrixClientPeg} from "../MatrixClientPeg";
|
||||
import {AutoDiscovery} from "matrix-js-sdk";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
|
||||
const HS_MANAGERS_REFRESH_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours
|
||||
const KIND_PREFERENCE = [
|
||||
// Ordered: first is most preferred, last is least preferred.
|
||||
KIND_ACCOUNT,
|
||||
|
@ -44,7 +42,6 @@ export class IntegrationManagers {
|
|||
|
||||
_managers: IntegrationManagerInstance[] = [];
|
||||
_client: MatrixClient;
|
||||
_wellknownRefreshTimerId: number = null;
|
||||
_primaryManager: IntegrationManagerInstance;
|
||||
|
||||
constructor() {
|
||||
|
@ -55,20 +52,19 @@ export class IntegrationManagers {
|
|||
this.stopWatching();
|
||||
this._client = MatrixClientPeg.get();
|
||||
this._client.on("accountData", this._onAccountData);
|
||||
this._client.on("WellKnown.client", this._setupHomeserverManagers);
|
||||
this._compileManagers();
|
||||
setInterval(() => this._setupHomeserverManagers(), HS_MANAGERS_REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
stopWatching(): void {
|
||||
if (!this._client) return;
|
||||
this._client.removeListener("accountData", this._onAccountData);
|
||||
if (this._wellknownRefreshTimerId !== null) clearInterval(this._wellknownRefreshTimerId);
|
||||
this._client.removeListener("WellKnown.client", this._setupHomeserverManagers);
|
||||
}
|
||||
|
||||
_compileManagers() {
|
||||
this._managers = [];
|
||||
this._setupConfiguredManager();
|
||||
this._setupHomeserverManagers();
|
||||
this._setupAccountManagers();
|
||||
}
|
||||
|
||||
|
@ -82,39 +78,31 @@ export class IntegrationManagers {
|
|||
}
|
||||
}
|
||||
|
||||
async _setupHomeserverManagers() {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
try {
|
||||
console.log("Updating homeserver-configured integration managers...");
|
||||
const homeserverDomain = MatrixClientPeg.getHomeserverName();
|
||||
const discoveryResponse = await AutoDiscovery.getRawClientConfig(homeserverDomain);
|
||||
if (discoveryResponse && discoveryResponse['m.integrations']) {
|
||||
let managers = discoveryResponse['m.integrations']['managers'];
|
||||
if (!Array.isArray(managers)) managers = []; // make it an array so we can wipe the HS managers
|
||||
async _setupHomeserverManagers(discoveryResponse) {
|
||||
console.log("Updating homeserver-configured integration managers...");
|
||||
if (discoveryResponse && discoveryResponse['m.integrations']) {
|
||||
let managers = discoveryResponse['m.integrations']['managers'];
|
||||
if (!Array.isArray(managers)) managers = []; // make it an array so we can wipe the HS managers
|
||||
|
||||
console.log(`Homeserver has ${managers.length} integration managers`);
|
||||
console.log(`Homeserver has ${managers.length} integration managers`);
|
||||
|
||||
// Clear out any known managers for the homeserver
|
||||
// TODO: Log out of the scalar clients
|
||||
this._managers = this._managers.filter(m => m.kind !== KIND_HOMESERVER);
|
||||
// Clear out any known managers for the homeserver
|
||||
// TODO: Log out of the scalar clients
|
||||
this._managers = this._managers.filter(m => m.kind !== KIND_HOMESERVER);
|
||||
|
||||
// Now add all the managers the homeserver wants us to have
|
||||
for (const hsManager of managers) {
|
||||
if (!hsManager["api_url"]) continue;
|
||||
this._managers.push(new IntegrationManagerInstance(
|
||||
KIND_HOMESERVER,
|
||||
hsManager["api_url"],
|
||||
hsManager["ui_url"], // optional
|
||||
));
|
||||
}
|
||||
|
||||
this._primaryManager = null; // reset primary
|
||||
} else {
|
||||
console.log("Homeserver has no integration managers");
|
||||
// Now add all the managers the homeserver wants us to have
|
||||
for (const hsManager of managers) {
|
||||
if (!hsManager["api_url"]) continue;
|
||||
this._managers.push(new IntegrationManagerInstance(
|
||||
KIND_HOMESERVER,
|
||||
hsManager["api_url"],
|
||||
hsManager["ui_url"], // optional
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Errors during discovery are non-fatal
|
||||
|
||||
this._primaryManager = null; // reset primary
|
||||
} else {
|
||||
console.log("Homeserver has no integration managers");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,8 @@ limitations under the License.
|
|||
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import {MatrixClientPeg} from "../MatrixClientPeg";
|
||||
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
|
||||
|
||||
const JITSI_WK_PROPERTY = "im.vector.riot.jitsi";
|
||||
const JITSI_WK_CHECK_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours, arbitrarily selected
|
||||
|
||||
export interface JitsiWidgetData {
|
||||
conferenceId: string;
|
||||
|
@ -36,39 +34,27 @@ export class Jitsi {
|
|||
return this.domain || 'jitsi.riot.im';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// We rely on the first call to be an .update() instead of doing one here. Doing one
|
||||
// here could result in duplicate calls to the homeserver.
|
||||
|
||||
// Start a timer to update the server info regularly
|
||||
setInterval(() => this.update(), JITSI_WK_CHECK_INTERVAL);
|
||||
public start() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("WellKnown.client", this.update);
|
||||
// call update initially in case we missed the first WellKnown.client event and for if no well-known present
|
||||
this.update(cli.getClientWellKnown());
|
||||
}
|
||||
|
||||
public async update(): Promise<any> {
|
||||
private update = async (discoveryResponse): Promise<any> => {
|
||||
// Start with a default of the config's domain
|
||||
let domain = (SdkConfig.get()['jitsi'] || {})['preferredDomain'] || 'jitsi.riot.im';
|
||||
|
||||
// Now request the .well-known config to see if it changed
|
||||
if (MatrixClientPeg.get()) {
|
||||
try {
|
||||
console.log("Attempting to get Jitsi conference information from homeserver");
|
||||
|
||||
const homeserverDomain = MatrixClientPeg.getHomeserverName();
|
||||
const discoveryResponse = await AutoDiscovery.getRawClientConfig(homeserverDomain);
|
||||
if (discoveryResponse && discoveryResponse[JITSI_WK_PROPERTY]) {
|
||||
const wkPreferredDomain = discoveryResponse[JITSI_WK_PROPERTY]['preferredDomain'];
|
||||
if (wkPreferredDomain) domain = wkPreferredDomain;
|
||||
}
|
||||
} catch (e) {
|
||||
// These are non-fatal errors
|
||||
console.error(e);
|
||||
}
|
||||
console.log("Attempting to get Jitsi conference information from homeserver");
|
||||
if (discoveryResponse && discoveryResponse[JITSI_WK_PROPERTY]) {
|
||||
const wkPreferredDomain = discoveryResponse[JITSI_WK_PROPERTY]['preferredDomain'];
|
||||
if (wkPreferredDomain) domain = wkPreferredDomain;
|
||||
}
|
||||
|
||||
// Put the result into memory for us to use later
|
||||
this.domain = domain;
|
||||
console.log("Jitsi conference domain:", this.preferredDomain);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the given URL into the data needed for a Jitsi widget, if the widget
|
||||
|
|
Loading…
Reference in a new issue