Merge pull request #12780 from vector-im/travis/wrapped-jitsi
Use a local widget wrapper for Jitsi calls
This commit is contained in:
commit
775e1fc4ae
|
@ -22,7 +22,6 @@
|
||||||
"https://scalar-staging.vector.im/api",
|
"https://scalar-staging.vector.im/api",
|
||||||
"https://scalar-staging.riot.im/scalar/api"
|
"https://scalar-staging.riot.im/scalar/api"
|
||||||
],
|
],
|
||||||
"integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
|
|
||||||
"bug_report_endpoint_url": "https://riot.im/bugreports/submit",
|
"bug_report_endpoint_url": "https://riot.im/bugreports/submit",
|
||||||
"defaultCountryCode": "GB",
|
"defaultCountryCode": "GB",
|
||||||
"showLabsSettings": false,
|
"showLabsSettings": false,
|
||||||
|
@ -52,5 +51,9 @@
|
||||||
},
|
},
|
||||||
"settingDefaults": {
|
"settingDefaults": {
|
||||||
"breadcrumbs": true
|
"breadcrumbs": true
|
||||||
|
},
|
||||||
|
"jitsi": {
|
||||||
|
"preferredDomain": "jitsi.riot.im",
|
||||||
|
"externalApiUrl": "https://jitsi.riot.im/libs/external_api.min.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,13 @@ For a good example, see https://riot.im/develop/config.json.
|
||||||
By default, this is "https://matrix.to" to generate matrix.to (spec) permalinks.
|
By default, this is "https://matrix.to" to generate matrix.to (spec) permalinks.
|
||||||
Set this to your Riot instance URL if you run an unfederated server (eg:
|
Set this to your Riot instance URL if you run an unfederated server (eg:
|
||||||
"https://riot.example.org").
|
"https://riot.example.org").
|
||||||
|
1. `jitsi`: Used to change the default conference options.
|
||||||
|
1. `preferredDomain`: The domain name of the preferred Jitsi instance. Defaults
|
||||||
|
to `jitsi.riot.im`. This is used whenever a user clicks on the voice/video
|
||||||
|
call buttons - integration managers may use a different domain.
|
||||||
|
1. `externalApiUrl`: The URL to the Jitsi Meet API script. This is required
|
||||||
|
for showing any Jitsi widgets, no matter the source. Defaults to
|
||||||
|
`https://jitsi.riot.im/libs/external_api.min.js`.
|
||||||
|
|
||||||
Note that `index.html` also has an og:image meta tag that is set to an image
|
Note that `index.html` also has an og:image meta tag that is set to an image
|
||||||
hosted on riot.im. This is the image used if links to your copy of Riot
|
hosted on riot.im. This is the image used if links to your copy of Riot
|
||||||
|
|
|
@ -39,9 +39,6 @@ import url from 'url';
|
||||||
|
|
||||||
import {parseQs, parseQsFromFragment} from './url_utils';
|
import {parseQs, parseQsFromFragment} from './url_utils';
|
||||||
|
|
||||||
import ElectronPlatform from './platform/ElectronPlatform';
|
|
||||||
import WebPlatform from './platform/WebPlatform';
|
|
||||||
|
|
||||||
import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg';
|
import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg';
|
||||||
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
|
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
|
||||||
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
|
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
|
||||||
|
@ -50,6 +47,7 @@ import {setTheme} from "matrix-react-sdk/src/theme";
|
||||||
import Olm from 'olm';
|
import Olm from 'olm';
|
||||||
|
|
||||||
import CallHandler from 'matrix-react-sdk/src/CallHandler';
|
import CallHandler from 'matrix-react-sdk/src/CallHandler';
|
||||||
|
import {loadConfig, preparePlatform} from "./initial-load";
|
||||||
|
|
||||||
let lastLocationHashSet = null;
|
let lastLocationHashSet = null;
|
||||||
|
|
||||||
|
@ -191,35 +189,11 @@ export async function loadApp() {
|
||||||
await loadOlm();
|
await loadOlm();
|
||||||
|
|
||||||
// set the platform for react sdk
|
// set the platform for react sdk
|
||||||
if (window.ipcRenderer) {
|
preparePlatform();
|
||||||
console.log("Using Electron platform");
|
|
||||||
const plaf = new ElectronPlatform();
|
|
||||||
PlatformPeg.set(plaf);
|
|
||||||
} else {
|
|
||||||
console.log("Using Web platform");
|
|
||||||
PlatformPeg.set(new WebPlatform());
|
|
||||||
}
|
|
||||||
|
|
||||||
const platform = PlatformPeg.get();
|
const platform = PlatformPeg.get();
|
||||||
|
|
||||||
let configJson;
|
// Load the config from the platform
|
||||||
let configError;
|
const configInfo = await loadConfig();
|
||||||
let configSyntaxError = false;
|
|
||||||
try {
|
|
||||||
configJson = await platform.getConfig();
|
|
||||||
} catch (e) {
|
|
||||||
configError = e;
|
|
||||||
|
|
||||||
if (e && e.err && e.err instanceof SyntaxError) {
|
|
||||||
console.error("SyntaxError loading config:", e);
|
|
||||||
configSyntaxError = true;
|
|
||||||
configJson = {}; // to prevent errors between here and loading CSS for the error box
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: We call this twice, once here and once in MatrixChat as a prop. We call it here to ensure
|
|
||||||
// granular settings are loaded correctly and to avoid duplicating the override logic for the theme.
|
|
||||||
SdkConfig.put(configJson);
|
|
||||||
|
|
||||||
// Load language after loading config.json so that settingsDefaults.language can be applied
|
// Load language after loading config.json so that settingsDefaults.language can be applied
|
||||||
await loadLanguage();
|
await loadLanguage();
|
||||||
|
@ -248,7 +222,7 @@ export async function loadApp() {
|
||||||
await setTheme();
|
await setTheme();
|
||||||
|
|
||||||
// Now that we've loaded the theme (CSS), display the config syntax error if needed.
|
// Now that we've loaded the theme (CSS), display the config syntax error if needed.
|
||||||
if (configSyntaxError) {
|
if (configInfo.configSyntaxError) {
|
||||||
const errorMessage = (
|
const errorMessage = (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -260,7 +234,7 @@ export async function loadApp() {
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_t(
|
||||||
"The message from the parser is: %(message)s",
|
"The message from the parser is: %(message)s",
|
||||||
{message: configError.err.message || _t("Invalid JSON")},
|
{message: configInfo.configError.err.message || _t("Invalid JSON")},
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -280,7 +254,7 @@ export async function loadApp() {
|
||||||
|
|
||||||
const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
|
const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
|
||||||
console.log("Vector starting at " + urlWithoutQuery);
|
console.log("Vector starting at " + urlWithoutQuery);
|
||||||
if (configError) {
|
if (configInfo.configError) {
|
||||||
window.matrixChat = ReactDOM.render(<div className="error">
|
window.matrixChat = ReactDOM.render(<div className="error">
|
||||||
Unable to load config file: please refresh the page to try again.
|
Unable to load config file: please refresh the page to try again.
|
||||||
</div>, document.getElementById('matrixchat'));
|
</div>, document.getElementById('matrixchat'));
|
||||||
|
@ -298,7 +272,7 @@ export async function loadApp() {
|
||||||
config={newConfig}
|
config={newConfig}
|
||||||
realQueryParams={params}
|
realQueryParams={params}
|
||||||
startingFragmentQueryParams={fragparts.params}
|
startingFragmentQueryParams={fragparts.params}
|
||||||
enableGuest={!configJson.disable_guests}
|
enableGuest={!SdkConfig.get().disable_guests}
|
||||||
onTokenLoginCompleted={onTokenLoginCompleted}
|
onTokenLoginCompleted={onTokenLoginCompleted}
|
||||||
initialScreenAfterLogin={getScreenFromLocation(window.location)}
|
initialScreenAfterLogin={getScreenFromLocation(window.location)}
|
||||||
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
|
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ElectronPlatform from './platform/ElectronPlatform';
|
||||||
|
import WebPlatform from './platform/WebPlatform';
|
||||||
|
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
|
||||||
|
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
|
||||||
|
|
||||||
|
export function preparePlatform() {
|
||||||
|
if ((<any>window).ipcRenderer) {
|
||||||
|
console.log("Using Electron platform");
|
||||||
|
const plaf = new ElectronPlatform();
|
||||||
|
PlatformPeg.set(plaf);
|
||||||
|
} else {
|
||||||
|
console.log("Using Web platform");
|
||||||
|
PlatformPeg.set(new WebPlatform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadConfig(): Promise<{configError?: Error, configSyntaxError: boolean}> {
|
||||||
|
const platform = PlatformPeg.get();
|
||||||
|
|
||||||
|
let configJson;
|
||||||
|
let configError;
|
||||||
|
let configSyntaxError = false;
|
||||||
|
try {
|
||||||
|
configJson = await platform.getConfig();
|
||||||
|
} catch (e) {
|
||||||
|
configError = e;
|
||||||
|
|
||||||
|
if (e && e.err && e.err instanceof SyntaxError) {
|
||||||
|
console.error("SyntaxError loading config:", e);
|
||||||
|
configSyntaxError = true;
|
||||||
|
configJson = {}; // to prevent errors between here and loading CSS for the error box
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: We call this twice, once here and once in MatrixChat as a prop. We call it here to ensure
|
||||||
|
// granular settings are loaded correctly and to avoid duplicating the override logic for the theme.
|
||||||
|
//
|
||||||
|
// Note: this isn't called twice for some wrappers, like the Jitsi wrapper.
|
||||||
|
SdkConfig.put(configJson);
|
||||||
|
|
||||||
|
return {configError, configSyntaxError};
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Jitsi Widget</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="jitsiContainer"><!-- the js will put the conference here --></div>
|
||||||
|
<div id="joinButtonContainer">
|
||||||
|
<div class="joinConferenceFloating">
|
||||||
|
<div class="joinConferencePrompt">
|
||||||
|
<!-- TODO: i18n -->
|
||||||
|
<h2>Jitsi Video Conference</h2>
|
||||||
|
<button type="button" id="joinButton">Join Conference</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 New Vector Ltd.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Match the user's theme: https://github.com/vector-im/riot-web/issues/12794
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Nunito';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('~matrix-react-sdk/res/fonts/Nunito/Nunito-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Nunito, Arial, Helvetica, sans-serif;
|
||||||
|
background-color: #181b21;
|
||||||
|
color: #edf3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jitsiContainer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#joinButtonContainer {
|
||||||
|
display: table;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joinConferenceFloating {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joinConferencePrompt {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#joinButton {
|
||||||
|
// A mix of AccessibleButton styles
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 7px 18px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #03b381;
|
||||||
|
border: 0;
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 New Vector Ltd.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// We have to trick webpack into loading our CSS for us.
|
||||||
|
require("./index.scss");
|
||||||
|
|
||||||
|
import * as qs from 'querystring';
|
||||||
|
import { Capability, WidgetApi } from "matrix-react-sdk/src/widgets/WidgetApi";
|
||||||
|
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
|
||||||
|
import { loadConfig, preparePlatform } from "../initial-load";
|
||||||
|
|
||||||
|
// Dev note: we use raw JS without many dependencies to reduce bundle size.
|
||||||
|
// We do not need all of React to render a Jitsi conference.
|
||||||
|
|
||||||
|
declare var JitsiMeetExternalAPI: any;
|
||||||
|
|
||||||
|
let inConference = false;
|
||||||
|
|
||||||
|
// Jitsi params
|
||||||
|
let jitsiDomain: string;
|
||||||
|
let conferenceId: string;
|
||||||
|
let displayName: string;
|
||||||
|
let avatarUrl: string;
|
||||||
|
let userId: string;
|
||||||
|
|
||||||
|
let widgetApi: WidgetApi;
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
try {
|
||||||
|
// The widget's options are encoded into the fragment to avoid leaking info to the server. The widget
|
||||||
|
// spec on the other hand requires the widgetId and parentUrl to show up in the regular query string.
|
||||||
|
const widgetQuery = qs.parse(window.location.hash.substring(1));
|
||||||
|
const query = Object.assign({}, qs.parse(window.location.search.substring(1)), widgetQuery);
|
||||||
|
const qsParam = (name: string, optional = false): string => {
|
||||||
|
if (!optional && (!query[name] || typeof (query[name]) !== 'string')) {
|
||||||
|
throw new Error(`Expected singular ${name} in query string`);
|
||||||
|
}
|
||||||
|
return <string>query[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set this up as early as possible because Riot will be hitting it almost immediately.
|
||||||
|
widgetApi = new WidgetApi(qsParam('parentUrl'), qsParam('widgetId'), [
|
||||||
|
Capability.AlwaysOnScreen,
|
||||||
|
]);
|
||||||
|
|
||||||
|
widgetApi.waitReady().then(async () => {
|
||||||
|
// Start off by ensuring we're not stuck on screen
|
||||||
|
await widgetApi.setAlwaysOnScreen(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bootstrap ourselves for loading the script and such
|
||||||
|
preparePlatform();
|
||||||
|
await loadConfig();
|
||||||
|
|
||||||
|
// Populate the Jitsi params now
|
||||||
|
jitsiDomain = qsParam('conferenceDomain', true) || SdkConfig.get()['jitsi']['preferredDomain'];
|
||||||
|
conferenceId = qsParam('conferenceId');
|
||||||
|
displayName = qsParam('displayName', true);
|
||||||
|
avatarUrl = qsParam('avatarUrl', true); // http not mxc
|
||||||
|
userId = qsParam('userId');
|
||||||
|
|
||||||
|
// Get the Jitsi Meet API loaded up as fast as possible, but ensure that the widget's postMessage
|
||||||
|
// receiver (WidgetApi) is up and running first.
|
||||||
|
const scriptTag = document.createElement("script");
|
||||||
|
scriptTag.src = SdkConfig.get()['jitsi']['externalApiUrl'];
|
||||||
|
document.body.appendChild(scriptTag);
|
||||||
|
|
||||||
|
// TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/riot-web/issues/12795)
|
||||||
|
|
||||||
|
document.getElementById("joinButton").onclick = () => joinConference();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error setting up Jitsi widget", e);
|
||||||
|
document.getElementById("jitsiContainer").innerText = "Failed to load Jitsi widget";
|
||||||
|
switchVisibleContainers();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function switchVisibleContainers() {
|
||||||
|
inConference = !inConference;
|
||||||
|
document.getElementById("jitsiContainer").style.visibility = inConference ? 'unset' : 'hidden';
|
||||||
|
document.getElementById("joinButtonContainer").style.visibility = inConference ? 'hidden' : 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinConference() { // event handler bound in HTML
|
||||||
|
switchVisibleContainers();
|
||||||
|
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
widgetApi.setAlwaysOnScreen(true); // ignored promise because we don't care if it works
|
||||||
|
|
||||||
|
const meetApi = new JitsiMeetExternalAPI(jitsiDomain, {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
parentNode: document.querySelector("#jitsiContainer"),
|
||||||
|
roomName: conferenceId,
|
||||||
|
interfaceConfigOverwrite: {
|
||||||
|
SHOW_JITSI_WATERMARK: false,
|
||||||
|
SHOW_WATERMARK_FOR_GUESTS: false,
|
||||||
|
MAIN_TOOLBAR_BUTTONS: [],
|
||||||
|
VIDEO_LAYOUT_FIT: "height",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (displayName) meetApi.executeCommand("displayName", displayName);
|
||||||
|
if (avatarUrl) meetApi.executeCommand("avatarUrl", avatarUrl);
|
||||||
|
if (userId) meetApi.executeCommand("email", userId);
|
||||||
|
|
||||||
|
meetApi.on("readyToClose", () => {
|
||||||
|
switchVisibleContainers();
|
||||||
|
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
widgetApi.setAlwaysOnScreen(false); // ignored promise because we don't care if it works
|
||||||
|
|
||||||
|
document.getElementById("jitsiContainer").innerHTML = "";
|
||||||
|
});
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ module.exports = (env, argv) => {
|
||||||
"bundle": "./src/vector/index.js",
|
"bundle": "./src/vector/index.js",
|
||||||
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
|
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
|
||||||
"mobileguide": "./src/vector/mobile_guide/index.js",
|
"mobileguide": "./src/vector/mobile_guide/index.js",
|
||||||
|
"jitsi": "./src/vector/jitsi/index.ts",
|
||||||
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
|
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
|
||||||
|
|
||||||
// CSS themes
|
// CSS themes
|
||||||
|
@ -303,13 +304,21 @@ module.exports = (env, argv) => {
|
||||||
// HtmlWebpackPlugin will screw up our formatting like the names
|
// HtmlWebpackPlugin will screw up our formatting like the names
|
||||||
// of the themes and which chunks we actually care about.
|
// of the themes and which chunks we actually care about.
|
||||||
inject: false,
|
inject: false,
|
||||||
excludeChunks: ['mobileguide', 'usercontent'],
|
excludeChunks: ['mobileguide', 'usercontent', 'jitsi'],
|
||||||
minify: argv.mode === 'production',
|
minify: argv.mode === 'production',
|
||||||
vars: {
|
vars: {
|
||||||
og_image_url: og_image_url,
|
og_image_url: og_image_url,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// This is the jitsi widget wrapper (embedded, so isolated stack)
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './src/vector/jitsi/index.html',
|
||||||
|
filename: 'jitsi.html',
|
||||||
|
minify: argv.mode === 'production',
|
||||||
|
chunks: ['jitsi'],
|
||||||
|
}),
|
||||||
|
|
||||||
// This is the mobile guide's entry point (separate for faster mobile loading)
|
// This is the mobile guide's entry point (separate for faster mobile loading)
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './src/vector/mobile_guide/index.html',
|
template: './src/vector/mobile_guide/index.html',
|
||||||
|
|
Loading…
Reference in New Issue