WIP concept for a "thin widget" wrapper

This commit is contained in:
Travis Ralston 2021-01-11 23:42:49 -07:00
parent 66080b5751
commit 3456b4c58a
7 changed files with 213 additions and 11 deletions

View file

@ -28,22 +28,13 @@ require('katex/dist/katex.css');
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import {parseQsFromFragment} from "./url_utils";
import './modernizr';
import {settled} from "./promise_utils";
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
async function settled(...promises: Array<Promise<any>>) {
for (const prom of promises) {
try {
await prom;
} catch (e) {
console.error(e);
}
}
}
function checkBrowserFeatures() {
if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing.");

View file

@ -0,0 +1,25 @@
/*
Copyright 2021 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.
*/
export async function settled(...promises: Array<Promise<any>>) {
for (const prom of promises) {
try {
await prom;
} catch (e) {
console.error(e);
}
}
}

View file

@ -0,0 +1,44 @@
/*
Copyright 2021 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 React from 'react';
// add React and ReactPerf to the global namespace, to make them easier to access via the console
// this incidentally means we can forget our React imports in JSX files without penalty.
window.React = React;
import AppTile from "matrix-react-sdk/src/components/views/elements/AppTile";
export interface IStartOpts {
accessToken: string;
widgetId: string;
roomId?: string;
}
export async function loadApp(opts: IStartOpts) {
// TODO: Actually use `opts` to populate the widget
return <AppTile
app={{
id: "test1234",
url: "http://localhost:8081/index.html#/?widgetId=$matrix_widget_id",
name: "Test Widget",
type: "m.custom",
data: {},
}}
fullWidth={true}
userId={"@test:example.org"}
userWidget={true}
/>;
}

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Thin Widget</title>
</head>
<body>
<noscript>Sorry, this requires JavaScript to be enabled.</noscript> <!-- TODO: Translate this? -->
<section id="matrixchat" style="height: 100%; overflow: auto;" class="notranslate"></section>
</body>
</html>

View file

@ -0,0 +1,44 @@
/*
Copyright 2021 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/element-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');
}
$dark-fg: #edf3ff;
$dark-bg: #363c43;
$light-fg: #2e2f32;
$light-bg: #fff;
body {
font-family: Nunito, Arial, Helvetica, sans-serif;
background-color: $dark-bg;
color: $dark-fg;
}
body.theme-light {
background-color: $light-bg;
color: $light-fg;
}
body, html {
padding: 0;
margin: 0;
}

View file

@ -0,0 +1,78 @@
/*
Copyright 2021 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 { settled } from "../promise_utils";
import ReactDOM from 'react-dom';
// 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 qsParam = (name: string, optional = false): string => {
if (!optional && (!widgetQuery[name] || typeof (widgetQuery[name]) !== 'string')) {
throw new Error(`Expected singular ${name} in query string`);
}
return widgetQuery[name] as string;
};
const accessToken = qsParam("accessToken");
const roomId = qsParam("roomId", true);
const widgetId = qsParam("widgetId"); // state_key or account data key
(async function() {
const {
rageshakePromise,
preparePlatform,
loadOlm, // to handle timelines
loadLanguage,
loadTheme,
showError,
_t,
} = await import(
/* webpackChunkName: "thin-wrapper-init" */
/* webpackPreload: true */
"../init");
try {
// give rageshake a chance to load/fail, we don't actually assert rageshake loads, we allow it to fail if no IDB
console.log("Waiting for rageshake...");
await settled(rageshakePromise);
console.log("Running startup...");
await loadOlm();
preparePlatform();
await loadTheme();
await loadLanguage();
// Now we can start our custom code
console.log("Loading app...");
const module = await import(
/* webpackChunkName: "thin-wrapper-app" */
/* webpackPreload: true */
"./app");
window.matrixChat = ReactDOM.render(await module.loadApp({accessToken, roomId, widgetId}),
document.getElementById('matrixchat'));
} catch (err) {
console.error(err);
// Like the compatibility page, AWOOOOOGA at the user
// This uses the default brand since the app config is unavailable.
await showError(_t("Your Element is misconfigured"), [
err.translatedMessage || _t("Unexpected error preparing the app. See console for details."),
]);
}
})();

View file

@ -40,6 +40,7 @@ module.exports = (env, argv) => {
"mobileguide": "./src/vector/mobile_guide/index.js",
"jitsi": "./src/vector/jitsi/index.ts",
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
"thinwidget": "./src/vector/thin_widget/index.ts",
// CSS themes
"theme-legacy": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.scss",
@ -312,7 +313,7 @@ module.exports = (env, argv) => {
// HtmlWebpackPlugin will screw up our formatting like the names
// of the themes and which chunks we actually care about.
inject: false,
excludeChunks: ['mobileguide', 'usercontent', 'jitsi'],
excludeChunks: ['mobileguide', 'usercontent', 'jitsi', 'thinwidget'],
minify: argv.mode === 'production',
vars: {
og_image_url: og_image_url,
@ -327,6 +328,14 @@ module.exports = (env, argv) => {
chunks: ['jitsi'],
}),
// This is a small thin wrapper for widgets (popout; isolated stack)
new HtmlWebpackPlugin({
template: './src/vector/thin_widget/index.html',
filename: 'thin_widget.html',
minify: argv.mode === 'production',
chunks: ['thinwidget'],
}),
// This is the mobile guide's entry point (separate for faster mobile loading)
new HtmlWebpackPlugin({
template: './src/vector/mobile_guide/index.html',