Merge branch 'develop' into optimize-image-assets

This commit is contained in:
MacLemon 2020-04-22 18:35:02 +02:00
commit 5d39bd69dc
40 changed files with 1708 additions and 981 deletions

View File

@ -78,7 +78,7 @@ Riot is a modular webapp built with modern ES6 and uses a Node.js build system.
Ensure you have the latest LTS version of Node.js installed.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install
guide](https://yarnpkg.com/docs/install/) if you do not have it already.
guide](https://classic.yarnpkg.com/en/docs/install) if you do not have it already.
1. Install or update `node.js` so that your `node` is at least v10.x.
1. Install `yarn` if not present already.
@ -288,6 +288,7 @@ yarn install
yarn start
```
Wait a few seconds for the initial build to finish; you should see something like:
```
Hash: b0af76309dd56d7275c8
@ -309,6 +310,23 @@ modifying it. See the [configuration docs](docs/config.md) for details.
Open http://127.0.0.1:8080/ in your browser to see your newly built Riot.
**Note**: The build script uses inotify by default on Linux to monitor directories
for changes. If the inotify watch limit is too low your build will silently fail.
To avoid this issue, we recommend a limit of at least 128M.
To set a new inotify watch limit, execute:
```
$ sudo sysctl fs.inotify.max_user_watches=131072
$ sudo sysctl -p
```
If you wish, you can make this new limit permanent, by executing:
```
$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
```
___
When you make changes to `matrix-react-sdk` or `matrix-js-sdk` they should be

View File

@ -32,7 +32,7 @@ For a good example, see https://riot.im/develop/config.json.
homeserver know what email template to use when talking to you.
1. `branding`: Configures various branding and logo details, such as:
1. `welcomeBackgroundUrl`: An image to use as a wallpaper outside the app
during authentication flows
during authentication flows. If an array is passed, an image is chosen randomly for each visit.
1. `authHeaderLogoUrl`: An logo image that is shown in the header during
authentication flows
1. `authFooterLinks`: a list of links to show in the authentication page footer:
@ -84,10 +84,23 @@ For a good example, see https://riot.im/develop/config.json.
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:
"https://riot.example.org").
1. `jitsi`: Used to change the default conference options.
1. `jitsi`: Used to change the default conference options. Learn more about the
Jitsi options at [jitsi.md](./jitsi.md).
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. `enable_presence_by_hs_url`: The property key should be the URL of the homeserver
and its value defines whether to enable/disable the presence status display
from that homeserver. If no options are configurd, presence is shown for all
homeservers.
1. `disable_guests`: Disables guest access tokens and auto-guest registrations.
Defaults to false (guests are allowed).
1. `disable_login_language_selector`: Disables the login language selector. Defaults
to false (language selector is shown).
1. `disable_3pid_login`: Disables 3rd party identity options on login and registration form
Defaults to false (3rd party identity options are shown).
1. `default_federate`: Default option for room federation when creating a room
Defaults to true (room federation enabled).
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

100
docs/jitsi-dev.md Normal file
View File

