Merge branch 'develop' into wmwragg/one-to-one-chat
This commit is contained in:
commit
9c0f51fb82
15 changed files with 285 additions and 112 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
||||||
|
Changes in [0.6.5-r3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.5-r3) (2016-09-02)
|
||||||
|
=========================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.5-r2...v0.6.5-r3)
|
||||||
|
|
||||||
|
* revert accidental debug logging >:(
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [0.6.5-r2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.5-r2) (2016-09-02)
|
||||||
|
=========================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.5-r1...v0.6.5-r2)
|
||||||
|
|
||||||
|
* Workaround vector-im/vector-web#2020 where floods of joins could crash the browser
|
||||||
|
(as seen in #matrix-dev right now)
|
||||||
|
|
||||||
|
Changes in [0.6.5-r1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.5-r1) (2016-09-01)
|
||||||
|
=========================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.5...v0.6.5-r1)
|
||||||
|
|
||||||
|
* Fix guest access
|
||||||
|
|
||||||
Changes in [0.6.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.5) (2016-08-28)
|
Changes in [0.6.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.5) (2016-08-28)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.4-r1...v0.6.5)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.4-r1...v0.6.5)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.6.5",
|
"version": "0.6.5-r3",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -197,15 +197,31 @@ function _restoreFromLocalStorage() {
|
||||||
|
|
||||||
if (access_token && user_id && hs_url) {
|
if (access_token && user_id && hs_url) {
|
||||||
console.log("Restoring session for %s", user_id);
|
console.log("Restoring session for %s", user_id);
|
||||||
setLoggedIn({
|
try {
|
||||||
userId: user_id,
|
setLoggedIn({
|
||||||
deviceId: device_id,
|
userId: user_id,
|
||||||
accessToken: access_token,
|
deviceId: device_id,
|
||||||
homeserverUrl: hs_url,
|
accessToken: access_token,
|
||||||
identityServerUrl: is_url,
|
homeserverUrl: hs_url,
|
||||||
guest: is_guest,
|
identityServerUrl: is_url,
|
||||||
});
|
guest: is_guest,
|
||||||
return true;
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Unable to restore session", e);
|
||||||
|
|
||||||
|
var msg = e.message;
|
||||||
|
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
||||||
|
msg = "You need to log back in to generate end-to-end encryption keys "
|
||||||
|
+ "for this device and submit the public key to your homeserver. "
|
||||||
|
+ "This is a once off; sorry for the inconvenience.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't leak things into the new session
|
||||||
|
_clearLocalStorage();
|
||||||
|
|
||||||
|
throw new Error("Unable to restore previous session: " + msg);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("No previous session found.");
|
console.log("No previous session found.");
|
||||||
return false;
|
return false;
|
||||||
|
@ -305,22 +321,27 @@ export function startMatrixClient() {
|
||||||
* a session has been logged out / ended.
|
* a session has been logged out / ended.
|
||||||
*/
|
*/
|
||||||
export function onLoggedOut() {
|
export function onLoggedOut() {
|
||||||
if (window.localStorage) {
|
_clearLocalStorage();
|
||||||
const hsUrl = window.localStorage.getItem("mx_hs_url");
|
|
||||||
const isUrl = window.localStorage.getItem("mx_is_url");
|
|
||||||
window.localStorage.clear();
|
|
||||||
// preserve our HS & IS URLs for convenience
|
|
||||||
// N.B. we cache them in hsUrl/isUrl and can't really inline them
|
|
||||||
// as getCurrentHsUrl() may call through to localStorage.
|
|
||||||
// NB. We do clear the device ID (as well as all the settings)
|
|
||||||
if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl);
|
|
||||||
if (isUrl) window.localStorage.setItem("mx_is_url", isUrl);
|
|
||||||
}
|
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
|
|
||||||
dis.dispatch({action: 'on_logged_out'});
|
dis.dispatch({action: 'on_logged_out'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _clearLocalStorage() {
|
||||||
|
if (!window.localStorage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hsUrl = window.localStorage.getItem("mx_hs_url");
|
||||||
|
const isUrl = window.localStorage.getItem("mx_is_url");
|
||||||
|
window.localStorage.clear();
|
||||||
|
|
||||||
|
// preserve our HS & IS URLs for convenience
|
||||||
|
// N.B. we cache them in hsUrl/isUrl and can't really inline them
|
||||||
|
// as getCurrentHsUrl() may call through to localStorage.
|
||||||
|
// NB. We do clear the device ID (as well as all the settings)
|
||||||
|
if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl);
|
||||||
|
if (isUrl) window.localStorage.setItem("mx_is_url", isUrl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop all the background processes related to the current client
|
* Stop all the background processes related to the current client
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const MENTIONS_ONLY = 'mentions_only';
|
||||||
export const MUTE = 'mute';
|
export const MUTE = 'mute';
|
||||||
|
|
||||||
export function getRoomNotifsState(roomId) {
|
export function getRoomNotifsState(roomId) {
|
||||||
if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES;
|
if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES;
|
||||||
|
|
||||||
// look through the override rules for a rule affecting this room:
|
// look through the override rules for a rule affecting this room:
|
||||||
// if one exists, it will take precedence.
|
// if one exists, it will take precedence.
|
||||||
|
|
|
@ -18,9 +18,41 @@ var q = require("q");
|
||||||
var request = require('browser-request');
|
var request = require('browser-request');
|
||||||
|
|
||||||
var SdkConfig = require('./SdkConfig');
|
var SdkConfig = require('./SdkConfig');
|
||||||
|
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
|
||||||
class ScalarAuthClient {
|
class ScalarAuthClient {
|
||||||
getScalarToken(openid_token_object) {
|
|
||||||
|
constructor() {
|
||||||
|
this.scalarToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
return this.getScalarToken().then((tok) => {
|
||||||
|
this.scalarToken = tok;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCredentials() {
|
||||||
|
return this.scalarToken != null; // undef or null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a scalar_token string
|
||||||
|
getScalarToken() {
|
||||||
|
var tok = window.localStorage.getItem("mx_scalar_token");
|
||||||
|
if (tok) return q(tok);
|
||||||
|
|
||||||
|
// No saved token, so do the dance to get one. First, we
|
||||||
|
// need an openid bearer token from the HS.
|
||||||
|
return MatrixClientPeg.get().getOpenIdToken().then((token_object) => {
|
||||||
|
// Now we can send that to scalar and exchange it for a scalar token
|
||||||
|
return this.exchangeForScalarToken(token_object);
|
||||||
|
}).then((token_object) => {
|
||||||
|
window.localStorage.setItem("mx_scalar_token", token_object);
|
||||||
|
return token_object;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exchangeForScalarToken(openid_token_object) {
|
||||||
var defer = q.defer();
|
var defer = q.defer();
|
||||||
|
|
||||||
var scalar_rest_url = SdkConfig.get().integrations_rest_url;
|
var scalar_rest_url = SdkConfig.get().integrations_rest_url;
|
||||||
|
@ -43,6 +75,17 @@ class ScalarAuthClient {
|
||||||
|
|
||||||
return defer.promise;
|
return defer.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScalarInterfaceUrlForRoom(roomId) {
|
||||||
|
var url = SdkConfig.get().integrations_ui_url;
|
||||||
|
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||||
|
url += "&room_id=" + encodeURIComponent(roomId);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStarterLink(starterLinkUrl) {
|
||||||
|
return starterLinkUrl + "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ScalarAuthClient;
|
module.exports = ScalarAuthClient;
|
||||||
|
|
|
@ -62,6 +62,9 @@ function textForMemberEvent(ev) {
|
||||||
return senderName + " changed their profile picture";
|
return senderName + " changed their profile picture";
|
||||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||||
return senderName + " set a profile picture";
|
return senderName + " set a profile picture";
|
||||||
|
} else {
|
||||||
|
// hacky hack for https://github.com/vector-im/vector-web/issues/2020
|
||||||
|
return senderName + " rejoined the room.";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||||
|
|
|
@ -13,6 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import q from 'q';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
var Favico = require('favico.js');
|
var Favico = require('favico.js');
|
||||||
|
@ -164,6 +167,9 @@ module.exports = React.createClass({
|
||||||
// their mind & log back in)
|
// their mind & log back in)
|
||||||
this.guestCreds = null;
|
this.guestCreds = null;
|
||||||
|
|
||||||
|
// if the automatic session load failed, the error
|
||||||
|
this.sessionLoadError = null;
|
||||||
|
|
||||||
if (this.props.config.sync_timeline_limit) {
|
if (this.props.config.sync_timeline_limit) {
|
||||||
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
|
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
|
||||||
}
|
}
|
||||||
|
@ -191,13 +197,20 @@ module.exports = React.createClass({
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
|
|
||||||
Lifecycle.loadSession({
|
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
||||||
realQueryParams: this.props.realQueryParams,
|
// asynchronous ones.
|
||||||
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
q().then(() => {
|
||||||
enableGuest: this.props.enableGuest,
|
return Lifecycle.loadSession({
|
||||||
guestHsUrl: this.getCurrentHsUrl(),
|
realQueryParams: this.props.realQueryParams,
|
||||||
guestIsUrl: this.getCurrentIsUrl(),
|
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
||||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
enableGuest: this.props.enableGuest,
|
||||||
|
guestHsUrl: this.getCurrentHsUrl(),
|
||||||
|
guestIsUrl: this.getCurrentIsUrl(),
|
||||||
|
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Unable to load session", e);
|
||||||
|
this.sessionLoadError = e.message;
|
||||||
}).done(()=>{
|
}).done(()=>{
|
||||||
// stuff this through the dispatcher so that it happens
|
// stuff this through the dispatcher so that it happens
|
||||||
// after the on_logged_in action.
|
// after the on_logged_in action.
|
||||||
|
@ -1097,7 +1110,7 @@ module.exports = React.createClass({
|
||||||
onLoginClick={this.onLoginClick} />
|
onLoginClick={this.onLoginClick} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
var r = (
|
||||||
<Login
|
<Login
|
||||||
onLoggedIn={Lifecycle.setLoggedIn}
|
onLoggedIn={Lifecycle.setLoggedIn}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
|
@ -1110,8 +1123,16 @@ module.exports = React.createClass({
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||||
enableGuest={this.props.enableGuest}
|
enableGuest={this.props.enableGuest}
|
||||||
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
||||||
/>
|
initialErrorText={this.sessionLoadError}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// we only want to show the session load error the first time the
|
||||||
|
// Login component is rendered. This is pretty hacky but I can't
|
||||||
|
// think of another way to achieve it.
|
||||||
|
this.sessionLoadError = null;
|
||||||
|
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,12 +52,14 @@ module.exports = React.createClass({
|
||||||
// login shouldn't care how password recovery is done.
|
// login shouldn't care how password recovery is done.
|
||||||
onForgotPasswordClick: React.PropTypes.func,
|
onForgotPasswordClick: React.PropTypes.func,
|
||||||
onCancelClick: React.PropTypes.func,
|
onCancelClick: React.PropTypes.func,
|
||||||
|
|
||||||
|
initialErrorText: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: null,
|
errorText: this.props.initialErrorText,
|
||||||
loginIncorrect: false,
|
loginIncorrect: false,
|
||||||
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||||
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||||
|
@ -116,7 +118,8 @@ module.exports = React.createClass({
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
onHsUrlChanged: function(newHsUrl) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredHomeserverUrl: newHsUrl
|
enteredHomeserverUrl: newHsUrl,
|
||||||
|
errorText: null, // reset err messages
|
||||||
}, function() {
|
}, function() {
|
||||||
self._initLoginLogic(newHsUrl);
|
self._initLoginLogic(newHsUrl);
|
||||||
});
|
});
|
||||||
|
@ -125,7 +128,8 @@ module.exports = React.createClass({
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
onIsUrlChanged: function(newIsUrl) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredIdentityServerUrl: newIsUrl
|
enteredIdentityServerUrl: newIsUrl,
|
||||||
|
errorText: null, // reset err messages
|
||||||
}, function() {
|
}, function() {
|
||||||
self._initLoginLogic(null, newIsUrl);
|
self._initLoginLogic(null, newIsUrl);
|
||||||
});
|
});
|
||||||
|
@ -160,7 +164,6 @@ module.exports = React.createClass({
|
||||||
enteredHomeserverUrl: hsUrl,
|
enteredHomeserverUrl: hsUrl,
|
||||||
enteredIdentityServerUrl: isUrl,
|
enteredIdentityServerUrl: isUrl,
|
||||||
busy: true,
|
busy: true,
|
||||||
errorText: null, // reset err messages
|
|
||||||
loginIncorrect: false,
|
loginIncorrect: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,6 +23,10 @@ var linkify = require('linkifyjs');
|
||||||
var linkifyElement = require('linkifyjs/element');
|
var linkifyElement = require('linkifyjs/element');
|
||||||
var linkifyMatrix = require('../../../linkify-matrix');
|
var linkifyMatrix = require('../../../linkify-matrix');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
|
var ScalarAuthClient = require("../../../ScalarAuthClient");
|
||||||
|
var Modal = require("../../../Modal");
|
||||||
|
var SdkConfig = require('../../../SdkConfig');
|
||||||
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -176,15 +180,66 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onStarterLinkClick: function(starterLink, ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
// We need to add on our scalar token to the starter link, but we may not have one!
|
||||||
|
// In addition, we can't fetch one on click and then go to it immediately as that
|
||||||
|
// is then treated as a popup!
|
||||||
|
// We can get around this by fetching one now and showing a "confirmation dialog" (hurr hurr)
|
||||||
|
// which requires the user to click through and THEN we can open the link in a new tab because
|
||||||
|
// the window.open command occurs in the same stack frame as the onClick callback.
|
||||||
|
|
||||||
|
let integrationsEnabled = UserSettingsStore.isFeatureEnabled("integration_management");
|
||||||
|
if (!integrationsEnabled) {
|
||||||
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Integrations disabled",
|
||||||
|
description: "You need to enable the Labs option 'Integrations Management' in your Vector user settings first.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go fetch a scalar token
|
||||||
|
let scalarClient = new ScalarAuthClient();
|
||||||
|
scalarClient.connect().then(() => {
|
||||||
|
let completeUrl = scalarClient.getStarterLink(starterLink);
|
||||||
|
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
let integrationsUrl = SdkConfig.get().integrations_ui_url;
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: "Add an Integration",
|
||||||
|
description:
|
||||||
|
<div>
|
||||||
|
You are about to taken to a third-party site so you can authenticate your account for use with {integrationsUrl}.<br/>
|
||||||
|
Do you wish to continue?
|
||||||
|
</div>,
|
||||||
|
button: "Continue",
|
||||||
|
onFinished: function(confirmed) {
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let width = window.screen.width > 1024 ? 1024 : window.screen.width;
|
||||||
|
let height = window.screen.height > 800 ? 800 : window.screen.height;
|
||||||
|
let left = (window.screen.width - width) / 2;
|
||||||
|
let top = (window.screen.height - height) / 2;
|
||||||
|
window.open(completeUrl, '_blank', `height=${height}, width=${width}, top=${top}, left=${left},`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
var mxEvent = this.props.mxEvent;
|
var mxEvent = this.props.mxEvent;
|
||||||
var content = mxEvent.getContent();
|
var content = mxEvent.getContent();
|
||||||
|
|
||||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {});
|
var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {});
|
||||||
|
|
||||||
if (this.props.highlightLink) {
|
if (this.props.highlightLink) {
|
||||||
body = <a href={ this.props.highlightLink }>{ body }</a>;
|
body = <a href={ this.props.highlightLink }>{ body }</a>;
|
||||||
}
|
}
|
||||||
|
else if (content.data && typeof content.data["org.matrix.neb.starter_link"] === "string") {
|
||||||
|
body = <a href="#" onClick={ this.onStarterLinkClick.bind(this, content.data["org.matrix.neb.starter_link"]) }>{ body }</a>;
|
||||||
|
}
|
||||||
|
|
||||||
var widgets;
|
var widgets;
|
||||||
if (this.state.links.length && !this.state.widgetHidden && this.props.showUrlPreview) {
|
if (this.state.links.length && !this.state.widgetHidden && this.props.showUrlPreview) {
|
||||||
|
|
|
@ -57,12 +57,14 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onConferenceNotificationClick: function() {
|
onConferenceNotificationClick: function(ev, type) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'place_call',
|
action: 'place_call',
|
||||||
type: "video",
|
type: type,
|
||||||
room_id: this.props.room.roomId,
|
room_id: this.props.room.roomId,
|
||||||
});
|
});
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
@ -85,14 +87,20 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var conferenceCallNotification = null;
|
var conferenceCallNotification = null;
|
||||||
if (this.props.displayConfCallNotification) {
|
if (this.props.displayConfCallNotification) {
|
||||||
var supportedText;
|
var supportedText, joinText;
|
||||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||||
supportedText = " (unsupported)";
|
supportedText = " (unsupported)";
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
joinText = (<span>
|
||||||
|
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice')}} href="#">voice</a>
|
||||||
|
or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video') }} href="#">video</a>.
|
||||||
|
</span>);
|
||||||
|
|
||||||
|
}
|
||||||
conferenceCallNotification = (
|
conferenceCallNotification = (
|
||||||
<div className="mx_RoomView_ongoingConfCallNotification"
|
<div className="mx_RoomView_ongoingConfCallNotification">
|
||||||
onClick={this.onConferenceNotificationClick}>
|
Ongoing conference call{ supportedText }. { joinText }
|
||||||
Ongoing conference call {supportedText}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,43 +14,45 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class MemberDeviceInfo extends React.Component {
|
||||||
displayName: 'MemberDeviceInfo',
|
constructor(props) {
|
||||||
propTypes: {
|
super(props);
|
||||||
userId: React.PropTypes.string.isRequired,
|
this.onVerifyClick = this.onVerifyClick.bind(this);
|
||||||
device: React.PropTypes.object.isRequired,
|
this.onUnverifyClick = this.onUnverifyClick.bind(this);
|
||||||
},
|
this.onBlockClick = this.onBlockClick.bind(this);
|
||||||
|
this.onUnblockClick = this.onUnblockClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
onVerifyClick: function() {
|
onVerifyClick() {
|
||||||
MatrixClientPeg.get().setDeviceVerified(
|
MatrixClientPeg.get().setDeviceVerified(
|
||||||
this.props.userId, this.props.device.id, true
|
this.props.userId, this.props.device.deviceId, true
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
onUnverifyClick: function() {
|
onUnverifyClick() {
|
||||||
MatrixClientPeg.get().setDeviceVerified(
|
MatrixClientPeg.get().setDeviceVerified(
|
||||||
this.props.userId, this.props.device.id, false
|
this.props.userId, this.props.device.deviceId, false
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
onBlockClick: function() {
|
onBlockClick() {
|
||||||
MatrixClientPeg.get().setDeviceBlocked(
|
MatrixClientPeg.get().setDeviceBlocked(
|
||||||
this.props.userId, this.props.device.id, true
|
this.props.userId, this.props.device.deviceId, true
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
onUnblockClick: function() {
|
onUnblockClick() {
|
||||||
MatrixClientPeg.get().setDeviceBlocked(
|
MatrixClientPeg.get().setDeviceBlocked(
|
||||||
this.props.userId, this.props.device.id, false
|
this.props.userId, this.props.device.deviceId, false
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
var indicator = null, blockButton = null, verifyButton = null;
|
var indicator = null, blockButton = null, verifyButton = null;
|
||||||
if (this.props.device.blocked) {
|
if (this.props.device.isBlocked()) {
|
||||||
blockButton = (
|
blockButton = (
|
||||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
||||||
onClick={this.onUnblockClick}>
|
onClick={this.onUnblockClick}>
|
||||||
|
@ -66,7 +68,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.device.verified) {
|
if (this.props.device.isVerified()) {
|
||||||
verifyButton = (
|
verifyButton = (
|
||||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||||
onClick={this.onUnverifyClick}>
|
onClick={this.onUnverifyClick}>
|
||||||
|
@ -82,22 +84,22 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.device.blocked) {
|
if (this.props.device.isBlocked()) {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_blocked">✖</div>
|
<div className="mx_MemberDeviceInfo_blocked">Blocked</div>
|
||||||
);
|
);
|
||||||
} else if (this.props.device.verified) {
|
} else if (this.props.device.isVerified()) {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_verified">✔</div>
|
<div className="mx_MemberDeviceInfo_verified">Verified</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_unverified">?</div>
|
<div className="mx_MemberDeviceInfo_unverified">Unverified</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceName = this.props.device.display_name || this.props.device.id;
|
var deviceName = this.props.device.display_name || this.props.device.deviceId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberDeviceInfo">
|
<div className="mx_MemberDeviceInfo">
|
||||||
|
@ -107,5 +109,11 @@ module.exports = React.createClass({
|
||||||
{blockButton}
|
{blockButton}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
MemberDeviceInfo.displayName = 'MemberDeviceInfo';
|
||||||
|
MemberDeviceInfo.propTypes = {
|
||||||
|
userId: React.PropTypes.string.isRequired,
|
||||||
|
device: React.PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
|
@ -159,7 +159,7 @@ module.exports = React.createClass({
|
||||||
if (userId == this.props.member.userId) {
|
if (userId == this.props.member.userId) {
|
||||||
// no need to re-download the whole thing; just update our copy of
|
// no need to re-download the whole thing; just update our copy of
|
||||||
// the list.
|
// the list.
|
||||||
var devices = MatrixClientPeg.get().listDeviceKeys(userId);
|
var devices = MatrixClientPeg.get().getStoredDevicesForUser(userId);
|
||||||
this.setState({devices: devices});
|
this.setState({devices: devices});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -195,7 +195,7 @@ module.exports = React.createClass({
|
||||||
// we got cancelled - presumably a different user now
|
// we got cancelled - presumably a different user now
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var devices = client.listDeviceKeys(member.userId);
|
var devices = client.getStoredDevicesForUser(member.userId);
|
||||||
self.setState({devicesLoading: false, devices: devices});
|
self.setState({devicesLoading: false, devices: devices});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.log("Error downloading devices", err);
|
console.log("Error downloading devices", err);
|
||||||
|
|
|
@ -65,7 +65,6 @@ module.exports = React.createClass({
|
||||||
// Default to false if it's undefined, otherwise react complains about changing
|
// Default to false if it's undefined, otherwise react complains about changing
|
||||||
// components from uncontrolled to controlled
|
// components from uncontrolled to controlled
|
||||||
isRoomPublished: this._originalIsRoomPublished || false,
|
isRoomPublished: this._originalIsRoomPublished || false,
|
||||||
scalar_token: null,
|
|
||||||
scalar_error: null,
|
scalar_error: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -81,11 +80,16 @@ module.exports = React.createClass({
|
||||||
console.error("Failed to get room visibility: " + err);
|
console.error("Failed to get room visibility: " + err);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getScalarToken().done((token) => {
|
if (UserSettingsStore.isFeatureEnabled("integration_management")) {
|
||||||
this.setState({scalar_token: token});
|
this.scalarClient = new ScalarAuthClient();
|
||||||
}, (err) => {
|
this.scalarClient.connect().done(() => {
|
||||||
this.setState({scalar_error: err});
|
this.forceUpdate();
|
||||||
});
|
}, (err) => {
|
||||||
|
this.setState({
|
||||||
|
scalar_error: err
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'ui_opacity',
|
action: 'ui_opacity',
|
||||||
|
@ -249,7 +253,7 @@ module.exports = React.createClass({
|
||||||
var roomId = this.props.room.roomId;
|
var roomId = this.props.room.roomId;
|
||||||
return MatrixClientPeg.get().sendStateEvent(
|
return MatrixClientPeg.get().sendStateEvent(
|
||||||
roomId, "m.room.encryption",
|
roomId, "m.room.encryption",
|
||||||
{ algorithm: "m.olm.v1.curve25519-aes-sha2" }
|
{ algorithm: "m.megolm.v1.aes-sha2" }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -395,34 +399,13 @@ module.exports = React.createClass({
|
||||||
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
|
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
|
||||||
},
|
},
|
||||||
|
|
||||||
getScalarInterfaceUrl: function() {
|
|
||||||
var url = SdkConfig.get().integrations_ui_url;
|
|
||||||
url += "?scalar_token=" + encodeURIComponent(this.state.scalar_token);
|
|
||||||
url += "&room_id=" + encodeURIComponent(this.props.room.roomId);
|
|
||||||
return url;
|
|
||||||
},
|
|
||||||
|
|
||||||
getScalarToken() {
|
|
||||||
var tok = window.localStorage.getItem("mx_scalar_token");
|
|
||||||
if (tok) return q(tok);
|
|
||||||
|
|
||||||
// No saved token, so do the dance to get one. First, we
|
|
||||||
// need an openid bearer token from the HS.
|
|
||||||
return MatrixClientPeg.get().getOpenIdToken().then((token_object) => {
|
|
||||||
// Now we can send that to scalar and exchange it for a scalar token
|
|
||||||
var scalar_auth_client = new ScalarAuthClient();
|
|
||||||
return scalar_auth_client.getScalarToken(token_object);
|
|
||||||
}).then((token_object) => {
|
|
||||||
window.localStorage.setItem("mx_scalar_token", token_object);
|
|
||||||
return token_object;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onManageIntegrations(ev) {
|
onManageIntegrations(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||||
Modal.createDialog(IntegrationsManager, {
|
Modal.createDialog(IntegrationsManager, {
|
||||||
src: this.state.scalar_token ? this.getScalarInterfaceUrl() : null
|
src: this.scalarClient.hasCredentials() ?
|
||||||
|
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
|
||||||
|
null
|
||||||
}, "");
|
}, "");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -649,7 +632,7 @@ module.exports = React.createClass({
|
||||||
if (UserSettingsStore.isFeatureEnabled("integration_management")) {
|
if (UserSettingsStore.isFeatureEnabled("integration_management")) {
|
||||||
let integrations_body;
|
let integrations_body;
|
||||||
|
|
||||||
if (this.state.scalar_token) {
|
if (this.scalarClient.hasCredentials()) {
|
||||||
integrations_body = (
|
integrations_body = (
|
||||||
<div className="mx_RoomSettings_settings">
|
<div className="mx_RoomSettings_settings">
|
||||||
<a href="#" onClick={ this.onManageIntegrations }>Manage integrations</a>
|
<a href="#" onClick={ this.onManageIntegrations }>Manage integrations</a>
|
||||||
|
|
|
@ -95,8 +95,10 @@ module.exports = React.createClass({
|
||||||
if (call) {
|
if (call) {
|
||||||
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
||||||
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
||||||
// give a separate element for audio stream playback - both for voice calls
|
// always use a separate element for audio stream playback.
|
||||||
// and for the voice stream of screen captures
|
// this is to let us move CallView around the DOM without interrupting remote audio
|
||||||
|
// during playback, by having the audio rendered by a top-level <audio/> element.
|
||||||
|
// rather than being rendered by the main remoteVideo <video/> element.
|
||||||
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
|
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
|
||||||
}
|
}
|
||||||
if (call && call.type === "video" && call.call_state !== "ended" && call.call_state !== "ringing") {
|
if (call && call.type === "video" && call.call_state !== "ended" && call.call_state !== "ringing") {
|
||||||
|
|
|
@ -50,7 +50,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getRemoteAudioElement: function() {
|
getRemoteAudioElement: function() {
|
||||||
return this.refs.remoteAudio;
|
// this needs to be somewhere at the top of the DOM which
|
||||||
|
// always exists to avoid audio interruptions.
|
||||||
|
// Might as well just use DOM.
|
||||||
|
var remoteAudioElement = document.getElementById("remoteAudio");
|
||||||
|
if (!remoteAudioElement) {
|
||||||
|
console.error("Failed to find remoteAudio element - cannot play audio! You need to add an <audio/> to the DOM.");
|
||||||
|
}
|
||||||
|
return remoteAudioElement;
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalVideoElement: function() {
|
getLocalVideoElement: function() {
|
||||||
|
@ -106,7 +113,6 @@ module.exports = React.createClass({
|
||||||
<div className="mx_VideoView_remoteVideoFeed">
|
<div className="mx_VideoView_remoteVideoFeed">
|
||||||
<VideoFeed ref="remote" onResize={this.props.onResize}
|
<VideoFeed ref="remote" onResize={this.props.onResize}
|
||||||
maxHeight={maxVideoHeight} />
|
maxHeight={maxVideoHeight} />
|
||||||
<audio ref="remoteAudio"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_VideoView_localVideoFeed">
|
<div className="mx_VideoView_localVideoFeed">
|
||||||
<VideoFeed ref="local"/>
|
<VideoFeed ref="local"/>
|
||||||
|
|
Loading…
Reference in a new issue