Merge branch 'develop' into travis/inline-widgets

This commit is contained in:
Travis Ralston
2020-03-16 19:23:49 -06:00
144 changed files with 11229 additions and 7108 deletions

486
src/vector/app.js Normal file
View File

@@ -0,0 +1,486 @@
/*
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.
*/
import olmWasmPath from 'olm/olm.wasm';
import React from 'react';
// add React and ReactPerf to the global namespace, to make them easier to
// 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 * as languageHandler from 'matrix-react-sdk/src/languageHandler';
import {_t, _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";
import url from 'url';
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 SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import {setTheme} from "matrix-react-sdk/src/theme";
import Olm from 'olm';
import CallHandler from 'matrix-react-sdk/src/CallHandler';
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) {
const fragparts = parseQsFromFragment(location);
return {
screen: fragparts.location.substring(1),
params: fragparts.params,
};
}
// Here, we do some crude URL analysis to allow
// deep-linking.
function routeUrl(location) {
if (!window.matrixChat) return;
console.log("Routing URL ", location.href);
const s = getScreenFromLocation(location);
window.matrixChat.showScreen(s.screen, s.params);
}
function onHashChange(ev) {
if (decodeURIComponent(window.location.hash) === lastLocationHashSet) {
// we just set this: no need to route it!
return;
}
routeUrl(window.location);
}
// This will be called whenever the SDK changes screens,
// so a web page can update the URL bar appropriately.
function onNewScreen(screen) {
console.log("newscreen "+screen);
const hash = '#/' + screen;
lastLocationHashSet = hash;
window.location.hash = hash;
}
// We use this to work out what URL the SDK should
// pass through when registering to allow the user to
// click back to the client having registered.
// It's up to us to recognise if we're loaded with
// this URL and tell MatrixClient to resume registration.
//
// If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use riot.im/app for now instead.
function makeRegistrationUrl(params) {
let url;
if (window.location.protocol === "vector:") {
url = 'https://riot.im/app/#/register';
} else {
url = (
window.location.protocol + '//' +
window.location.host +
window.location.pathname +
'#/register'
);
}
const keys = Object.keys(params);
for (let i = 0; i < keys.length; ++i) {
if (i === 0) {
url += '?';
} else {
url += '&';
}
const k = keys[i];
url += k + '=' + encodeURIComponent(params[k]);
}
return url;
}
function onTokenLoginCompleted() {
// if we did a token login, we're now left with the token, hs and is
// url as query params in the url; a little nasty but let's redirect to
// clear them.
const parsedUrl = url.parse(window.location.href);
parsedUrl.search = "";
const formatted = url.format(parsedUrl);
console.log("Redirecting to " + formatted + " to drop loginToken " +
"from queryparams");
window.location.href = formatted;
}
export async function loadApp() {
// 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;
if (!vectorIndexeddbWorkerScript) {
// If this is missing, something has probably gone wrong with
// 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!");
}
MatrixClientPeg.setIndexedDbWorkerScript(vectorIndexeddbWorkerScript);
CallHandler.setConferenceHandler(VectorConferenceHandler);
window.addEventListener('hashchange', onHashChange);
await loadOlm();
// set the platform for react sdk
if (window.ipcRenderer) {
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();
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.
SdkConfig.put(configJson);
// 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 (configSyntaxError) {
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={!configJson.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>,
document.getElementById('matrixchat'),
);
}).catch(err => {
console.error(err);
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'),
);
}
}
function loadOlm() {
/* Load Olm. We try the WebAssembly version first, and then the legacy,
* asm.js version if that fails. For this reason we need to wait for this
* to finish before continuing to load the rest of the app. In future
* we could somehow pass a promise down to react-sdk and have it wait on
* that so olm can be loading in parallel with the rest of the app.
*
* We also need to tell the Olm js to look for its wasm file at the same
* level as index.html. It really should be in the same place as the js,
* ie. in the bundle directory, but as far as I can tell this is
* completely impossible with webpack. We do, however, use a hashed
* filename to avoid caching issues.
*/
return Olm.init({
locateFile: () => olmWasmPath,
}).then(() => {
console.log("Using WebAssembly Olm");
}).catch((e) => {
console.log("Failed to load Olm: trying legacy version", e);
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'olm_legacy.js'; // XXX: This should be cache-busted too
s.onload = resolve;
s.onerror = reject;
document.body.appendChild(s);
}).then(() => {
// Init window.Olm, ie. the one just loaded by the script tag,
// not 'Olm' which is still the failed wasm version.
return window.Olm.init();
}).then(() => {
console.log("Using legacy Olm");
}).catch((e) => {
console.log("Both WebAssembly and asm.js Olm failed!", e);
});
});
}
async function loadLanguage() {
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/true);
let langs = [];
if (!prefLang) {
languageHandler.getLanguagesFromBrowser().forEach((l) => {
langs.push(...languageHandler.getNormalizedLanguageKeys(l));
});
} else {
langs = [prefLang];
}
try {
await languageHandler.setLanguage(langs);
document.documentElement.setAttribute("lang", languageHandler.getCurrentLanguage());
} catch (e) {
console.error("Unable to set language", e);
}
}
async function verifyServerConfig() {
let validatedConfig;
try {
console.log("Verifying homeserver configuration");
// Note: the query string may include is_url and hs_url - we only respect these in the
// context of email validation. Because we don't respect them otherwise, we do not need
// to parse or consider them here.
// Note: Although we throw all 3 possible configuration options through a .well-known-style
// verification, we do not care if the servers are online at this point. We do moderately
// care if they are syntactically correct though, so we shove them through the .well-known
// validators for that purpose.
const config = SdkConfig.get();
let wkConfig = config['default_server_config']; // overwritten later under some conditions
const serverName = config['default_server_name'];
const hsUrl = config['default_hs_url'];
const isUrl = config['default_is_url'];
const incompatibleOptions = [wkConfig, serverName, hsUrl].filter(i => !!i);
if (incompatibleOptions.length > 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(_td(
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
"or default_hs_url.",
));
}
if (incompatibleOptions.length < 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(_td("Invalid configuration: no default server specified."));
}
if (hsUrl) {
console.log("Config uses a default_hs_url - constructing a default_server_config using this information");
console.warn(
"DEPRECATED CONFIG OPTION: In the future, default_hs_url will not be accepted. Please use " +
"default_server_config instead.",
);
wkConfig = {
"m.homeserver": {
"base_url": hsUrl,
},
};
if (isUrl) {
wkConfig["m.identity_server"] = {
"base_url": isUrl,
};
}
}
let discoveryResult = null;
if (wkConfig) {
console.log("Config uses a default_server_config - validating object");
discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);
}
if (serverName) {
console.log("Config uses a default_server_name - doing .well-known lookup");
console.warn(
"DEPRECATED CONFIG OPTION: In the future, default_server_name will not be accepted. Please " +
"use default_server_config instead.",
);
discoveryResult = await AutoDiscovery.findClientConfig(serverName);
}
validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
} catch (e) {
const {hsUrl, isUrl, userId} = Lifecycle.getLocalStorageSessionVars();
if (hsUrl && userId) {
console.error(e);
console.warn("A session was found - suppressing config error and using the session's homeserver");
console.log("Using pre-existing hsUrl and isUrl: ", {hsUrl, isUrl});
validatedConfig = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
} else {
// the user is not logged in, so scream
throw e;
}
}
validatedConfig.isDefault = true;
// Just in case we ever have to debug this
console.log("Using homeserver config:", validatedConfig);
// Add the newly built config to the actual config for use by the app
console.log("Updating SdkConfig with validated discovery information");
SdkConfig.add({"validated_server_config": validatedConfig});
return SdkConfig.get();
}