@ -0,0 +1,100 @@
# Jitsi wrapper developer docs
*If you're looking for information on how to set up Jitsi in your Riot, see
[jitsi.md](./jitsi.md) instead.*
These docs are for developers wondering how the different conference buttons work
within Riot. If you're not a developer, you're probably looking for [jitsi.md](./jitsi.md).
## Brief introduction to widgets
Widgets are embedded web applications in a room, controlled through state events, and
have a `url` property. They are largely specified by [MSC1236](https://github.com/matrix-org/matrix-doc/issues/1236)
and have extensions proposed under [MSC1286](https://github.com/matrix-org/matrix-doc/issues/1286).
The `url` is typically something we shove into an iframe with sandboxing (see `AppTile`
in the react-sdk), though for some widgets special integration can be done. v2 widgets
have a `data` object which helps achieve that special integration, though v1 widgets
are best iframed and left alone.
Widgets have a `postMessage` API they can use to interact with Riot, which also allows
Riot to interact with them. Typically this is most used by the sticker picker (an
account-level widget), though widgets like the Jitsi widget will request permissions to
get 'stuck' into the room list during a conference.
Widgets can be added with the `/addwidget <url>` command.
## Brief introduction to integration managers
Integration managers (like Scalar and Dimension) are accessible via the 4 squares in
the top right of the room and provide a simple UI over top of bridges, bots, and other
stuff to plug into a room. They are a separate service to Riot and are thus iframed
in a dialog as well. They also have a `postMessage` API they can use to interact with
the client to create things like widgets, give permissions to bridges, and generally
set everything up for the integration the user is working with.
Integration managers do not currently have a spec associated with them, though efforts
are underway in [MSC1286](https://github.com/matrix-org/matrix-doc/issues/1286).
## Widgets configured by integration managers
Integration managers will often "wrap" a widget by using a widget `url` which points
to the integration manager instead of to where the user requested the widget be. For
example, a custom widget added in an integration manager for https://matrix.org will
end up creating a widget with a URL like `https://integrations.example.org?widgetUrl=https%3A%2F%2Fmatrix.org`.
The integration manager's wrapper will typically have another iframe to isolate the
widget from the client by yet another layer. The wrapper often provides other functionality
which might not be available on the embedded site, such as a fullscreen button or the
communication layer with the client (all widgets *should* be talking to the client
over `postMessage`, even if they aren't going to be using the widget APIs).
Widgets added with the `/addwidget` command will *not* be wrapped as they are not going
through an integration manager. The widgets themselves *should* also work outside of
Riot. Widgets currently have a "pop out" button which opens them in a new tab and
therefore have no connection back to Riot.
## Jitsi widgets from integration managers
Integration managers will create an entire widget event and send it over `postMessage`
for the client to add to the room. This means that the integration manager gets to
decide the conference domain, conference name, and other aspects of the widget. As
a result, users can end up with a Jitsi widget that does not use the same conference
server they specified in their config.json - this is expected.
Some integration managers allow the user to change the conference name while others
will generate one for the user.
## Jitsi widgets generated by Riot itself
When the user clicks on the call buttons by the composer, the integration manager is
not involved in the slightest. Instead, Riot itself generates a widget event, this time
using the config.json parameters, and publishes that to the room. If there's only two
people in the room, a plain WebRTC call is made instead of using a widget at all - these
are defined in the Matrix specification.
The Jitsi widget created by Riot uses a local `jitsi.html` wrapper (or one hosted by
`https://riot.im/app` for desktop users or those on non-https domains) as the widget
`url`. The wrapper has some basic functionality for talking to Riot to ensure the
required `postMessage` calls are fulfilled.
**Note**: Per [jitsi.md](./jitsi.md) the `preferredDomain` can also come from the server's
client .well-known data.
## The Jitsi wrapper in Riot
Whenever Riot sees a Jitsi widget, it ditches the `url` and instead replaces it with
its local wrapper, much like what it would do when creating a widget. However, instead
of using one from riot.im/app, it will use one local to the client instead.
The wrapper is used to provide a consistent experience to users, as well as being faster
and less risky to load. The local wrapper URL is populated with the conference information
from the original widget (which could be a v1 or v2 widget) so the user joins the right
call.
Critically, when the widget URL is reconstructed it does *not* take into account the
config.json's `preferredDomain` for Jitsi. If it did this, users would end up on different
conference servers and therefore different calls entirely.
**Note**: Per [jitsi.md](./jitsi.md) the `preferredDomain` can also come from the server's
client .well-known data.

58
docs/jitsi.md Normal file
View File

@ -0,0 +1,58 @@
# Jitsi in Riot
Riot uses [Jitsi](https://jitsi.org/) for conference calls, which provides options for
self-hosting your own server and supports most major platforms.
1:1 calls, or calls between you and one other person, do not use Jitsi. Instead, those
calls work directly between clients or via TURN servers configured on the respective
homeservers.
There's a number of ways to start a Jitsi call: the easiest way is to click on the
voice or video buttons near the message composer in a room with more than 2 people. This
will add a Jitsi widget which allows anyone in the room to join.
Integration managers (available through the 4 squares in the top right of the room) may
provide their own approaches for adding Jitsi widgets.
## Configuring Riot to use your self-hosted Jitsi server
Riot will use the Jitsi server that is embedded in the widget, even if it is not the
one you configured. This is because conference calls must be held on a single Jitsi
server and cannot be split over multiple servers.
However, you can configure Riot to *start* a conference with your Jitsi server by adding
to your [config](./config.md) the following:
```json
{
"jitsi": {
"preferredDomain": "your.jitsi.example.org"
}
}
```
The default is `jitsi.riot.im` (a free service offered by Riot), and the demo site for
Jitsi uses `meet.jit.si` (also free).
Once you've applied the config change, refresh Riot and press the call button. This
should start a new conference on your Jitsi server.
**Note**: The widget URL will point to a `jitsi.html` page hosted by Riot. The Jitsi
domain will appear later in the URL as a configuration parameter.
**Hint**: If you want everyone on your homeserver to use the same Jitsi server by
default, and you are using riot-web 1.6 or newer, set the following on your homeserver's
`/.well-known/matrix/client` config:
```json
{
"im.vector.riot.jitsi": {
"preferredDomain": "your.jitsi.example.org"
}
}
```
## Mobile app support
Currently the Riot mobile apps do not support custom Jitsi servers and will instead
use the default `jitsi.riot.im` server. When users on the mobile apps join the call,
they will be joining a different conference which has the same name, but not the same
participants. This is a known bug and which needs to be fixed.

View File

@ -1,6 +1,7 @@
# Labs features
Some notes on the features you can enable by going to `Settings->Labs`. Not exhaustive, chat in
If Labs is enabled in the [Riot config](config.md), you can enable some of these features by going
to `Settings->Labs`. This list is non-exhaustive and subject to change, chat in
[#riot-web:matrix.org](https://matrix.to/#/#riot-web:matrix.org) for more information.
**Be warned! Labs features are not finalised, they may be fragile, they may change, they may be
@ -66,13 +67,24 @@ An implementation of [MSC2241](https://github.com/matrix-org/matrix-doc/pull/224
This also includes a new implementation of the user & member info panel, designed to share more code between showing community members & room members. Built on top of this new panel is also a new UX for verification from the member panel.
## Cross-signing (in development) (`feature_cross_signing`)
## Cross-signing
Cross-signing ([MSC1756](https://github.com/matrix-org/matrix-doc/pull/1756))
improves the device verification experience by allowing you to verify a user
instead of verifying each of their devices.
This feature is still in development and will be landing in several chunks.
The feature is enabled by default and does not follow a traditional labs flag
at the moment. If something goes wrong, add this to your config to disable it:
```json
{
"settingDefaults": {
"feature_cross_signing": false
}
}
```
The setting will be removed in a future release, enabling it non-optionally for
all users.
## Event indexing and E2EE search support using Seshat (`feature_event_indexing`)

View File

@ -23,14 +23,8 @@
"siteId": 1,
"policyUrl": "https://matrix.org/legal/riot-im-cookie-policy"
},
"phasedRollOut": {
"feature_lazyloading": {
"offset": 1539684000000,
"period": 604800000
}
},
"features": {
"feature_lazyloading": "enable"
"feature_cross_signing": "enable"
},
"enable_presence_by_hs_url": {
"https://matrix.org": false,

View File

@ -35,7 +35,7 @@ const tray = require('./tray');
const vectorMenu = require('./vectormenu');
const webContentsHandler = require('./webcontents-handler');
const updater = require('./updater');
const protocolInit = require('./protocol');
const {getProfileFromDeeplink, protocolInit, recordSSOSession} = require('./protocol');
const windowStateKeeper = require('electron-window-state');
const Store = require('electron-store');
@ -68,7 +68,11 @@ if (argv["help"]) {
app.exit();
}
if (argv['profile-dir']) {
// check if we are passed a profile in the SSO callback url
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
if (userDataPathInProtocol) {
app.setPath('userData', userDataPathInProtocol);
} else if (argv['profile-dir']) {
app.setPath('userData', argv['profile-dir']);
} else if (argv['profile']) {
app.setPath('userData', `${app.getPath('userData')}-${argv['profile']}`);
@ -233,6 +237,19 @@ ipcMain.on('ipcCall', async function(ev, payload) {
case 'getConfig':
ret = vectorConfig;
break;
case 'navigateBack':
if (mainWindow.webContents.canGoBack()) {
mainWindow.webContents.goBack();
}
break;
case 'navigateForward':
if (mainWindow.webContents.canGoForward()) {
mainWindow.webContents.goForward();
}
break;
case 'startSSOFlow':
recordSSOSession(args[0]);
break;
default:
mainWindow.webContents.send('ipcReply', {

View File

@ -14,40 +14,91 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const {app} = require('electron');
const {app} = require("electron");
const path = require("path");
const fs = require("fs");
const PROTOCOL = "riot://";
const SEARCH_PARAM = "riot-desktop-ssoid";
const STORE_FILE_NAME = "sso-sessions.json";
// we getPath userData before electron-main changes it, so this is the default value
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
const processUrl = (url) => {
if (!global.mainWindow) return;
console.log("Handling link: ", url);
global.mainWindow.loadURL(url.replace("riot://", "vector://"));
global.mainWindow.loadURL(url.replace(PROTOCOL, "vector://"));
};
module.exports = () => {
// get all args except `hidden` as it'd mean the app would not get focused
// XXX: passing args to protocol handlers only works on Windows,
// so unpackaged deep-linking and --profile passing won't work on Mac/Linux
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
if (app.isPackaged) {
app.setAsDefaultProtocolClient('riot', process.execPath, args);
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
app.setAsDefaultProtocolClient('riot', process.execPath, [app.getAppPath(), ...args]);
}
if (process.platform === 'darwin') {
// Protocol handler for macos
app.on('open-url', function(ev, url) {
ev.preventDefault();
processUrl(url);
});
} else {
// Protocol handler for win32/Linux
app.on('second-instance', (ev, commandLine) => {
const url = commandLine[commandLine.length - 1];
if (!url.startsWith("riot://")) return;
processUrl(url);
});
const readStore = () => {
try {
const s = fs.readFileSync(storePath, { encoding: "utf8" });
const o = JSON.parse(s);
return typeof o === "object" ? o : {};
} catch (e) {
return {};
}
};
const writeStore = (data) => {
fs.writeFileSync(storePath, JSON.stringify(data));
};
module.exports = {
recordSSOSession: (sessionID) => {
const userDataPath = app.getPath('userData');
const store = readStore();
for (const key in store) {
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
if (store[key] === userDataPath) {
delete store[key];
break;
}
}
store[sessionID] = userDataPath;
writeStore(store);
},
getProfileFromDeeplink: (args) => {
// check if we are passed a profile in the SSO callback url
const deeplinkUrl = args.find(arg => arg.startsWith('riot://'));
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {
const parsedUrl = new URL(deeplinkUrl);
if (parsedUrl.protocol === 'riot:') {
const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
const store = readStore();
console.log("Forwarding to profile: ", store[ssoID]);
return store[ssoID];
}
}
},
protocolInit: () => {
// get all args except `hidden` as it'd mean the app would not get focused
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
if (app.isPackaged) {
app.setAsDefaultProtocolClient('riot', process.execPath, args);
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
app.setAsDefaultProtocolClient('riot', process.execPath, [app.getAppPath(), ...args]);
}
if (process.platform === 'darwin') {
// Protocol handler for macos
app.on('open-url', function(ev, url) {
ev.preventDefault();
processUrl(url);
});
} else {
// Protocol handler for win32/Linux
app.on('second-instance', (ev, commandLine) => {
const url = commandLine[commandLine.length - 1];
if (!url.startsWith(PROTOCOL)) return;
processUrl(url);
});
}
},
};

View File

@ -500,9 +500,9 @@ minimist@0.0.8:
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.2.tgz#b00a00230a1108c48c169e69a291aafda3aacd63"
integrity sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==
version "1.2.3"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.3.tgz#3db5c0765545ab8637be71f333a104a965a9ca3f"
integrity sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw==
mkdirp@0.5.1, mkdirp@^0.5.1:
version "0.5.1"

View File

@ -38,7 +38,7 @@
"clean": "rimraf lib webapp electron_app/dist",
"build": "yarn clean && yarn build:genfiles && yarn build:compile && yarn build:types && yarn build:bundle",
"build-stats": "yarn clean && yarn build:genfiles && yarn build:compile && yarn build:types && yarn build:bundle-stats",
"build:jitsi": "curl -s https://jitsi.riot.im/libs/external_api.min.js > ./webapp/jitsi_external_api.min.js",
"build:jitsi": "node scripts/build-jitsi.js",
"build:res": "node scripts/copy-res.js",
"build:genfiles": "yarn reskindex && yarn build:res && yarn build:jitsi",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
@ -96,6 +96,7 @@
"@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"@types/modernizr": "^3.5.3",
"@types/react": "16.9",
"@types/react-dom": "^16.9.4",
"autoprefixer": "^9.7.3",
@ -134,8 +135,10 @@
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"modernizr": "^3.6.0",
"node-fetch": "^2.6.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-extend": "^1.0.5",
"postcss-hexrgba": "^2.0.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-mixins": "^6.2.3",

View File

@ -0,0 +1,6 @@
<svg width="42" height="50" viewBox="0 0 42 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.04021 42.174C1.04021 45.944 4.27657 49 8.26822 49C12.2603 49 15.4962 45.944 15.4962 42.174V35.2587L22.8119 35.2518C23.2234 35.2518 23.6386 35.2385 24.0415 35.2122C33.5209 34.6096 40.9465 27.1046 40.9465 18.1261C40.9465 8.68273 32.8114 1 22.8119 1H8.26822C4.27657 1 1.04021 4.05637 1.04021 7.82645V27.7141C1.01327 27.9548 0.999582 28.1987 1.00001 28.4459C1.00001 28.6887 1.01412 28.9286 1.04021 29.1645V42.174ZM15.4963 21.6066V14.6525H22.812C24.8405 14.6525 26.4901 16.2103 26.4901 18.1261C26.4901 19.9469 24.9881 21.4692 23.0665 21.5916C22.9822 21.5969 22.8975 21.5993 22.8047 21.5993L15.4963 21.6066Z" fill="#A2DDEF"/>
<path d="M15.4963 14.6525V14.0405H14.8844V14.6525H15.4963ZM15.4963 21.6066H14.8844V22.2191L15.4969 22.2185L15.4963 21.6066ZM22.8047 21.5993V20.9874H22.8041L22.8047 21.5993ZM23.0665 21.5916L23.1045 22.2024L23.1053 22.2023L23.0665 21.5916ZM1.04021 29.1645H1.65214V29.1308L1.64843 29.0972L1.04021 29.1645ZM1.00001 28.4459H1.61194L1.61193 28.4449L1.00001 28.4459ZM1.04021 27.7141L1.64834 27.7821L1.65214 27.7482V27.7141H1.04021ZM24.0415 35.2122L24.0027 34.6015L24.0017 34.6016L24.0415 35.2122ZM22.8119 35.2518V34.6399H22.8113L22.8119 35.2518ZM15.4962 35.2587L15.4957 34.6467L14.8843 34.6473V35.2587H15.4962ZM14.8844 14.6525V21.6066H16.1082V14.6525H14.8844ZM15.4969 22.2185L22.8053 22.2112L22.8041 20.9874L15.4957 20.9946L15.4969 22.2185ZM22.8047 22.2112C22.9085 22.2112 23.006 22.2085 23.1045 22.2024L23.0284 20.9809C22.9584 20.9852 22.8865 20.9874 22.8047 20.9874V22.2112ZM23.1053 22.2023C25.3203 22.0612 27.1021 20.2979 27.1021 18.1261H25.8782C25.8782 19.5959 24.6559 20.8772 23.0276 20.9809L23.1053 22.2023ZM27.1021 18.1261C27.1021 15.8399 25.145 14.0405 22.812 14.0405V15.2644C24.536 15.2644 25.8782 16.5808 25.8782 18.1261H27.1021ZM22.812 14.0405H15.4963V15.2644H22.812V14.0405ZM8.26822 48.3881C4.58104 48.3881 1.65214 45.5735 1.65214 42.174H0.428288C0.428288 46.3145 3.97209 49.6119 8.26822 49.6119V48.3881ZM1.65214 42.174V29.1645H0.428288V42.174H1.65214ZM1.64843 29.0972C1.62467 28.8824 1.61193 28.665 1.61193 28.4459H0.388085C0.388085 28.7124 0.403576 28.9748 0.431997 29.2318L1.64843 29.0972ZM1.61193 28.4449C1.61155 28.221 1.62394 28.0001 1.64834 27.7821L0.432085 27.646C0.402599 27.9094 0.387617 28.1765 0.388085 28.447L1.61193 28.4449ZM1.65214 27.7141V7.82645H0.428288V27.7141H1.65214ZM1.65214 7.82645C1.65214 4.42682 4.58109 1.61193 8.26822 1.61193V0.388075C3.97204 0.388075 0.428288 3.68592 0.428288 7.82645H1.65214ZM8.26822 1.61193H22.8119V0.388075H8.26822V1.61193ZM22.8119 1.61193C32.5069 1.61193 40.3346 9.05321 40.3346 18.1261H41.5584C41.5584 8.31226 33.1159 0.388075 22.8119 0.388075V1.61193ZM40.3346 18.1261C40.3346 26.7525 33.1898 34.0175 24.0027 34.6015L24.0804 35.8229C33.8521 35.2017 41.5584 27.4566 41.5584 18.1261H40.3346ZM24.0017 34.6016C23.6124 34.6269 23.2104 34.6399 22.8119 34.6399V35.8637C23.2363 35.8637 23.6649 35.85 24.0813 35.8228L24.0017 34.6016ZM22.8113 34.6399L15.4957 34.6467L15.4968 35.8706L22.8125 35.8637L22.8113 34.6399ZM14.8843 35.2587V42.174H16.1082V35.2587H14.8843ZM14.8843 42.174C14.8843 45.5736 11.9558 48.3881 8.26822 48.3881V49.6119C12.5648 49.6119 16.1082 46.3145 16.1082 42.174H14.8843Z" fill="#368BD6"/>
<path d="M8.26823 42.174V7.82642H22.8119C28.8351 7.82642 33.7181 12.4378 33.7181 18.1261C33.7181 23.5784 29.232 28.0412 23.5561 28.4019C23.3098 28.4176 23.0621 28.4257 22.8119 28.4257L8.22803 28.4395" stroke="#368BD6" stroke-width="1.22385" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4227 9.0113C15.758 7.21673 15.3325 5.4048 14.2243 3.91155C11.9366 0.828522 7.41753 0.0768486 4.15037 2.23574C2.56747 3.28105 1.51106 4.84579 1.17575 6.64116C0.84001 8.43653 1.26557 10.2481 2.37372 11.7413C4.66146 14.8243 9.18092 15.576 12.4481 13.4171C14.0306 12.3714 15.087 10.8071 15.4227 9.0113ZM27.852 46.0868C29.2587 47.9824 31.4998 48.9962 33.7777 48.9962C35.21 48.9962 36.6569 48.5951 37.9195 47.7594C41.1883 45.5965 41.9817 41.3397 39.6905 38.2522L29.4751 24.4846C27.1843 21.3972 22.6769 20.6475 19.408 22.8121C16.1392 24.975 15.3458 29.2318 17.6365 32.3192L27.852 46.0868Z" fill="#368BD6"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -13,6 +13,7 @@
"hosting_signup_link": "https://modular.im/?utm_source=riot-web&utm_medium=web",
"bug_report_endpoint_url": "https://riot.im/bugreports/submit",
"features": {
"feature_cross_signing": "enable"
},
"piwik": {
"url": "https://piwik.riot.im/",

24
scripts/build-jitsi.js Normal file
View File

@ -0,0 +1,24 @@
// This is a JS script so that the directory is created in-process on Windows.
// If the script isn't run in-process, there's a risk of it racing or never running
// due to file associations in Windows.
// Sorry.
const fs = require("fs");
const path = require("path");
const mkdirp = require("mkdirp");
const fetch = require("node-fetch");
console.log("Making webapp directory");
mkdirp.sync("webapp");
// curl -s https://jitsi.riot.im/libs/external_api.min.js > ./webapp/jitsi_external_api.min.js
console.log("Downloading Jitsi script");
const fname = path.join("webapp", "jitsi_external_api.min.js");
fetch("https://jitsi.riot.im/libs/external_api.min.js").then(res => {
const stream = fs.createWriteStream(fname);
return new Promise((resolve, reject) => {
res.body.pipe(stream);
res.body.on('error', err => reject(err));
res.body.on('finish', () => resolve());
});
}).then(() => console.log('Done with Jitsi download'));

View File

@ -14,9 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
interface Window {
Olm: {
init: () => Promise<void>;
};
mxSendRageshake: (text: string, withLogs?: boolean) => void;
import "matrix-react-sdk/src/@types/global"; // load matrix-react-sdk's type extensions first
import {Renderer} from "react-dom";
declare global {
interface Window {
mxSendRageshake: (text: string, withLogs?: boolean) => void;
matrixChat: ReturnType<Renderer>;
// electron-only
ipcRenderer: any;
}
}

View File

@ -0,0 +1,46 @@
/*
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 * as React from "react";
import * as PropTypes from "prop-types";
import { _t } from "matrix-react-sdk/src/languageHandler";
interface IProps {
title: string;
messages?: string[];
}
const ErrorView: React.FC<IProps> = ({title, messages}) => {
return <div className="mx_GenericErrorPage">
<div className="mx_GenericErrorPage_box">
<h1>{title}</h1>
<div>
{messages && messages.map(msg => <p key={msg}>
{ _t(msg) }
</p>)}
</div>
</div>
</div>;
};
ErrorView.propTypes = {
title: PropTypes.string.isRequired,
messages: PropTypes.arrayOf(PropTypes.string.isRequired),
};
export default ErrorView;

View File

@ -29,7 +29,11 @@ export default class VectorAuthPage extends React.PureComponent {
const brandingConfig = SdkConfig.get().branding;
let backgroundUrl = "themes/riot/img/backgrounds/valley.jpg";
if (brandingConfig && brandingConfig.welcomeBackgroundUrl) {
backgroundUrl = brandingConfig.welcomeBackgroundUrl;
if (Array.isArray(brandingConfig.welcomeBackgroundUrl)) {
backgroundUrl = brandingConfig.welcomeBackgroundUrl[Math.floor(Math.random() * brandingConfig.welcomeBackgroundUrl.length)];
} else {
backgroundUrl = brandingConfig.welcomeBackgroundUrl;
}
}
const pageStyle = {

View File

@ -11,7 +11,7 @@
"Chat with Riot Bot": "Чати с Riot Bot",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Riot with an existing Matrix account on a different homeserver.": "Може да използвате настройките за собствен сървър за да влезете в друг Matrix сървър, чрез указване на адреса му. Това ви позволява да използвате Riot със съществуващ Matrix акаунт, принадлежащ към друг сървър.",
"Sign In": "Вписване",
"Create Account": "Създай акаунт",
"Create Account": "Създай профил",
"Need help?": "Нужда от помощ?",
"Explore rooms": "Открий стаи",
"Room Directory": "Директория със стаи",

View File

@ -21,5 +21,7 @@
"Your Riot is misconfigured": "Riot je špatně nakonfigurován",
"Unexpected error preparing the app. See console for details.": "Neočekávaná chyba při přípravě aplikace. Podrobnosti najdete v konzoli.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Neplatná konfigurace: je možné specifikovat pouze jednu volbu z default_server_config, default_server_name, nebo default_hs_url.",
"Invalid configuration: no default server specified.": "Neplatná konfigurace: není zadán výchozí server."
"Invalid configuration: no default server specified.": "Neplatná konfigurace: není zadán výchozí server.",
"Open user settings": "Otevřít uživatelské nastavení",
"Go to your browser to complete Sign In": "Přejděte do prohlížeče a dokončete přihlášení"
}

View File

@ -1,12 +1,15 @@
{
"Missing indexeddb worker script!": "Missing indexeddb worker script!",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.",
"Invalid configuration: no default server specified.": "Invalid configuration: no default server specified.",
"Your Riot is misconfigured": "Your Riot is misconfigured",
"Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.": "Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.",
"The message from the parser is: %(message)s": "The message from the parser is: %(message)s",
"Invalid JSON": "Invalid JSON",
"Your Riot is misconfigured": "Your Riot is misconfigured",
"Unable to load config file: please refresh the page to try again.": "Unable to load config file: please refresh the page to try again.",
"Unexpected error preparing the app. See console for details.": "Unexpected error preparing the app. See console for details.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.",
"Invalid configuration: no default server specified.": "Invalid configuration: no default server specified.",
"Open user settings": "Open user settings",
"Previous/next recently visited room or community": "Previous/next recently visited room or community",
"Riot Desktop on %(platformName)s": "Riot Desktop on %(platformName)s",
"Go to your browser to complete Sign In": "Go to your browser to complete Sign In",
"Unknown device": "Unknown device",

View File

@ -21,5 +21,7 @@
"Invalid configuration: no default server specified.": "Configuración errónea: no se ha especificado servidor.",
"Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.": "Tu configuración de Riot contiene JSON inválido. Por favor corrige el error y recarga la página.",
"The message from the parser is: %(message)s": "El mensaje del parser es: %(message)s",
"Invalid JSON": "JSON inválido"
"Invalid JSON": "JSON inválido",
"Open user settings": "Abrir opciones de usuario",
"Go to your browser to complete Sign In": "Abre tu navegador web para completar el registro"
}

7
src/i18n/strings/et.json Normal file
View File

@ -0,0 +1,7 @@
{
"Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.": "Sinu Rioti seadetes on vigane JSON. Palun, tee see korda ja laadi leht uuesti!",
"The message from the parser is: %(message)s": "Sõnum parserist on: %(message)s",
"Invalid JSON": "Vigane JSON",
"Your Riot is misconfigured": "Sinu Riot on valesti seadistatud",
"Unknown device": "Tundmatu seade"
}

View File

@ -8,5 +8,12 @@
"%(appName)s via %(browserName)s on %(osName)s": "%(appName)s از طریق %(browserName)s بر %(osName)s",
"Custom Server Options": "تنظیمات سفارشی برای سرور",
"Dismiss": "نادیده بگیر",
"You need to be using HTTPS to place a screen-sharing call.": "شما باید از ارتباط امن HTTPS برای به‌راه‌اندازی یک چتِ شامل به اشتراک‌گذاری صفحه‌ی کامیپوتر استفاده کنید."
"You need to be using HTTPS to place a screen-sharing call.": "شما باید از ارتباط امن HTTPS برای به‌راه‌اندازی یک چتِ شامل به اشتراک‌گذاری صفحه‌ی کامیپوتر استفاده کنید.",
"Invalid JSON": "JSON اشتباه",
"Open user settings": "تنظییمات کاربری",
"Go to your browser to complete Sign In": "برای تکمیل ورود به مرورگر خود بروید",
"Sign In": "ورود",
"Create Account": "ایجاد اکانت",
"Need help?": "به کمک نیازمندید؟",
"Explore rooms": "کاوش اتاق"
}

View File

@ -8,5 +8,12 @@
"Welcome to Riot.im": "ברוכים הבאים ל Riot.im",
"Chat with Riot Bot": "שיחה עם Riot בוט",
"%(appName)s via %(browserName)s on %(osName)s": "%(appName)s באמצעות הדפדפן %(browserName)s על גבי %(osName)s",
"Decentralised, encrypted chat &amp; collaboration powered by [matrix]": "צ'ט מוצפן &amp; ושת\"פ נעשה ע\"י ה [matrix]"
"Decentralised, encrypted chat &amp; collaboration powered by [matrix]": "צ'ט מוצפן &amp; ושת\"פ נעשה ע\"י ה [matrix]",
"Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.": "תצורת Riot שלך מכילה JSON לא חוקי. אנא תקן את הבעיה וטען מחדש את הדף.",
"Invalid JSON": "JSON לא חוקי",
"Your Riot is misconfigured": "ה Riot שלך מוגדר באופן שגוי",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "תצורה שגויה: ניתן לציין רק אחד מהבאים, default_server_config, default_server_name, או default_hs_url.",
"Invalid configuration: no default server specified.": "תצורה שגויה: לא צוין שרת ברירת מחדל.",
"Open user settings": "פתיחת הגדרות משתמש",
"Go to your browser to complete Sign In": "עבור לדפדפן להמשך ההתחברות"
}

View File

@ -22,5 +22,6 @@
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Klaidinga konfigūracija: galima nurodyti tik vieną iš default_server_config, default_server_name, arba default_hs_url.",
"Invalid configuration: no default server specified.": "Klaidinga konfigūracija: nenurodytas numatytasis serveris.",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Riot with an existing Matrix account on a different homeserver.": "Jūs galite naudoti pasirinktinius serverio nustatymus, kad prisijungtumėte prie kitų Matrix serverių, nurodydami kito serverio URL. Tai leidžia jums naudotis Riot su esama Matrix paskyra kitame serveryje.",
"Go to your browser to complete Sign In": "Norėdami užbaigti prisijungimą, eikite į naršyklę"
"Go to your browser to complete Sign In": "Norėdami užbaigti prisijungimą, eikite į naršyklę",
"Open user settings": "Atverti vartotojo nustatymus"
}

View File

@ -11,7 +11,7 @@
"Chat with Riot Bot": "Chat med Riot Bot",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Riot with an existing Matrix account on a different homeserver.": "Du kan bruke instillinger for «egendefinert tjener» til å logge inn på andre Matrix-tjenere ved å spesifisere en annen URL. Dette lar deg bruke Riot med en eksisterende Matrix-konto på en annen hjemmetjener.",
"Sign In": "Logg inn",
"Create Account": "Lag konto",
"Create Account": "Opprett konto",
"Need help?": "Trenger du hjelp?",
"Room Directory": "Alle rom",
"Explore rooms": "Se alle rom",
@ -21,5 +21,7 @@
"Your Riot is misconfigured": "Riot er feilkonfigurert",
"Invalid configuration: no default server specified.": "Ugyldig konfigurasjon: ingen standardserver spesifisert.",
"Unexpected error preparing the app. See console for details.": "Uventet feil oppsto mens appen ble gjort klar. Se konsollen for detaljer.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Ugyldig konfigurasjon: Spesifiser kun en av følgende: default_server_config, default_server_name eller default_hs_url."
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Ugyldig konfigurasjon: Spesifiser kun en av følgende: default_server_config, default_server_name eller default_hs_url.",
"Open user settings": "Åpne brukerinnstillinger",
"Go to your browser to complete Sign In": "Gå til nettleseren din for å fullføre innloggingen"
}

View File

@ -19,7 +19,9 @@
"Invalid JSON": "Błędny JSON",
"Your Riot is misconfigured": "Twój Riot jest źle skonfigurowany",
"Unexpected error preparing the app. See console for details.": "Niespodziewany błąd podczas przygotowywania aplikacji. Otwórz konsolę po szczegóły.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Błędna konfiguracja. Można sprecyzować tylko jedno z: default_server_config, default_server_name, lub default_hs_url.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Błędna konfiguracja. Akceptowalne wartości to: default_server_config, default_server_name, default_hs_url.",
"Invalid configuration: no default server specified.": "Błędna konfiguracja: nie wybrano domyślnego serwera.",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Riot with an existing Matrix account on a different homeserver.": "Możesz użyć Niestandardowych Opcji Serwera by zalogować się do innych serwerów Matrix poprzez podanie URL innego serwera głównego. Dzięki temu możesz używać Riot z istniejącym kontem z innego serwera głównego."
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Riot with an existing Matrix account on a different homeserver.": "Możesz użyć Niestandardowych Opcji Serwera by zalogować się do innych serwerów Matrix poprzez podanie URL innego serwera głównego. Dzięki temu możesz używać Riot z istniejącym kontem z innego serwera głównego.",
"Open user settings": "Otwórz ustawienia użytkownika",
"Go to your browser to complete Sign In": "Aby dokończyć proces rejestracji, przejdź do swojej przeglądarki"
}

View File

@ -22,5 +22,6 @@
"Your Riot configuration contains invalid JSON. Please correct the problem and reload the page.": "Ваша конфигурация Riot содержит нерабочий JSON. Пожалуйста исправьте проблему и перезагрузите страницу.",
"The message from the parser is: %(message)s": "Сообщение из парсера: %(message)s",
"Invalid JSON": "Нерабочий JSON",
"Go to your browser to complete Sign In": "Перейдите в браузер для завершения входа"
"Go to your browser to complete Sign In": "Перейдите в браузер для завершения входа",
"Open user settings": "Открыть настройки пользователя"
}

View File

@ -21,5 +21,7 @@
"Your Riot is misconfigured": "Riot är felkonfigurerat",
"Unexpected error preparing the app. See console for details.": "Oväntat fel vid appstart. Se konsollen för mer information.",
"Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Ogilitiga inställningar: enbart möjligt att specificera en default_config, default_server, eller default_hs_url.",
"Invalid configuration: no default server specified.": "Ogilitiga inställningar: ingen standardserver specificerad."
"Invalid configuration: no default server specified.": "Ogilitiga inställningar: ingen standardserver specificerad.",
"Open user settings": "Öppna användarinställningar",
"Go to your browser to complete Sign In": "Gå till din webbläsare för att slutföra inloggningen"
}

View File

@ -23,11 +23,10 @@ import React from 'react';
// access via the console
global.React = React;
import ReactDOM from 'react-dom';
import * as sdk from 'matrix-react-sdk';
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
import * as VectorConferenceHandler from 'matrix-react-sdk/src/VectorConferenceHandler';
import {_t, _td, newTranslatableError} from 'matrix-react-sdk/src/languageHandler';
import {_td, newTranslatableError} from 'matrix-react-sdk/src/languageHandler';
import AutoDiscoveryUtils from 'matrix-react-sdk/src/utils/AutoDiscoveryUtils';
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
@ -38,48 +37,11 @@ import {parseQs, parseQsFromFragment} from './url_utils';
import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg';
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import {setTheme} from "matrix-react-sdk/src/theme";
import CallHandler from 'matrix-react-sdk/src/CallHandler';
import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init";
let lastLocationHashSet = null;
function checkBrowserFeatures() {
if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing.");
return false;
}
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
// Modernizr requires rules to be lowercase with no punctuation:
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
window.Modernizr.addTest("promiseprototypefinally", () =>
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () =>
window.Object && typeof window.Object.fromEntries === "function");
const featureList = Object.keys(window.Modernizr);
let featureComplete = true;
for (let i = 0; i < featureList.length; i++) {
if (window.Modernizr[featureList[i]] === undefined) {
console.error(
"Looked for feature '%s' but Modernizr has no results for this. " +
"Has it been configured correctly?", featureList[i],
);
return false;
}
if (window.Modernizr[featureList[i]] === false) {
console.error("Browser missing feature: '%s'", featureList[i]);
// toggle flag rather than return early so we log all missing features rather than just the first.
featureComplete = false;
}
}
return featureComplete;
}
// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) {
@ -164,7 +126,7 @@ function onTokenLoginCompleted() {
window.location.href = formatted;
}
export async function loadApp() {
export async function loadApp(fragParams: {}) {
// XXX: the way we pass the path to the worker script from webpack via html in body's dataset is a hack
// but alternatives seem to require changing the interface to passing Workers to js-sdk
const vectorIndexeddbWorkerScript = document.body.dataset.vectorIndexeddbWorkerScript;
@ -173,133 +135,37 @@ export async function loadApp() {
// the bundling. The js-sdk will just fall back to accessing
// indexeddb directly with no worker script, but we want to
// make sure the indexeddb script is present, so fail hard.
throw new Error("Missing indexeddb worker script!");
throw newTranslatableError(_td("Missing indexeddb worker script!"));
}
MatrixClientPeg.setIndexedDbWorkerScript(vectorIndexeddbWorkerScript);
CallHandler.setConferenceHandler(VectorConferenceHandler);
window.addEventListener('hashchange', onHashChange);
await loadOlm();
// set the platform for react sdk
preparePlatform();
const platform = PlatformPeg.get();
// Load the config from the platform
const configError = await loadConfig();
// Load language after loading config.json so that settingsDefaults.language can be applied
await loadLanguage();
const fragparts = parseQsFromFragment(window.location);
const params = parseQs(window.location);
// don't try to redirect to the native apps if we're
// verifying a 3pid (but after we've loaded the config)
// or if the user is following a deep link
// (https://github.com/vector-im/riot-web/issues/7378)
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
if (!preventRedirect) {
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isAndroid = /Android/.test(navigator.userAgent);
if (isIos || isAndroid) {
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
window.location = "mobile_guide/";
return;
}
}
}
// as quickly as we possibly can, set a default theme...
await setTheme();
// Now that we've loaded the theme (CSS), display the config syntax error if needed.
if (configError && configError.err && configError.err instanceof SyntaxError) {
const errorMessage = (
<div>
<p>
{_t(
"Your Riot configuration contains invalid JSON. Please correct the problem " +
"and reload the page.",
)}
</p>
<p>
{_t(
"The message from the parser is: %(message)s",
{message: configError.err.message || _t("Invalid JSON")},
)}
</p>
</div>
);
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
window.matrixChat = ReactDOM.render(
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
document.getElementById('matrixchat'),
);
return;
}
const validBrowser = checkBrowserFeatures();
const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
console.log("Vector starting at " + urlWithoutQuery);
if (configError) {
window.matrixChat = ReactDOM.render(<div className="error">
Unable to load config file: please refresh the page to try again.
</div>, document.getElementById('matrixchat'));
} else if (validBrowser || acceptInvalidBrowser) {
platform.startUpdater();
// Don't bother loading the app until the config is verified
verifyServerConfig().then((newConfig) => {
const MatrixChat = sdk.getComponent('structures.MatrixChat');
window.matrixChat = ReactDOM.render(
<MatrixChat
onNewScreen={onNewScreen}
makeRegistrationUrl={makeRegistrationUrl}
ConferenceHandler={VectorConferenceHandler}
config={newConfig}
realQueryParams={params}
startingFragmentQueryParams={fragparts.params}
enableGuest={!SdkConfig.get().disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>,
document.getElementById('matrixchat'),
);
}).catch(err => {
console.error(err);
platform.startUpdater();
let errorMessage = err.translatedMessage
|| _t("Unexpected error preparing the app. See console for details.");
errorMessage = <span>{errorMessage}</span>;
// Like the compatibility page, AWOOOOOGA at the user
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
window.matrixChat = ReactDOM.render(
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
document.getElementById('matrixchat'),
);
});
} else {
console.error("Browser is missing required features.");
// take to a different landing page to AWOOOOOGA at the user
const CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
window.matrixChat = ReactDOM.render(
<CompatibilityPage onAccept={function() {
if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
console.log("User accepts the compatibility risks.");
loadApp();
}} />,
document.getElementById('matrixchat'),
);
}
// Don't bother loading the app until the config is verified
const config = await verifyServerConfig();
const MatrixChat = sdk.getComponent('structures.MatrixChat');
return <MatrixChat
onNewScreen={onNewScreen}
makeRegistrationUrl={makeRegistrationUrl}
ConferenceHandler={VectorConferenceHandler}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>;
}
async function verifyServerConfig() {
@ -384,7 +250,6 @@ async function verifyServerConfig() {
}
}
validatedConfig.isDefault = true;
// Just in case we ever have to debug this

View File

@ -1,47 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C.
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.
*/
// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import './rageshakesetup';
import './modernizr';
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference
// the SDK until we have to in imports.
console.log("Loading skin...");
import * as sdk from 'matrix-react-sdk';
import * as skin from "../component-index";
sdk.loadSkin(skin);
console.log("Skin loaded!");
// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
// run on the components. We use `require` here to make sure webpack doesn't optimize this into an async
// import and thus running before the skin can load.
require("./app").loadApp();

207
src/vector/index.ts Normal file
View File

@ -0,0 +1,207 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C.
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.
*/
// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gfm.css/gfm.css');
require('highlight.js/styles/github.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';
// 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.");
return false;
}
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
// Modernizr requires rules to be lowercase with no punctuation:
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
window.Modernizr.addTest("promiseprototypefinally", () =>
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () =>
window.Object && typeof window.Object.fromEntries === "function");
const featureList = Object.keys(window.Modernizr);
let featureComplete = true;
for (let i = 0; i < featureList.length; i++) {
if (window.Modernizr[featureList[i]] === undefined) {
console.error(
"Looked for feature '%s' but Modernizr has no results for this. " +
"Has it been configured correctly?", featureList[i],
);
return false;
}
if (window.Modernizr[featureList[i]] === false) {
console.error("Browser missing feature: '%s'", featureList[i]);
// toggle flag rather than return early so we log all missing features rather than just the first.
featureComplete = false;
}
}
return featureComplete;
}
let acceptBrowser = checkBrowserFeatures();
if (!acceptBrowser && window.localStorage) {
acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
}
// React depends on Map & Set which we check for using modernizr's es6collections
// if modernizr fails we may not have a functional react to show the error message.
// try in react but fallback to an `alert`
// We start loading stuff but don't block on it until as late as possible to allow
// the browser to use as much parallelism as it can.
// Load parallelism is based on research in https://github.com/vector-im/riot-web/issues/12253
async function start() {
// load init.ts async so that its code is not executed immediately and we can catch any exceptions
const {
rageshakePromise,
preparePlatform,
loadOlm,
loadConfig,
loadSkin,
loadLanguage,
loadTheme,
loadApp,
showError,
showIncompatibleBrowser,
_t,
} = await import(
/* webpackChunkName: "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
await settled(rageshakePromise);
const fragparts = parseQsFromFragment(window.location);
// don't try to redirect to the native apps if we're
// verifying a 3pid (but after we've loaded the config)
// or if the user is following a deep link
// (https://github.com/vector-im/riot-web/issues/7378)
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
if (!preventRedirect) {
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isAndroid = /Android/.test(navigator.userAgent);
if (isIos || isAndroid) {
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
window.location.href = "mobile_guide/";
return;
}
}
}
const loadOlmPromise = loadOlm();
// set the platform for react sdk
preparePlatform();
// load config requires the platform to be ready
const loadConfigPromise = loadConfig();
await settled(loadConfigPromise); // wait for it to settle
// keep initialising so that we can show any possible error with as many features (theme, i18n) as possible
// Load language after loading config.json so that settingsDefaults.language can be applied
const loadLanguagePromise = loadLanguage();
// as quickly as we possibly can, set a default theme...
const loadThemePromise = loadTheme();
const loadSkinPromise = loadSkin();
// await things settling so that any errors we have to render have features like i18n running
await settled(loadSkinPromise, loadThemePromise, loadLanguagePromise);
// ##########################
// error handling begins here
// ##########################
if (!acceptBrowser) {
await new Promise(resolve => {
console.error("Browser is missing required features.");
// take to a different landing page to AWOOOOOGA at the user
showIncompatibleBrowser(() => {
if (window.localStorage) {
window.localStorage.setItem('mx_accepts_unsupported_browser', String(true));
}
console.log("User accepts the compatibility risks.");
resolve();
});
});
}
try {
// await config here
await loadConfigPromise;
} catch (error) {
// Now that we've loaded the theme (CSS), display the config syntax error if needed.
if (error.err && error.err instanceof SyntaxError) {
return showError(_t("Your Riot is misconfigured"), [
_t("Your Riot configuration contains invalid JSON. Please correct the problem and reload the page."),
_t("The message from the parser is: %(message)s", { message: error.err.message || _t("Invalid JSON")}),
]);
}
return showError(_t("Unable to load config file: please refresh the page to try again."));
}
// ##################################
// app load critical path starts here
// assert things started successfully
// ##################################
await loadOlmPromise;
await loadSkinPromise;
await loadThemePromise;
await loadLanguagePromise;
// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
// run on the components.
await loadApp(fragparts.params);
} catch (err) {
console.error(err);
// Like the compatibility page, AWOOOOOGA at the user
await showError(_t("Your Riot is misconfigured"), [
err.translatedMessage || _t("Unexpected error preparing the app. See console for details."),
]);
}
}
start().catch(err => {
console.error(err);
if (!acceptBrowser) {
// TODO redirect to static incompatible browser page
}
});

View File

@ -20,17 +20,24 @@ limitations under the License.
// @ts-ignore
import olmWasmPath from "olm/olm.wasm";
import Olm from 'olm';
import * as ReactDOM from "react-dom";
import * as React from "react";
import * as languageHandler from 'matrix-react-sdk/src/languageHandler';
import * as languageHandler from "matrix-react-sdk/src/languageHandler";
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import ElectronPlatform from "./platform/ElectronPlatform";
import WebPlatform from "./platform/WebPlatform";
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import {setTheme} from "matrix-react-sdk/src/theme";
import { initRageshake } from "./rageshakesetup";
export const rageshakePromise = initRageshake();
export function preparePlatform() {
if ((<any>window).ipcRenderer) {
if (window.ipcRenderer) {
console.log("Using Electron platform");
const plaf = new ElectronPlatform();
PlatformPeg.set(plaf);
@ -40,21 +47,12 @@ export function preparePlatform() {
}
}
export async function loadConfig(): Promise<Error | void> {
const platform = PlatformPeg.get();
let configJson;
try {
configJson = await platform.getConfig();
} catch (e) {
return e;
} finally {
// 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 || {});
}
export async function loadConfig() {
// 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(await PlatformPeg.get().getConfig() || {});
}
export function loadOlm(): Promise<void> {
@ -112,3 +110,57 @@ export async function loadLanguage() {
console.error("Unable to set language", e);
}
}
export async function loadSkin() {
// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference
// the SDK until we have to in imports.
console.log("Loading skin...");
// load these async so that its code is not executed immediately and we can catch any exceptions
const [sdk, skin] = await Promise.all([
import(
/* webpackChunkName: "matrix-react-sdk" */
/* webpackPreload: true */
"matrix-react-sdk"),
import(
/* webpackChunkName: "riot-web-component-index" */
/* webpackPreload: true */
// @ts-ignore - this module is generated so may fail lint
"../component-index"),
]);
sdk.loadSkin(skin);
console.log("Skin loaded!");
}
export async function loadTheme() {
setTheme();
}
export async function loadApp(fragParams: {}) {
// load app.js async so that its code is not executed immediately and we can catch any exceptions
const module = await import(
/* webpackChunkName: "riot-web-app" */
/* webpackPreload: true */
"./app");
window.matrixChat = ReactDOM.render(await module.loadApp(fragParams),
document.getElementById('matrixchat'));
}
export async function showError(title: string, messages?: string[]) {
const ErrorView = (await import(
/* webpackChunkName: "error-view" */
/* webpackPreload: true */
"../components/structures/ErrorView")).default;
window.matrixChat = ReactDOM.render(<ErrorView title={title} messages={messages} />,
document.getElementById('matrixchat'));
}
export async function showIncompatibleBrowser(onAccept) {
const CompatibilityPage = (await import(
/* webpackChunkName: "compatibility-page" */
/* webpackPreload: true */
"matrix-react-sdk/src/components/structures/CompatibilityPage")).default;
window.matrixChat = ReactDOM.render(<CompatibilityPage onAccept={onAccept} />,
document.getElementById('matrixchat'));
}
export const _t = languageHandler._t;

View File

@ -32,6 +32,7 @@ import Spinner from "matrix-react-sdk/src/components/views/elements/Spinner";
import {Categories, Modifiers, registerShortcut} from "matrix-react-sdk/src/accessibility/KeyboardShortcuts";
import {Key} from "matrix-react-sdk/src/Keyboard";
import React from "react";
import {randomString} from "matrix-js-sdk/src/randomstring";
const ipcRenderer = window.ipcRenderer;
const isMac = navigator.platform.toUpperCase().includes('MAC');
@ -218,7 +219,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
// register Mac specific shortcuts
// register OS-specific shortcuts
if (isMac) {
registerShortcut(Categories.NAVIGATION, {
keybinds: [{
@ -227,7 +228,33 @@ export default class ElectronPlatform extends VectorBasePlatform {
}],
description: _td("Open user settings"),
});
registerShortcut(Categories.NAVIGATION, {
keybinds: [{
modifiers: [Modifiers.COMMAND],
key: Key.SQUARE_BRACKET_LEFT,
}, {
modifiers: [Modifiers.COMMAND],
key: Key.SQUARE_BRACKET_RIGHT,
}],
description: _td("Previous/next recently visited room or community"),
});
} else {
registerShortcut(Categories.NAVIGATION, {
keybinds: [{
modifiers: [Modifiers.ALT],
key: Key.ARROW_LEFT,
}, {
modifiers: [Modifiers.ALT],
key: Key.ARROW_RIGHT,
}],
description: _td("Previous/next recently visited room or community"),
});
}
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
this.ssoID = randomString(32);
this._ipcCall("startSSOFlow", this.ssoID);
}
async getConfig(): Promise<{}> {
@ -424,6 +451,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
getSSOCallbackUrl(hsUrl: string, isUrl: string): URL {
const url = super.getSSOCallbackUrl(hsUrl, isUrl);
url.protocol = "riot";
url.searchParams.set("riot-desktop-ssoid", this.ssoID);
return url;
}
@ -434,4 +462,32 @@ export default class ElectronPlatform extends VectorBasePlatform {
description: <Spinner />,
});
}
_navigateForwardBack(back: boolean) {
this._ipcCall(back ? "navigateBack" : "navigateForward");
}
onKeyDown(ev: KeyboardEvent): boolean {
let handled = false;
switch (ev.key) {
case Key.SQUARE_BRACKET_LEFT:
case Key.SQUARE_BRACKET_RIGHT:
if (isMac && ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey) {
this._navigateForwardBack(ev.key === Key.SQUARE_BRACKET_LEFT);
handled = true;
}
break;
case Key.ARROW_LEFT:
case Key.ARROW_RIGHT:
if (!isMac && ev.altKey && !ev.metaKey && !ev.ctrlKey && !ev.shiftKey) {
this._navigateForwardBack(ev.key === Key.ARROW_LEFT);
handled = true;
}
break;
}
return handled;
}
}

View File

@ -19,7 +19,7 @@ limitations under the License.
import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import request from 'browser-request';
import dis from 'matrix-react-sdk/src/dispatcher.js';
import dis from 'matrix-react-sdk/src/dispatcher';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import url from 'url';

View File

@ -30,8 +30,9 @@ import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import sendBugReport from "matrix-react-sdk/src/rageshake/submit-rageshake";
function initRageshake() {
rageshake.init().then(() => {
export function initRageshake() {
const prom = rageshake.init();
prom.then(() => {
console.log("Initialised rageshake.");
console.log("To fix line numbers in Chrome: " +
"Meatball menu → Settings → Blackboxing → Add /rageshake\\.js$");
@ -46,10 +47,9 @@ function initRageshake() {
}, (err) => {
console.error("Failed to initialise rageshake: " + err);
});
return prom;
}
initRageshake();
window.mxSendRageshake = function(text: string, withLogs?: boolean) {
if (withLogs === undefined) withLogs = true;
if (!text || !text.trim()) {

View File

@ -32,7 +32,7 @@ export function parseQsFromFragment(location: Location) {
const result = {
location: decodeURIComponent(hashparts[0]),
params: {},
params: <qs.ParsedUrlQuery>{},
};
if (hashparts.length > 1) {

View File

@ -312,7 +312,6 @@ describe('loading:', function() {
it('shows the last known room by default', function() {
httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
loadApp();
@ -332,7 +331,6 @@ describe('loading:', function() {
localStorage.removeItem("mx_last_room_id");
httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
loadApp();
@ -350,7 +348,6 @@ describe('loading:', function() {
it('shows a room view if we followed a room link', function() {
httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
loadApp({
uriFragment: "#/room/!room:id",
@ -663,7 +660,6 @@ describe('loading:', function() {
return sleep(1);
}).then(() => {
httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
return expectAndAwaitSync().catch((e) => {
throw new Error("Never got /sync after login: did the client start?");
});

View File

@ -2,6 +2,8 @@
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2016",

View File

@ -31,7 +31,7 @@ module.exports = (env, argv) => {
...development,
entry: {
"bundle": "./src/vector/index.js",
"bundle": "./src/vector/index.ts",
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
"mobileguide": "./src/vector/mobile_guide/index.js",
"jitsi": "./src/vector/jitsi/index.ts",
@ -182,6 +182,7 @@ module.exports = (env, argv) => {
require("postcss-simple-vars")(),
require("postcss-strip-inline-comments")(),
require("postcss-hexrgba")(),
// It's important that this plugin is last otherwise we end
// up with broken CSS.
@ -219,6 +220,7 @@ module.exports = (env, argv) => {
require("postcss-mixins")(),
require("postcss-easings")(),
require("postcss-strip-inline-comments")(),
require("postcss-hexrgba")(),
// It's important that this plugin is last otherwise we end
// up with broken CSS.

1571
yarn.lock

File diff suppressed because it is too large Load Diff