View File

@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import request from 'browser-request';
// Load the config file. First try to load up a domain-specific config of the
// form "config.$domain.json" and if that fails, fall back to config.json.
export async function getVectorConfig(relativeLocation) {
if (relativeLocation === undefined) relativeLocation = '';
if (relativeLocation !== '' && !relativeLocation.endsWith('/')) relativeLocation += '/';
@@ -35,30 +36,34 @@ export async function getVectorConfig(relativeLocation) {
function getConfig(configJsonFilename) {
return new Promise(function(resolve, reject) {
request(
{ method: "GET", url: configJsonFilename },
{ method: "GET", url: configJsonFilename, qs: { cachebuster: Date.now() } },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
// Lack of a config isn't an error, we should
// just use the defaults.
// Also treat a blank config as no config, assuming
// the status code is 0, because we don't get 404s
// from file: URIs so this is the only way we can
// not fail if the file doesn't exist when loading
// from a file:// URI.
if (response) {
if (response.status == 404 || (response.status == 0 && body == '')) {
resolve({});
try {
if (err || response.status < 200 || response.status >= 300) {
// Lack of a config isn't an error, we should
// just use the defaults.
// Also treat a blank config as no config, assuming
// the status code is 0, because we don't get 404s
// from file: URIs so this is the only way we can
// not fail if the file doesn't exist when loading
// from a file:// URI.
if (response) {
if (response.status == 404 || (response.status == 0 && body == '')) {
resolve({});
}
}
reject({err: err, response: response});
return;
}
reject({err: err, response: response});
return;
}
// We parse the JSON ourselves rather than use the JSON
// parameter, since this throws a parse error on empty
// which breaks if there's no config.json and we're
// loading from the filesystem (see above).
resolve(JSON.parse(body));
// We parse the JSON ourselves rather than use the JSON
// parameter, since this throws a parse error on empty
// which breaks if there's no config.json and we're
// loading from the filesystem (see above).
resolve(JSON.parse(body));
} catch (e) {
reject({err: e});
}
},
);
});

View File

@@ -13,6 +13,7 @@
<link rel="apple-touch-icon" sizes="152x152" href="<%= require('../../res/vector-icons/apple-touch-icon-152x152.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../../res/vector-icons/apple-touch-icon-180x180.png') %>">
<link rel="manifest" href="manifest.json">
<meta name="referrer" content="no-referrer">
<link rel="shortcut icon" href="<%= require('../../res/vector-icons/favicon.ico') %>">
<meta name="apple-mobile-web-app-title" content="Riot">
<meta name="application-name" content="Riot">
@@ -21,6 +22,21 @@
<meta name="msapplication-config" content="<%= require('../../res/vector-icons/browserconfig.xml') %>">
<meta name="theme-color" content="#ffffff">
<meta property="og:image" content="<%= htmlWebpackPlugin.options.vars.og_image_url %>" />
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-eval' https://www.recaptcha.net https://www.gstatic.com;
img-src * blob: data:;
connect-src *;
font-src 'self' data:;
media-src * blob: data:;
child-src * blob: data:;
worker-src 'self';
frame-src * blob: data:;
form-action 'self';
object-src 'self';
manifest-src 'self';
">
<% for (var i=0; i < htmlWebpackPlugin.files.css.length; i++) {
var file = htmlWebpackPlugin.files.css[i];
var match = file.match(/^bundles\/.*?\/theme-(.*)\.css$/);
@@ -33,14 +49,13 @@
<% }
} %>
</head>
<body style="height: 100%;">
<section id="matrixchat" style="height: 100%; overflow: auto;"></section>
<body style="height: 100%;" data-vector-indexeddb-worker-script="<%= htmlWebpackPlugin.files.chunks['indexeddb-worker'].entry %>">
<noscript>Sorry, Riot requires JavaScript to be enabled.</noscript> <!-- TODO: Translate this? -->
<script>
window.vector_indexeddb_worker_script = '<%= htmlWebpackPlugin.files.chunks['indexeddb-worker'].entry %>';
</script>
<section id="matrixchat" style="height: 100%; overflow: auto;"></section>
<script src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"></script>
<img src="<%= require('matrix-react-sdk/res/img/warning.svg') %>" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
<img src="<%= require('matrix-react-sdk/res/img/e2e/warning.svg') %>" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
<img src="<%= require('matrix-react-sdk/res/img/feather-customised/warning-triangle.svg') %>" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
<audio id="messageAudio">
<source src="media/message.ogg" type="audio/ogg" />
<source src="media/message.mp3" type="audio/mpeg" />

View File

@@ -2,6 +2,8 @@
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.
@@ -22,525 +24,25 @@ limitations under the License.
require('gemini-scrollbar/gemini-scrollbar.css');
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
require('draft-js/dist/Draft.css');
import olmWasmPath from 'olm/olm.wasm';
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import './rageshakesetup';
import React from 'react';
// add React and ReactPerf to the global namespace, to make them easier to
// access via the console
global.React = React;
if (process.env.NODE_ENV !== 'production') {
global.Perf = require('react-addons-perf');
}
import './modernizr';
import ReactDOM from 'react-dom';
import sdk from 'matrix-react-sdk';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
sdk.loadSkin(require('../component-index'));
import VectorConferenceHandler from 'matrix-react-sdk/lib/VectorConferenceHandler';
import Promise from 'bluebird';
import request from 'browser-request';
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
import {_t, _td, newTranslatableError} from 'matrix-react-sdk/lib/languageHandler';
import AutoDiscoveryUtils from 'matrix-react-sdk/lib/utils/AutoDiscoveryUtils';
import {AutoDiscovery} from "matrix-js-sdk/lib/autodiscovery";
import * as Lifecycle from "matrix-react-sdk/lib/Lifecycle";
import url from 'url';
import {parseQs, parseQsFromFragment} from './url_utils';
import ElectronPlatform from './platform/ElectronPlatform';
import WebPlatform from './platform/WebPlatform';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore";
import Tinter from 'matrix-react-sdk/lib/Tinter';
import SdkConfig from "matrix-react-sdk/lib/SdkConfig";
import Olm from 'olm';
import CallHandler from 'matrix-react-sdk/lib/CallHandler';
import {getVectorConfig} from './getconfig';
let lastLocationHashSet = null;
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
Promise.config({warnings: false});
function checkBrowserFeatures(featureList) {
if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing.");
return false;
}
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;
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) {
const fragparts = parseQsFromFragment(location);
return {
screen: fragparts.location.substring(1),
params: fragparts.params,
};
}
// 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!");
// Here, we do some crude URL analysis to allow
// deep-linking.
function routeUrl(location) {
if (!window.matrixChat) return;
console.log("Routing URL ", location.href);
const s = getScreenFromLocation(location);
window.matrixChat.showScreen(s.screen, s.params);
}
function onHashChange(ev) {
if (decodeURIComponent(window.location.hash) == lastLocationHashSet) {
// we just set this: no need to route it!
return;
}
routeUrl(window.location);
}
// This will be called whenever the SDK changes screens,
// so a web page can update the URL bar appropriately.
function onNewScreen(screen) {
console.log("newscreen "+screen);
const hash = '#/' + screen;
lastLocationHashSet = hash;
window.location.hash = hash;
}
// We use this to work out what URL the SDK should
// pass through when registering to allow the user to
// click back to the client having registered.
// It's up to us to recognise if we're loaded with
// this URL and tell MatrixClient to resume registration.
//
// If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use riot.im/app for now instead.
function makeRegistrationUrl(params) {
let url;
if (window.location.protocol === "vector:") {
url = 'https://riot.im/app/#/register';
} else {
url = (
window.location.protocol + '//' +
window.location.host +
window.location.pathname +
'#/register'
);
}
const keys = Object.keys(params);
for (let i = 0; i < keys.length; ++i) {
if (i == 0) {
url += '?';
} else {
url += '&';
}
const k = keys[i];
url += k + '=' + encodeURIComponent(params[k]);
}
return url;
}
export function getConfig(configJsonFilename) {
return new Promise(function(resolve, reject) {
request(
{ method: "GET", url: configJsonFilename },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
// Lack of a config isn't an error, we should
// just use the defaults.
// Also treat a blank config as no config, assuming
// the status code is 0, because we don't get 404s
// from file: URIs so this is the only way we can
// not fail if the file doesn't exist when loading
// from a file:// URI.
if (response) {
if (response.status == 404 || (response.status == 0 && body == '')) {
resolve({});
}
}
reject({err: err, response: response});
return;
}
// We parse the JSON ourselves rather than use the JSON
// parameter, since this throws a parse error on empty
// which breaks if there's no config.json and we're
// loading from the filesystem (see above).
resolve(JSON.parse(body));
},
);
});
}
function onTokenLoginCompleted() {
// if we did a token login, we're now left with the token, hs and is
// url as query params in the url; a little nasty but let's redirect to
// clear them.
const parsedUrl = url.parse(window.location.href);
parsedUrl.search = "";
const formatted = url.format(parsedUrl);
console.log("Redirecting to " + formatted + " to drop loginToken " +
"from queryparams");
window.location.href = formatted;
}
async function loadApp() {
if (window.vector_indexeddb_worker_script === undefined) {
// If this is missing, something has probably gone wrong with
// 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!");
}
MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);
CallHandler.setConferenceHandler(VectorConferenceHandler);
window.addEventListener('hashchange', onHashChange);
await loadOlm();
await loadLanguage();
const fragparts = parseQsFromFragment(window.location);
const params = parseQs(window.location);
// set the platform for react sdk
if (window.ipcRenderer) {
console.log("Using Electron platform");
const plaf = new ElectronPlatform();
PlatformPeg.set(plaf);
// Electron only: see if we need to do a one-time data
// migration
if (window.localStorage.getItem('mx_user_id') === null) {
console.log("Migrating session from old origin...");
await plaf.migrateFromOldOrigin();
console.log("Origin migration complete");
}
} else {
console.log("Using Web platform");
PlatformPeg.set(new WebPlatform());
}
// Load the config file. First try to load up a domain-specific config of the
// form "config.$domain.json" and if that fails, fall back to config.json.
let configJson;
let configError;
try {
configJson = await getVectorConfig();
} catch (e) {
configError = e;
}
// 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);
// 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...
let a;
const theme = SettingsStore.getValue("theme");
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
const href = a.getAttribute("href");
if (!href) continue;
// shouldn't we be using the 'title' tag rather than the href?
const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
if (match) {
if (match[1] === theme) {
// remove the disabled flag off the stylesheet
// Firefox requires setting the attribute to false, so do
// that instead of removing it. Related:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1281135
a.disabled = false;
// in case the Tinter.tint() in MatrixChat fires before the
// CSS has actually loaded (which in practice happens)...
// This if fixes Tinter.setTheme to not fire on Firefox
// in case it is the first time loading Riot.
// `InstallTrigger` is a Object which only exists on Firefox
// (it is used for their Plugins) and can be used as a
// feature check.
// Firefox loads css always before js. This is why we dont use
// onload or it's EventListener as thoose will never trigger.
if (typeof InstallTrigger !== 'undefined') {
Tinter.setTheme(theme);
} else {
// FIXME: we should probably block loading the app or even
// showing a spinner until the theme is loaded, to avoid
// flashes of unstyled content.
a.onload = () => {
Tinter.setTheme(theme);
};
}
} else {
// Firefox requires this to not be done via `setAttribute`
// or via HTML.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1281135
a.disabled = true;
}
}
}
const validBrowser = checkBrowserFeatures([
"displaytable", "flexbox", "es5object", "es5function", "localstorage",
"objectfit", "indexeddb", "webworkers",
]);
const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
console.log("Vector starting at "+window.location);
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) {
const platform = PlatformPeg.get();
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={!configJson.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>,
document.getElementById('matrixchat'),
);
}).catch(err => {
console.error(err);
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'),
);
}
}
function loadOlm() {
/* Load Olm. We try the WebAssembly version first, and then the legacy,
* asm.js version if that fails. For this reason we need to wait for this
* to finish before continuing to load the rest of the app. In future
* we could somehow pass a promise down to react-sdk and have it wait on
* that so olm can be loading in parallel with the rest of the app.
*
* We also need to tell the Olm js to look for its wasm file at the same
* level as index.html. It really should be in the same place as the js,
* ie. in the bundle directory, but as far as I can tell this is
* completely impossible with webpack. We do, however, use a hashed
* filename to avoid caching issues.
*/
return Olm.init({
locateFile: () => olmWasmPath,
}).then(() => {
console.log("Using WebAssembly Olm");
}).catch((e) => {
console.log("Failed to load Olm: trying legacy version");
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'olm_legacy.js'; // XXX: This should be cache-busted too
s.onload = resolve;
s.onerror = reject;
document.body.appendChild(s);
}).then(() => {
// Init window.Olm, ie. the one just loaded by the script tag,
// not 'Olm' which is still the failed wasm version.
return window.Olm.init();
}).then(() => {
console.log("Using legacy Olm");
}).catch((e) => {
console.log("Both WebAssembly and asm.js Olm failed!", e);
});
});
}
async function loadLanguage() {
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/true);
let langs = [];
if (!prefLang) {
languageHandler.getLanguagesFromBrowser().forEach((l) => {
langs.push(...languageHandler.getNormalizedLanguageKeys(l));
});
} else {
langs = [prefLang];
}
try {
await languageHandler.setLanguage(langs);
document.documentElement.setAttribute("lang", languageHandler.getCurrentLanguage());
} catch (e) {
console.error("Unable to set language", e);
}
}
async function verifyServerConfig() {
let validatedConfig;
try {
console.log("Verifying homeserver configuration");
// Note: the query string may include is_url and hs_url - we only respect these in the
// context of email validation. Because we don't respect them otherwise, we do not need
// to parse or consider them here.
// Note: Although we throw all 3 possible configuration options through a .well-known-style
// verification, we do not care if the servers are online at this point. We do moderately
// care if they are syntactically correct though, so we shove them through the .well-known
// validators for that purpose.
const config = SdkConfig.get();
let wkConfig = config['default_server_config']; // overwritten later under some conditions
const serverName = config['default_server_name'];
const hsUrl = config['default_hs_url'];
const isUrl = config['default_is_url'];
const incompatibleOptions = [wkConfig, serverName, hsUrl].filter(i => !!i);
if (incompatibleOptions.length > 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(_td(
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
"or default_hs_url.",
));
}
if (incompatibleOptions.length < 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(_td("Invalid configuration: no default server specified."));
}
if (hsUrl) {
console.log("Config uses a default_hs_url - constructing a default_server_config using this information");
console.warn(
"DEPRECATED CONFIG OPTION: In the future, default_hs_url will not be accepted. Please use " +
"default_server_config instead.",
);
wkConfig = {
"m.homeserver": {
"base_url": hsUrl,
},
};
if (isUrl) {
wkConfig["m.identity_server"] = {
"base_url": isUrl,
};
}
}
let discoveryResult = null;
if (wkConfig) {
console.log("Config uses a default_server_config - validating object");
discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);
}
if (serverName) {
console.log("Config uses a default_server_name - doing .well-known lookup");
console.warn(
"DEPRECATED CONFIG OPTION: In the future, default_server_name will not be accepted. Please " +
"use default_server_config instead.",
);
discoveryResult = await AutoDiscovery.findClientConfig(serverName);
}
validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
} catch (e) {
const {hsUrl, isUrl, userId} = Lifecycle.getLocalStorageSessionVars();
if (hsUrl && userId) {
console.error(e);
console.warn("A session was found - suppressing config error and using the session's homeserver");
console.log("Using pre-existing hsUrl and isUrl: ", {hsUrl, isUrl});
validatedConfig = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
} else {
// the user is not logged in, so scream
throw e;
}
}
validatedConfig.isDefault = true;
// Just in case we ever have to debug this
console.log("Using homeserver config:", validatedConfig);
// Add the newly built config to the actual config for use by the app
console.log("Updating SdkConfig with validated discovery information");
SdkConfig.add({"validated_server_config": validatedConfig});
return SdkConfig.get();
}
loadApp();
// 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();

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {IndexedDBStoreWorker} from 'matrix-js-sdk/lib/indexeddb-worker.js';
import {IndexedDBStoreWorker} from 'matrix-js-sdk/src/indexeddb-worker.js';
const remoteWorker = new IndexedDBStoreWorker(postMessage);

File diff suppressed because one or more lines are too long

View File

@@ -92,12 +92,14 @@ async function initPage() {
if (isUrl && !isUrl.endsWith('/')) isUrl += '/';
if (hsUrl !== 'https://matrix.org/') {
document.getElementById('configure_riot_button').href =
"https://riot.im/config/config?hs_url=" + encodeURIComponent(hsUrl) +
"&is_url=" + encodeURIComponent(isUrl);
document.getElementById('step1_heading').innerHTML= '1: Install the app';
document.getElementById('step2_container').style.display = 'block';
document.getElementById('hs_url').innerText = hsUrl;
document.getElementById('step_login_header').innerHTML= 'Step 3: Register or Log in';
if (isUrl && isUrl !== "https://vector.im/") {
document.getElementById('default_is').style.display = 'none';
if (isUrl) {
document.getElementById('custom_is').style.display = 'block';
document.getElementById('is_url').style.display = 'block';
document.getElementById('is_url').innerText = isUrl;

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,8 @@
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Copyright 2018 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.
@@ -19,10 +21,15 @@ limitations under the License.
*/
import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import dis from 'matrix-react-sdk/lib/dispatcher';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import Promise from 'bluebird';
import rageshake from 'matrix-react-sdk/lib/rageshake/rageshake';
import BaseEventIndexManager from 'matrix-react-sdk/src/indexing/BaseEventIndexManager';
import dis from 'matrix-react-sdk/src/dispatcher';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import * as rageshake from 'matrix-react-sdk/src/rageshake/rageshake';
import {MatrixClient} from "matrix-js-sdk";
import Modal from "matrix-react-sdk/src/Modal";
import InfoDialog from "matrix-react-sdk/src/components/views/dialogs/InfoDialog";
import Spinner from "matrix-react-sdk/src/components/views/elements/Spinner";
import React from "react";
const ipcRenderer = window.ipcRenderer;
@@ -65,12 +72,112 @@ function getUpdateCheckStatus(status) {
}
}
class SeshatIndexManager extends BaseEventIndexManager {
constructor() {
super();
this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
ipcRenderer.on('seshatReply', this._onIpcReply.bind(this));
}
async _ipcCall(name: string, ...args: []): Promise<{}> {
// TODO this should be moved into the preload.js file.
const ipcCallId = ++this._nextIpcCallId;
return new Promise((resolve, reject) => {
this._pendingIpcCalls[ipcCallId] = {resolve, reject};
window.ipcRenderer.send('seshat', {id: ipcCallId, name, args});
});
}
_onIpcReply(ev: {}, payload: {}) {
if (payload.id === undefined) {
console.warn("Ignoring IPC reply with no ID");
return;
}
if (this._pendingIpcCalls[payload.id] === undefined) {
console.warn("Unknown IPC payload ID: " + payload.id);
return;
}
const callbacks = this._pendingIpcCalls[payload.id];
delete this._pendingIpcCalls[payload.id];
if (payload.error) {
callbacks.reject(payload.error);
} else {
callbacks.resolve(payload.reply);
}
}
async supportsEventIndexing(): Promise<boolean> {
return this._ipcCall('supportsEventIndexing');
}
async initEventIndex(): Promise<> {
return this._ipcCall('initEventIndex');
}
async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<> {
return this._ipcCall('addEventToIndex', ev, profile);
}
async isEventIndexEmpty(): Promise<boolean> {
return this._ipcCall('isEventIndexEmpty');
}
async commitLiveEvents(): Promise<> {
return this._ipcCall('commitLiveEvents');
}
async searchEventIndex(searchConfig: SearchConfig): Promise<SearchResult> {
return this._ipcCall('searchEventIndex', searchConfig);
}
async addHistoricEvents(
events: [HistoricEvent],
checkpoint: CrawlerCheckpoint | null,
oldCheckpoint: CrawlerCheckpoint | null,
): Promise<> {
return this._ipcCall('addHistoricEvents', events, checkpoint, oldCheckpoint);
}
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
return this._ipcCall('addCrawlerCheckpoint', checkpoint);
}
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
return this._ipcCall('removeCrawlerCheckpoint', checkpoint);
}
async loadFileEvents(args): Promise<[EventAndProfile]> {
return this._ipcCall('loadFileEvents', args);
}
async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
return this._ipcCall('loadCheckpoints');
}
async closeEventIndex(): Promise<> {
return this._ipcCall('closeEventIndex');
}
async getStats(): Promise<> {
return this._ipcCall('getStats');
}
async deleteEventIndex(): Promise<> {
return this._ipcCall('deleteEventIndex');
}
}
export default class ElectronPlatform extends VectorBasePlatform {
constructor() {
super();
this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
this.eventIndexManager = new SeshatIndexManager();
dis.register(_onAction);
/*
@@ -101,6 +208,10 @@ export default class ElectronPlatform extends VectorBasePlatform {
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
}
async getConfig(): Promise<{}> {
return this._ipcCall('getConfig');
}
async onUpdateDownloaded(ev, updateInfo) {
dis.dispatch({
action: 'new_version',
@@ -169,7 +280,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
}
async getAppVersion(): Promise<string> {
return await this._ipcCall('getAppVersion');
return this._ipcCall('getAppVersion');
}
supportsAutoLaunch(): boolean {
@@ -177,23 +288,37 @@ export default class ElectronPlatform extends VectorBasePlatform {
}
async getAutoLaunchEnabled(): boolean {
return await this._ipcCall('getAutoLaunchEnabled');
return this._ipcCall('getAutoLaunchEnabled');
}
async setAutoLaunchEnabled(enabled: boolean): void {
return await this._ipcCall('setAutoLaunchEnabled', enabled);
return this._ipcCall('setAutoLaunchEnabled', enabled);
}
supportsAutoHideMenuBar(): boolean {
// This is irelevant on Mac as Menu bars don't live in the app window
return !navigator.platform.toUpperCase().includes('MAC');
}
async getAutoHideMenuBarEnabled(): boolean {
return this._ipcCall('getAutoHideMenuBarEnabled');
}
async setAutoHideMenuBarEnabled(enabled: boolean): void {
return this._ipcCall('setAutoHideMenuBarEnabled', enabled);
}
supportsMinimizeToTray(): boolean {
return true;
// Things other than Mac support tray icons
return !navigator.platform.toUpperCase().includes('MAC');
}
async getMinimizeToTrayEnabled(): boolean {
return await this._ipcCall('getMinimizeToTrayEnabled');
return this._ipcCall('getMinimizeToTrayEnabled');
}
async setMinimizeToTrayEnabled(enabled: boolean): void {
return await this._ipcCall('setMinimizeToTrayEnabled', enabled);
return this._ipcCall('setMinimizeToTrayEnabled', enabled);
}
async canSelfUpdate(): boolean {
@@ -234,10 +359,6 @@ export default class ElectronPlatform extends VectorBasePlatform {
window.location.reload(false);
}
async migrateFromOldOrigin() {
return this._ipcCall('origin_migrate');
}
async _ipcCall(name, ...args) {
const ipcCallId = ++this._nextIpcCallId;
return new Promise((resolve, reject) => {
@@ -266,4 +387,29 @@ export default class ElectronPlatform extends VectorBasePlatform {
callbacks.resolve(payload.reply);
}
}
getEventIndexingManager(): BaseEventIndexManager | null {
return this.eventIndexManager;
}
setLanguage(preferredLangs: string[]) {
this._ipcCall('setLanguage', preferredLangs).catch(error => {
console.log("Failed to send setLanguage IPC to Electron");
console.error(error);
});
}
getSSOCallbackUrl(hsUrl: string, isUrl: string): URL {
const url = super.getSSOCallbackUrl(hsUrl, isUrl);
url.protocol = "riot";
return url;
}
startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas") {
super.startSingleSignOn(mxClient, loginType); // this will get intercepted by electron-main will-navigate
Modal.createTrackedDialog('Electron', 'SSO', InfoDialog, {
title: _t("Go to your browser to complete Sign In"),
description: <Spinner />,
});
}
}

View File

@@ -4,6 +4,7 @@
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,9 +19,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import BasePlatform from 'matrix-react-sdk/lib/BasePlatform';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import dis from 'matrix-react-sdk/lib/dispatcher';
import BasePlatform from 'matrix-react-sdk/src/BasePlatform';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import dis from 'matrix-react-sdk/src/dispatcher';
import {getVectorConfig} from "../getconfig";
import Favico from 'favico.js';
@@ -44,6 +46,10 @@ export default class VectorBasePlatform extends BasePlatform {
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
}
async getConfig(): Promise<{}> {
return getVectorConfig();
}
getHumanReadableName(): string {
return 'Vector Base Platform'; // no translation required: only used for analytics
}
@@ -79,9 +85,29 @@ export default class VectorBasePlatform extends BasePlatform {
bgColor = "#f00";
}
this.favicon.badge(notif, {
bgColor: bgColor,
});
const doUpdate = () => {
this.favicon.badge(notif, {
bgColor: bgColor,
});
};
doUpdate();
// HACK: Workaround for Chrome 78+ and dependency incompatibility.
// The library we use doesn't appear to work in Chrome 78, likely due to their
// changes surrounding tab behaviour. Tabs went through a bit of a redesign and
// restructuring in Chrome 78, so it's not terribly surprising that the library
// doesn't work correctly. The library we use hasn't been updated in years and
// does not look easy to fix/fork ourselves - we might as well write our own that
// doesn't include animation/webcam/etc support. However, that's a bit difficult
// so for now we'll just trigger the update twice.
//
// Note that trying to reproduce the problem in isolation doesn't seem to work:
// see https://gist.github.com/turt2live/5ab87919918adbfd7cfb8f1ad10f2409 for
// an example (you'll need your own web server to host that).
if (window.chrome) {
doUpdate();
}
} catch (e) {
console.warn(`Failed to set badge count: ${e.message}`);
}
@@ -147,12 +173,4 @@ export default class VectorBasePlatform extends BasePlatform {
getDefaultDeviceDisplayName(): string {
return _t("Unknown device");
}
/**
* Migrate account data from a previous origin
* Used only for the electron app
*/
async migrateFromOldOrigin() {
return false;
}
}

View File

@@ -19,9 +19,8 @@ limitations under the License.
import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import request from 'browser-request';
import dis from 'matrix-react-sdk/lib/dispatcher.js';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import Promise from 'bluebird';
import dis from 'matrix-react-sdk/src/dispatcher.js';
import { _t } from 'matrix-react-sdk/src/languageHandler';
import url from 'url';
import UAParser from 'ua-parser-js';

View File

@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
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.
@@ -25,8 +26,9 @@ limitations under the License.
* from the rageshake.)
*/
import rageshake from "matrix-react-sdk/lib/rageshake/rageshake";
import SdkConfig from "matrix-react-sdk/lib/SdkConfig";
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(() => {
@@ -50,15 +52,17 @@ initRageshake();
global.mxSendRageshake = function(text, withLogs) {
if (withLogs === undefined) withLogs = true;
require(['matrix-react-sdk/lib/rageshake/submit-rageshake'], (s) => {
s(SdkConfig.get().bug_report_endpoint_url, {
userText: text,
sendLogs: withLogs,
progressCallback: console.log.bind(console),
}).then(() => {
console.log("Bug report sent!");
}, (err) => {
console.error(err);
});
if (!text || !text.trim()) {
console.error("Cannot send a rageshake without a message - please tell us what went wrong");
return;
}
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
userText: text,
sendLogs: withLogs,
progressCallback: console.log.bind(console),
}).then(() => {
console.log("Bug report sent!");
}, (err) => {
console.error(err);
});
};

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import qs from 'querystring';
import * as qs from 'querystring';
// We want to support some name / value pairs in the fragment
// so we're re-using query string like format