Merge branch 'develop' into matthew/warn-unknown-devices
This commit is contained in:
commit
c09d173415
20 changed files with 416 additions and 176 deletions
|
@ -53,7 +53,11 @@ module.exports = {
|
||||||
* things that are errors in the js-sdk config that the current
|
* things that are errors in the js-sdk config that the current
|
||||||
* code does not adhere to, turned down to warn
|
* code does not adhere to, turned down to warn
|
||||||
*/
|
*/
|
||||||
"max-len": ["warn"],
|
"max-len": ["warn", {
|
||||||
|
// apparently people believe the length limit shouldn't apply
|
||||||
|
// to JSX.
|
||||||
|
ignorePattern: '^\\s*<',
|
||||||
|
}],
|
||||||
"valid-jsdoc": ["warn"],
|
"valid-jsdoc": ["warn"],
|
||||||
"new-cap": ["warn"],
|
"new-cap": ["warn"],
|
||||||
"key-spacing": ["warn"],
|
"key-spacing": ["warn"],
|
||||||
|
|
|
@ -165,6 +165,14 @@ module.exports = function (config) {
|
||||||
},
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
webpackMiddleware: {
|
||||||
|
stats: {
|
||||||
|
// don't fill the console up with a mahoosive list of modules
|
||||||
|
chunks: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
browserNoActivityTimeout: 15000,
|
browserNoActivityTimeout: 15000,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
80
src/RtsClient.js
Normal file
80
src/RtsClient.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
|
function checkStatus(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
return response.text().then((text) => {
|
||||||
|
throw new Error(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJson(response) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeQueryParams(params) {
|
||||||
|
return '?' + Object.keys(params).map((k) => {
|
||||||
|
return k + '=' + encodeURIComponent(params[k]);
|
||||||
|
}).join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = (url, opts) => {
|
||||||
|
if (opts && opts.qs) {
|
||||||
|
url += encodeQueryParams(opts.qs);
|
||||||
|
delete opts.qs;
|
||||||
|
}
|
||||||
|
if (opts && opts.body) {
|
||||||
|
if (!opts.headers) {
|
||||||
|
opts.headers = {};
|
||||||
|
}
|
||||||
|
opts.body = JSON.stringify(opts.body);
|
||||||
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
return fetch(url, opts)
|
||||||
|
.then(checkStatus)
|
||||||
|
.then(parseJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default class RtsClient {
|
||||||
|
constructor(url) {
|
||||||
|
this._url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTeamsConfig() {
|
||||||
|
return request(this._url + '/teams');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track a referral with the Riot Team Server. This should be called once a referred
|
||||||
|
* user has been successfully registered.
|
||||||
|
* @param {string} referrer the user ID of one who referred the user to Riot.
|
||||||
|
* @param {string} userId the user ID of the user being referred.
|
||||||
|
* @param {string} userEmail the email address linked to `userId`.
|
||||||
|
* @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
|
||||||
|
* success.
|
||||||
|
*/
|
||||||
|
trackReferral(referrer, userId, userEmail) {
|
||||||
|
return request(this._url + '/register',
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
referrer: referrer,
|
||||||
|
user_id: userId,
|
||||||
|
user_email: userEmail,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTeam(teamToken) {
|
||||||
|
return request(this._url + '/teamConfiguration',
|
||||||
|
{
|
||||||
|
qs: {
|
||||||
|
team_token: teamToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ export default React.createClass({
|
||||||
return this.props.matrixClient.exportRoomKeys();
|
return this.props.matrixClient.exportRoomKeys();
|
||||||
}).then((k) => {
|
}).then((k) => {
|
||||||
return MegolmExportEncryption.encryptMegolmKeyFile(
|
return MegolmExportEncryption.encryptMegolmKeyFile(
|
||||||
JSON.stringify(k), passphrase
|
JSON.stringify(k), passphrase,
|
||||||
);
|
);
|
||||||
}).then((f) => {
|
}).then((f) => {
|
||||||
const blob = new Blob([f], {
|
const blob = new Blob([f], {
|
||||||
|
@ -95,9 +95,14 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onCancelClick: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.props.onFinished(false);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
|
||||||
|
|
||||||
const disableForm = (this.state.phase === PHASE_EXPORTING);
|
const disableForm = (this.state.phase === PHASE_EXPORTING);
|
||||||
|
|
||||||
|
@ -159,10 +164,9 @@ export default React.createClass({
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Export'
|
<input className='mx_Dialog_primary' type='submit' value='Export'
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton element='button' onClick={this.props.onFinished}
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
disabled={disableForm}>
|
|
||||||
Cancel
|
Cancel
|
||||||
</AccessibleButton>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default React.createClass({
|
||||||
|
|
||||||
return readFileAsArrayBuffer(file).then((arrayBuffer) => {
|
return readFileAsArrayBuffer(file).then((arrayBuffer) => {
|
||||||
return MegolmExportEncryption.decryptMegolmKeyFile(
|
return MegolmExportEncryption.decryptMegolmKeyFile(
|
||||||
arrayBuffer, passphrase
|
arrayBuffer, passphrase,
|
||||||
);
|
);
|
||||||
}).then((keys) => {
|
}).then((keys) => {
|
||||||
return this.props.matrixClient.importRoomKeys(JSON.parse(keys));
|
return this.props.matrixClient.importRoomKeys(JSON.parse(keys));
|
||||||
|
@ -98,9 +98,14 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onCancelClick: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.props.onFinished(false);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
|
||||||
|
|
||||||
const disableForm = (this.state.phase !== PHASE_EDIT);
|
const disableForm = (this.state.phase !== PHASE_EDIT);
|
||||||
|
|
||||||
|
@ -158,10 +163,9 @@ export default React.createClass({
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Import'
|
<input className='mx_Dialog_primary' type='submit' value='Import'
|
||||||
disabled={!this.state.enableSubmit || disableForm}
|
disabled={!this.state.enableSubmit || disableForm}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton element='button' onClick={this.props.onFinished}
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
disabled={disableForm}>
|
|
||||||
Cancel
|
Cancel
|
||||||
</AccessibleButton>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -171,6 +171,7 @@ export default React.createClass({
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
collapsedRhs={this.props.collapse_rhs}
|
||||||
enableLabs={this.props.config.enableLabs}
|
enableLabs={this.props.config.enableLabs}
|
||||||
|
referralBaseUrl={this.props.config.referralBaseUrl}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1055,12 +1055,13 @@ module.exports = React.createClass({
|
||||||
sessionId={this.state.register_session_id}
|
sessionId={this.state.register_session_id}
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingFragmentQueryParams.email}
|
email={this.props.startingFragmentQueryParams.email}
|
||||||
|
referrer={this.props.startingFragmentQueryParams.referrer}
|
||||||
username={this.state.upgradeUsername}
|
username={this.state.upgradeUsername}
|
||||||
guestAccessToken={this.state.guestAccessToken}
|
guestAccessToken={this.state.guestAccessToken}
|
||||||
defaultHsUrl={this.getDefaultHsUrl()}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
defaultIsUrl={this.getDefaultIsUrl()}
|
defaultIsUrl={this.getDefaultIsUrl()}
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
teamsConfig={this.props.config.teamsConfig}
|
teamServerConfig={this.props.config.teamServerConfig}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
registrationUrl={this.props.registrationUrl}
|
registrationUrl={this.props.registrationUrl}
|
||||||
|
|
|
@ -1332,12 +1332,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onStatusBarVisible: function() {
|
onStatusBarVisible: function() {
|
||||||
|
if (this.unmounted) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
statusBarVisible: true,
|
statusBarVisible: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onStatusBarHidden: function() {
|
onStatusBarHidden: function() {
|
||||||
|
if (this.unmounted) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
});
|
});
|
||||||
|
@ -1507,13 +1509,14 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
var statusBar;
|
var statusBar;
|
||||||
|
let isStatusAreaExpanded = true;
|
||||||
|
|
||||||
if (ContentMessages.getCurrentUploads().length > 0) {
|
if (ContentMessages.getCurrentUploads().length > 0) {
|
||||||
var UploadBar = sdk.getComponent('structures.UploadBar');
|
var UploadBar = sdk.getComponent('structures.UploadBar');
|
||||||
statusBar = <UploadBar room={this.state.room} />;
|
statusBar = <UploadBar room={this.state.room} />;
|
||||||
} else if (!this.state.searchResults) {
|
} else if (!this.state.searchResults) {
|
||||||
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
|
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
|
||||||
|
isStatusAreaExpanded = this.state.statusBarVisible;
|
||||||
statusBar = <RoomStatusBar
|
statusBar = <RoomStatusBar
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
tabComplete={this.tabComplete}
|
tabComplete={this.tabComplete}
|
||||||
|
@ -1683,7 +1686,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let statusBarAreaClass = "mx_RoomView_statusArea mx_fadable";
|
let statusBarAreaClass = "mx_RoomView_statusArea mx_fadable";
|
||||||
if (this.state.statusBarVisible) {
|
if (isStatusAreaExpanded) {
|
||||||
statusBarAreaClass += " mx_RoomView_statusArea_expanded";
|
statusBarAreaClass += " mx_RoomView_statusArea_expanded";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -570,7 +570,7 @@ module.exports = React.createClass({
|
||||||
var boundingRect = node.getBoundingClientRect();
|
var boundingRect = node.getBoundingClientRect();
|
||||||
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
||||||
|
|
||||||
debuglog("Scrolling to token '" + node.dataset.scrollToken + "'+" +
|
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
|
||||||
pixelOffset + " (delta: "+scrollDelta+")");
|
pixelOffset + " (delta: "+scrollDelta+")");
|
||||||
|
|
||||||
if(scrollDelta != 0) {
|
if(scrollDelta != 0) {
|
||||||
|
@ -582,7 +582,7 @@ module.exports = React.createClass({
|
||||||
_saveScrollState: function() {
|
_saveScrollState: function() {
|
||||||
if (this.props.stickyBottom && this.isAtBottom()) {
|
if (this.props.stickyBottom && this.isAtBottom()) {
|
||||||
this.scrollState = { stuckAtBottom: true };
|
this.scrollState = { stuckAtBottom: true };
|
||||||
debuglog("Saved scroll state", this.scrollState);
|
debuglog("ScrollPanel: Saved scroll state", this.scrollState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,12 +601,12 @@ module.exports = React.createClass({
|
||||||
trackedScrollToken: node.dataset.scrollToken,
|
trackedScrollToken: node.dataset.scrollToken,
|
||||||
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
||||||
};
|
};
|
||||||
debuglog("Saved scroll state", this.scrollState);
|
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debuglog("Unable to save scroll state: found no children in the viewport");
|
debuglog("ScrollPanel: unable to save scroll state: found no children in the viewport");
|
||||||
},
|
},
|
||||||
|
|
||||||
_restoreSavedScrollState: function() {
|
_restoreSavedScrollState: function() {
|
||||||
|
@ -640,7 +640,7 @@ module.exports = React.createClass({
|
||||||
this._lastSetScroll = scrollNode.scrollTop;
|
this._lastSetScroll = scrollNode.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
debuglog("Set scrollTop:", scrollNode.scrollTop,
|
debuglog("ScrollPanel: set scrollTop:", scrollNode.scrollTop,
|
||||||
"requested:", scrollTop,
|
"requested:", scrollTop,
|
||||||
"_lastSetScroll:", this._lastSetScroll);
|
"_lastSetScroll:", this._lastSetScroll);
|
||||||
},
|
},
|
||||||
|
|
|
@ -92,6 +92,9 @@ module.exports = React.createClass({
|
||||||
// True to show the 'labs' section of experimental features
|
// True to show the 'labs' section of experimental features
|
||||||
enableLabs: React.PropTypes.bool,
|
enableLabs: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// The base URL to use in the referral link. Defaults to window.location.origin.
|
||||||
|
referralBaseUrl: React.PropTypes.string,
|
||||||
|
|
||||||
// true if RightPanel is collapsed
|
// true if RightPanel is collapsed
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
@ -444,6 +447,27 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderReferral: function() {
|
||||||
|
const teamToken = window.localStorage.getItem('mx_team_token');
|
||||||
|
if (!teamToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof teamToken !== 'string') {
|
||||||
|
console.warn('Team token not a string');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const href = (this.props.referralBaseUrl || window.location.origin) +
|
||||||
|
`/#/register?referrer=${this._me}&team_token=${teamToken}`;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Referral</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
Refer a friend to Riot: <a href={href}>{href}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
_renderUserInterfaceSettings: function() {
|
_renderUserInterfaceSettings: function() {
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
@ -819,6 +843,8 @@ module.exports = React.createClass({
|
||||||
{accountJsx}
|
{accountJsx}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{this._renderReferral()}
|
||||||
|
|
||||||
{notification_area}
|
{notification_area}
|
||||||
|
|
||||||
{this._renderUserInterfaceSettings()}
|
{this._renderUserInterfaceSettings()}
|
||||||
|
|
|
@ -25,6 +25,7 @@ var ServerConfig = require("../../views/login/ServerConfig");
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var RegistrationForm = require("../../views/login/RegistrationForm");
|
var RegistrationForm = require("../../views/login/RegistrationForm");
|
||||||
var CaptchaForm = require("../../views/login/CaptchaForm");
|
var CaptchaForm = require("../../views/login/CaptchaForm");
|
||||||
|
var RtsClient = require("../../../RtsClient");
|
||||||
|
|
||||||
var MIN_PASSWORD_LENGTH = 6;
|
var MIN_PASSWORD_LENGTH = 6;
|
||||||
|
|
||||||
|
@ -47,23 +48,16 @@ module.exports = React.createClass({
|
||||||
defaultIsUrl: React.PropTypes.string,
|
defaultIsUrl: React.PropTypes.string,
|
||||||
brand: React.PropTypes.string,
|
brand: React.PropTypes.string,
|
||||||
email: React.PropTypes.string,
|
email: React.PropTypes.string,
|
||||||
|
referrer: React.PropTypes.string,
|
||||||
username: React.PropTypes.string,
|
username: React.PropTypes.string,
|
||||||
guestAccessToken: React.PropTypes.string,
|
guestAccessToken: React.PropTypes.string,
|
||||||
teamsConfig: React.PropTypes.shape({
|
teamServerConfig: React.PropTypes.shape({
|
||||||
// Email address to request new teams
|
// Email address to request new teams
|
||||||
supportEmail: React.PropTypes.string,
|
supportEmail: React.PropTypes.string.isRequired,
|
||||||
teams: React.PropTypes.arrayOf(React.PropTypes.shape({
|
// URL of the riot-team-server to get team configurations and track referrals
|
||||||
// The displayed name of the team
|
teamServerURL: React.PropTypes.string.isRequired,
|
||||||
"name": React.PropTypes.string,
|
|
||||||
// The suffix with which every team email address ends
|
|
||||||
"emailSuffix": React.PropTypes.string,
|
|
||||||
// The rooms to use during auto-join
|
|
||||||
"rooms": React.PropTypes.arrayOf(React.PropTypes.shape({
|
|
||||||
"id": React.PropTypes.string,
|
|
||||||
"autoJoin": React.PropTypes.bool,
|
|
||||||
})),
|
|
||||||
})).required,
|
|
||||||
}),
|
}),
|
||||||
|
teamSelected: React.PropTypes.object,
|
||||||
|
|
||||||
defaultDeviceDisplayName: React.PropTypes.string,
|
defaultDeviceDisplayName: React.PropTypes.string,
|
||||||
|
|
||||||
|
@ -75,6 +69,7 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
busy: false,
|
busy: false,
|
||||||
|
teamServerBusy: false,
|
||||||
errorText: null,
|
errorText: null,
|
||||||
// We remember the values entered by the user because
|
// We remember the values entered by the user because
|
||||||
// the registration form will be unmounted during the
|
// the registration form will be unmounted during the
|
||||||
|
@ -90,6 +85,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
this._unmounted = false;
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
// attach this to the instance rather than this.state since it isn't UI
|
// attach this to the instance rather than this.state since it isn't UI
|
||||||
this.registerLogic = new Signup.Register(
|
this.registerLogic = new Signup.Register(
|
||||||
|
@ -103,10 +99,40 @@ module.exports = React.createClass({
|
||||||
this.registerLogic.setIdSid(this.props.idSid);
|
this.registerLogic.setIdSid(this.props.idSid);
|
||||||
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
|
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
|
||||||
this.registerLogic.recheckState();
|
this.registerLogic.recheckState();
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.props.teamServerConfig &&
|
||||||
|
this.props.teamServerConfig.teamServerURL &&
|
||||||
|
!this._rtsClient
|
||||||
|
) {
|
||||||
|
this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
teamServerBusy: true,
|
||||||
|
});
|
||||||
|
// GET team configurations including domains, names and icons
|
||||||
|
this._rtsClient.getTeamsConfig().then((data) => {
|
||||||
|
const teamsConfig = {
|
||||||
|
teams: data,
|
||||||
|
supportEmail: this.props.teamServerConfig.supportEmail,
|
||||||
|
};
|
||||||
|
console.log('Setting teams config to ', teamsConfig);
|
||||||
|
this.setState({
|
||||||
|
teamsConfig: teamsConfig,
|
||||||
|
teamServerBusy: false,
|
||||||
|
});
|
||||||
|
}, (err) => {
|
||||||
|
console.error('Error retrieving config for teams', err);
|
||||||
|
this.setState({
|
||||||
|
teamServerBusy: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
this._unmounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -184,24 +210,41 @@ module.exports = React.createClass({
|
||||||
accessToken: response.access_token
|
accessToken: response.access_token
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-join rooms
|
if (
|
||||||
if (self.props.teamsConfig && self.props.teamsConfig.teams) {
|
self._rtsClient &&
|
||||||
for (let i = 0; i < self.props.teamsConfig.teams.length; i++) {
|
self.props.referrer &&
|
||||||
let team = self.props.teamsConfig.teams[i];
|
self.state.teamSelected
|
||||||
if (self.state.formVals.email.endsWith(team.emailSuffix)) {
|
) {
|
||||||
console.log("User successfully registered with team " + team.name);
|
// Track referral, get team_token in order to retrieve team config
|
||||||
|
self._rtsClient.trackReferral(
|
||||||
|
self.props.referrer,
|
||||||
|
response.user_id,
|
||||||
|
self.state.formVals.email
|
||||||
|
).then((data) => {
|
||||||
|
const teamToken = data.team_token;
|
||||||
|
// Store for use /w welcome pages
|
||||||
|
window.localStorage.setItem('mx_team_token', teamToken);
|
||||||
|
|
||||||
|
self._rtsClient.getTeam(teamToken).then((team) => {
|
||||||
|
console.log(
|
||||||
|
`User successfully registered with team ${team.name}`
|
||||||
|
);
|
||||||
if (!team.rooms) {
|
if (!team.rooms) {
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
// Auto-join rooms
|
||||||
team.rooms.forEach((room) => {
|
team.rooms.forEach((room) => {
|
||||||
if (room.autoJoin) {
|
if (room.auto_join && room.room_id) {
|
||||||
console.log("Auto-joining " + room.id);
|
console.log(`Auto-joining ${room.room_id}`);
|
||||||
MatrixClientPeg.get().joinRoom(room.id);
|
MatrixClientPeg.get().joinRoom(room.room_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
}, (err) => {
|
||||||
}
|
console.error('Error getting team config', err);
|
||||||
}
|
});
|
||||||
|
}, (err) => {
|
||||||
|
console.error('Error tracking referral', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.props.brand) {
|
if (self.props.brand) {
|
||||||
|
@ -273,7 +316,15 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onTeamSelected: function(teamSelected) {
|
||||||
|
if (!this._unmounted) {
|
||||||
|
this.setState({ teamSelected });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_getRegisterContentJsx: function() {
|
_getRegisterContentJsx: function() {
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
var currStep = this.registerLogic.getStep();
|
var currStep = this.registerLogic.getStep();
|
||||||
var registerStep;
|
var registerStep;
|
||||||
switch (currStep) {
|
switch (currStep) {
|
||||||
|
@ -283,17 +334,23 @@ module.exports = React.createClass({
|
||||||
case "Register.STEP_m.login.dummy":
|
case "Register.STEP_m.login.dummy":
|
||||||
// NB. Our 'username' prop is specifically for upgrading
|
// NB. Our 'username' prop is specifically for upgrading
|
||||||
// a guest account
|
// a guest account
|
||||||
|
if (this.state.teamServerBusy) {
|
||||||
|
registerStep = <Spinner />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
registerStep = (
|
registerStep = (
|
||||||
<RegistrationForm
|
<RegistrationForm
|
||||||
showEmail={true}
|
showEmail={true}
|
||||||
defaultUsername={this.state.formVals.username}
|
defaultUsername={this.state.formVals.username}
|
||||||
defaultEmail={this.state.formVals.email}
|
defaultEmail={this.state.formVals.email}
|
||||||
defaultPassword={this.state.formVals.password}
|
defaultPassword={this.state.formVals.password}
|
||||||
teamsConfig={this.props.teamsConfig}
|
teamsConfig={this.state.teamsConfig}
|
||||||
guestUsername={this.props.username}
|
guestUsername={this.props.username}
|
||||||
minPasswordLength={MIN_PASSWORD_LENGTH}
|
minPasswordLength={MIN_PASSWORD_LENGTH}
|
||||||
onError={this.onFormValidationFailed}
|
onError={this.onFormValidationFailed}
|
||||||
onRegisterClick={this.onFormSubmit} />
|
onRegisterClick={this.onFormSubmit}
|
||||||
|
onTeamSelected={this.onTeamSelected}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "Register.STEP_m.login.email.identity":
|
case "Register.STEP_m.login.email.identity":
|
||||||
|
@ -322,7 +379,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
var busySpinner;
|
var busySpinner;
|
||||||
if (this.state.busy) {
|
if (this.state.busy) {
|
||||||
var Spinner = sdk.getComponent("elements.Spinner");
|
|
||||||
busySpinner = (
|
busySpinner = (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
);
|
);
|
||||||
|
@ -367,7 +423,7 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_Login">
|
<div className="mx_Login">
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
<LoginHeader />
|
<LoginHeader icon={this.state.teamSelected ? this.state.teamSelected.icon : null}/>
|
||||||
{this._getRegisterContentJsx()}
|
{this._getRegisterContentJsx()}
|
||||||
<LoginFooter />
|
<LoginFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,17 +14,18 @@ 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 classNames = require('classnames');
|
import classNames from 'classnames';
|
||||||
var sdk = require("../../../index");
|
import sdk from '../../../index';
|
||||||
var Invite = require("../../../Invite");
|
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
||||||
var createRoom = require("../../../createRoom");
|
import createRoom from '../../../createRoom';
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
var DMRoomMap = require('../../../utils/DMRoomMap');
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
var rate_limited_func = require("../../../ratelimitedfunc");
|
import rate_limited_func from '../../../ratelimitedfunc';
|
||||||
var dis = require("../../../dispatcher");
|
import dis from '../../../dispatcher';
|
||||||
var Modal = require('../../../Modal');
|
import Modal from '../../../Modal';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import q from 'q';
|
||||||
|
|
||||||
const TRUNCATE_QUERY_LIST = 40;
|
const TRUNCATE_QUERY_LIST = 40;
|
||||||
|
|
||||||
|
@ -186,13 +187,17 @@ module.exports = React.createClass({
|
||||||
// If the query isn't a user we know about, but is a
|
// If the query isn't a user we know about, but is a
|
||||||
// valid address, add an entry for that
|
// valid address, add an entry for that
|
||||||
if (queryList.length == 0) {
|
if (queryList.length == 0) {
|
||||||
const addrType = Invite.getAddressType(query);
|
const addrType = getAddressType(query);
|
||||||
if (addrType !== null) {
|
if (addrType !== null) {
|
||||||
queryList.push({
|
queryList[0] = {
|
||||||
addressType: addrType,
|
addressType: addrType,
|
||||||
address: query,
|
address: query,
|
||||||
isKnown: false,
|
isKnown: false,
|
||||||
});
|
};
|
||||||
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
|
if (addrType == 'email') {
|
||||||
|
this._lookupThreepid(addrType, query).done();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,6 +217,7 @@ module.exports = React.createClass({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
});
|
});
|
||||||
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -229,6 +235,7 @@ module.exports = React.createClass({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
});
|
});
|
||||||
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
},
|
},
|
||||||
|
|
||||||
_getDirectMessageRoom: function(addr) {
|
_getDirectMessageRoom: function(addr) {
|
||||||
|
@ -266,7 +273,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.roomId) {
|
if (this.props.roomId) {
|
||||||
// Invite new user to a room
|
// Invite new user to a room
|
||||||
var self = this;
|
var self = this;
|
||||||
Invite.inviteMultipleToRoom(this.props.roomId, addrTexts)
|
inviteMultipleToRoom(this.props.roomId, addrTexts)
|
||||||
.then(function(addrs) {
|
.then(function(addrs) {
|
||||||
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
|
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
|
||||||
return self._showAnyInviteErrors(addrs, room);
|
return self._showAnyInviteErrors(addrs, room);
|
||||||
|
@ -300,7 +307,7 @@ module.exports = React.createClass({
|
||||||
var room;
|
var room;
|
||||||
createRoom().then(function(roomId) {
|
createRoom().then(function(roomId) {
|
||||||
room = MatrixClientPeg.get().getRoom(roomId);
|
room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
return Invite.inviteMultipleToRoom(roomId, addrTexts);
|
return inviteMultipleToRoom(roomId, addrTexts);
|
||||||
})
|
})
|
||||||
.then(function(addrs) {
|
.then(function(addrs) {
|
||||||
return self._showAnyInviteErrors(addrs, room);
|
return self._showAnyInviteErrors(addrs, room);
|
||||||
|
@ -380,7 +387,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_isDmChat: function(addrs) {
|
_isDmChat: function(addrs) {
|
||||||
if (addrs.length === 1 && Invite.getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
|
if (addrs.length === 1 && getAddressType(addrs[0]) === "mx" && !this.props.roomId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -408,7 +415,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_addInputToList: function() {
|
_addInputToList: function() {
|
||||||
const addressText = this.refs.textinput.value.trim();
|
const addressText = this.refs.textinput.value.trim();
|
||||||
const addrType = Invite.getAddressType(addressText);
|
const addrType = getAddressType(addressText);
|
||||||
const addrObj = {
|
const addrObj = {
|
||||||
addressType: addrType,
|
addressType: addrType,
|
||||||
address: addressText,
|
address: addressText,
|
||||||
|
@ -432,9 +439,45 @@ module.exports = React.createClass({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
});
|
});
|
||||||
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
return inviteList;
|
return inviteList;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_lookupThreepid: function(medium, address) {
|
||||||
|
let cancelled = false;
|
||||||
|
// Note that we can't safely remove this after we're done
|
||||||
|
// because we don't know that it's the same one, so we just
|
||||||
|
// leave it: it's replacing the old one each time so it's
|
||||||
|
// not like they leak.
|
||||||
|
this._cancelThreepidLookup = function() {
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait a bit to let the user finish typing
|
||||||
|
return q.delay(500).then(() => {
|
||||||
|
if (cancelled) return null;
|
||||||
|
return MatrixClientPeg.get().lookupThreePid(medium, address);
|
||||||
|
}).then((res) => {
|
||||||
|
if (res === null || !res.mxid) return null;
|
||||||
|
if (cancelled) return null;
|
||||||
|
|
||||||
|
return MatrixClientPeg.get().getProfileInfo(res.mxid);
|
||||||
|
}).then((res) => {
|
||||||
|
if (res === null) return null;
|
||||||
|
if (cancelled) return null;
|
||||||
|
this.setState({
|
||||||
|
queryList: [{
|
||||||
|
// an InviteAddressType
|
||||||
|
addressType: medium,
|
||||||
|
address: address,
|
||||||
|
displayName: res.displayname,
|
||||||
|
avatarMxc: res.avatar_url,
|
||||||
|
isKnown: true,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
||||||
|
|
|
@ -94,14 +94,14 @@ export default React.createClass({
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
let info;
|
|
||||||
let error = false;
|
|
||||||
if (address.addressType === "mx" && address.isKnown) {
|
|
||||||
const nameClasses = classNames({
|
const nameClasses = classNames({
|
||||||
"mx_AddressTile_name": true,
|
"mx_AddressTile_name": true,
|
||||||
"mx_AddressTile_justified": this.props.justified,
|
"mx_AddressTile_justified": this.props.justified,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let info;
|
||||||
|
let error = false;
|
||||||
|
if (address.addressType === "mx" && address.isKnown) {
|
||||||
const idClasses = classNames({
|
const idClasses = classNames({
|
||||||
"mx_AddressTile_id": true,
|
"mx_AddressTile_id": true,
|
||||||
"mx_AddressTile_justified": this.props.justified,
|
"mx_AddressTile_justified": this.props.justified,
|
||||||
|
@ -123,13 +123,21 @@ export default React.createClass({
|
||||||
<div className={unknownMxClasses}>{ this.props.address.address }</div>
|
<div className={unknownMxClasses}>{ this.props.address.address }</div>
|
||||||
);
|
);
|
||||||
} else if (address.addressType === "email") {
|
} else if (address.addressType === "email") {
|
||||||
var emailClasses = classNames({
|
const emailClasses = classNames({
|
||||||
"mx_AddressTile_email": true,
|
"mx_AddressTile_email": true,
|
||||||
"mx_AddressTile_justified": this.props.justified,
|
"mx_AddressTile_justified": this.props.justified,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let nameNode = null;
|
||||||
|
if (address.displayName) {
|
||||||
|
nameNode = <div className={nameClasses}>{ address.displayName }</div>
|
||||||
|
}
|
||||||
|
|
||||||
info = (
|
info = (
|
||||||
|
<div className="mx_AddressTile_mx">
|
||||||
<div className={emailClasses}>{ address.address }</div>
|
<div className={emailClasses}>{ address.address }</div>
|
||||||
|
{nameNode}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
error = true;
|
error = true;
|
||||||
|
|
|
@ -44,8 +44,8 @@ module.exports = React.createClass({
|
||||||
teams: React.PropTypes.arrayOf(React.PropTypes.shape({
|
teams: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
// The displayed name of the team
|
// The displayed name of the team
|
||||||
"name": React.PropTypes.string,
|
"name": React.PropTypes.string,
|
||||||
// The suffix with which every team email address ends
|
// The domain of team email addresses
|
||||||
"emailSuffix": React.PropTypes.string,
|
"domain": React.PropTypes.string,
|
||||||
})).required,
|
})).required,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -117,9 +117,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_doSubmit: function() {
|
_doSubmit: function() {
|
||||||
let email = this.refs.email.value.trim();
|
let email = this.refs.email.value.trim();
|
||||||
if (this.state.selectedTeam) {
|
|
||||||
email += "@" + this.state.selectedTeam.emailSuffix;
|
|
||||||
}
|
|
||||||
var promise = this.props.onRegisterClick({
|
var promise = this.props.onRegisterClick({
|
||||||
username: this.refs.username.value.trim() || this.props.guestUsername,
|
username: this.refs.username.value.trim() || this.props.guestUsername,
|
||||||
password: this.refs.password.value.trim(),
|
password: this.refs.password.value.trim(),
|
||||||
|
@ -134,25 +131,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onSelectTeam: function(teamIndex) {
|
|
||||||
let team = this._getSelectedTeam(teamIndex);
|
|
||||||
if (team) {
|
|
||||||
this.refs.email.value = this.refs.email.value.split("@")[0];
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
selectedTeam: team,
|
|
||||||
showSupportEmail: teamIndex === "other",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_getSelectedTeam: function(teamIndex) {
|
|
||||||
if (this.props.teamsConfig &&
|
|
||||||
this.props.teamsConfig.teams[teamIndex]) {
|
|
||||||
return this.props.teamsConfig.teams[teamIndex];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if all fields were valid last time
|
* Returns true if all fields were valid last time
|
||||||
* they were validated.
|
* they were validated.
|
||||||
|
@ -167,20 +145,36 @@ module.exports = React.createClass({
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_isUniEmail: function(email) {
|
||||||
|
return email.endsWith('.ac.uk') || email.endsWith('.edu');
|
||||||
|
},
|
||||||
|
|
||||||
validateField: function(field_id) {
|
validateField: function(field_id) {
|
||||||
var pwd1 = this.refs.password.value.trim();
|
var pwd1 = this.refs.password.value.trim();
|
||||||
var pwd2 = this.refs.passwordConfirm.value.trim();
|
var pwd2 = this.refs.passwordConfirm.value.trim();
|
||||||
|
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case FIELD_EMAIL:
|
case FIELD_EMAIL:
|
||||||
let email = this.refs.email.value;
|
const email = this.refs.email.value;
|
||||||
if (this.props.teamsConfig) {
|
if (this.props.teamsConfig && this._isUniEmail(email)) {
|
||||||
let team = this.state.selectedTeam;
|
const matchingTeam = this.props.teamsConfig.teams.find(
|
||||||
if (team) {
|
(team) => {
|
||||||
email = email + "@" + team.emailSuffix;
|
return email.split('@').pop() === team.domain;
|
||||||
}
|
}
|
||||||
|
) || null;
|
||||||
|
this.setState({
|
||||||
|
selectedTeam: matchingTeam,
|
||||||
|
showSupportEmail: !matchingTeam,
|
||||||
|
});
|
||||||
|
this.props.onTeamSelected(matchingTeam);
|
||||||
|
} else {
|
||||||
|
this.props.onTeamSelected(null);
|
||||||
|
this.setState({
|
||||||
|
selectedTeam: null,
|
||||||
|
showSupportEmail: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let valid = email === '' || Email.looksValid(email);
|
const valid = email === '' || Email.looksValid(email);
|
||||||
this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID");
|
this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID");
|
||||||
break;
|
break;
|
||||||
case FIELD_USERNAME:
|
case FIELD_USERNAME:
|
||||||
|
@ -260,61 +254,35 @@ module.exports = React.createClass({
|
||||||
return cls;
|
return cls;
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderEmailInputSuffix: function() {
|
|
||||||
let suffix = null;
|
|
||||||
if (!this.state.selectedTeam) {
|
|
||||||
return suffix;
|
|
||||||
}
|
|
||||||
let team = this.state.selectedTeam;
|
|
||||||
if (team) {
|
|
||||||
suffix = "@" + team.emailSuffix;
|
|
||||||
}
|
|
||||||
return suffix;
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var emailSection, teamSection, teamAdditionSupport, registerButton;
|
var emailSection, belowEmailSection, registerButton;
|
||||||
if (this.props.showEmail) {
|
if (this.props.showEmail) {
|
||||||
let emailSuffix = this._renderEmailInputSuffix();
|
|
||||||
emailSection = (
|
emailSection = (
|
||||||
<div>
|
|
||||||
<input type="text" ref="email"
|
<input type="text" ref="email"
|
||||||
autoFocus={true} placeholder="Email address (optional)"
|
autoFocus={true} placeholder="Email address (optional)"
|
||||||
defaultValue={this.props.defaultEmail}
|
defaultValue={this.props.defaultEmail}
|
||||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||||
value={self.state.email}/>
|
value={self.state.email}/>
|
||||||
{emailSuffix ? <input className="mx_Login_field" value={emailSuffix} disabled/> : null }
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
if (this.props.teamsConfig) {
|
if (this.props.teamsConfig) {
|
||||||
teamSection = (
|
|
||||||
<select
|
|
||||||
defaultValue="-1"
|
|
||||||
className="mx_Login_field"
|
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
|
||||||
onChange={function(ev) {self.onSelectTeam(ev.target.value);}}
|
|
||||||
>
|
|
||||||
<option key="-1" value="-1">No team</option>
|
|
||||||
{this.props.teamsConfig.teams.map((t, index) => {
|
|
||||||
return (
|
|
||||||
<option key={index} value={index}>
|
|
||||||
{t.name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<option key="-2" value="other">Other</option>
|
|
||||||
</select>
|
|
||||||
);
|
|
||||||
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
|
||||||
teamAdditionSupport = (
|
belowEmailSection = (
|
||||||
<span>
|
<p className="mx_Login_support">
|
||||||
If your team is not listed, email
|
Sorry, but your university is not registered with us just yet.
|
||||||
|
Email us on
|
||||||
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
|
<a href={"mailto:" + this.props.teamsConfig.supportEmail}>
|
||||||
{this.props.teamsConfig.supportEmail}
|
{this.props.teamsConfig.supportEmail}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
to get your university signed up. Or continue to register with Riot to enjoy our open source platform.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
} else if (this.state.selectedTeam) {
|
||||||
|
belowEmailSection = (
|
||||||
|
<p className="mx_Login_support">
|
||||||
|
You are registering with {this.state.selectedTeam.name}
|
||||||
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,11 +301,8 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
{teamSection}
|
|
||||||
{teamAdditionSupport}
|
|
||||||
<br />
|
|
||||||
{emailSection}
|
{emailSection}
|
||||||
<br />
|
{belowEmailSection}
|
||||||
<input type="text" ref="username"
|
<input type="text" ref="username"
|
||||||
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
||||||
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function decryptMegolmKeyFile(data, password) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphertextLength = body.length-(1+16+16+4+32);
|
const ciphertextLength = body.length-(1+16+16+4+32);
|
||||||
if (body.length < 0) {
|
if (ciphertextLength < 0) {
|
||||||
throw new Error('Invalid file: too short');
|
throw new Error('Invalid file: too short');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +107,14 @@ export function encryptMegolmKeyFile(data, password, options) {
|
||||||
const salt = new Uint8Array(16);
|
const salt = new Uint8Array(16);
|
||||||
window.crypto.getRandomValues(salt);
|
window.crypto.getRandomValues(salt);
|
||||||
|
|
||||||
// clear bit 63 of the salt to stop us hitting the 64-bit counter boundary
|
|
||||||
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
|
||||||
// of a single bit of salt is a price we have to pay.
|
|
||||||
salt[9] &= 0x7f;
|
|
||||||
|
|
||||||
const iv = new Uint8Array(16);
|
const iv = new Uint8Array(16);
|
||||||
window.crypto.getRandomValues(iv);
|
window.crypto.getRandomValues(iv);
|
||||||
|
|
||||||
|
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
|
||||||
|
// (which would mean we wouldn't be able to decrypt on Android). The loss
|
||||||
|
// of a single bit of iv is a price we have to pay.
|
||||||
|
iv[9] &= 0x7f;
|
||||||
|
|
||||||
return deriveKeys(salt, kdf_rounds, password).then((keys) => {
|
return deriveKeys(salt, kdf_rounds, password).then((keys) => {
|
||||||
const [aes_key, hmac_key] = keys;
|
const [aes_key, hmac_key] = keys;
|
||||||
|
|
||||||
|
|
|
@ -42,17 +42,12 @@ describe('RoomView', function () {
|
||||||
it('resolves a room alias to a room id', function (done) {
|
it('resolves a room alias to a room id', function (done) {
|
||||||
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
||||||
|
|
||||||
var onRoomIdResolved = sinon.spy();
|
function onRoomIdResolved(room_id) {
|
||||||
|
expect(room_id).toEqual("!randomcharacters:aser.ver");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(<RoomView roomAddress="#alias:ser.ver" onRoomIdResolved={onRoomIdResolved} />, parentDiv);
|
ReactDOM.render(<RoomView roomAddress="#alias:ser.ver" onRoomIdResolved={onRoomIdResolved} />, parentDiv);
|
||||||
|
|
||||||
process.nextTick(function() {
|
|
||||||
// These expect()s don't read very well and don't give very good failure
|
|
||||||
// messages, but expect's toHaveBeenCalled only takes an expect spy object,
|
|
||||||
// not a sinon spy object.
|
|
||||||
expect(onRoomIdResolved.called).toExist();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('joins by alias if given an alias', function (done) {
|
it('joins by alias if given an alias', function (done) {
|
||||||
|
|
|
@ -73,6 +73,7 @@ var Tester = React.createClass({
|
||||||
|
|
||||||
/* returns a promise which will resolve when the fill happens */
|
/* returns a promise which will resolve when the fill happens */
|
||||||
awaitFill: function(dir) {
|
awaitFill: function(dir) {
|
||||||
|
console.log("ScrollPanel Tester: awaiting " + dir + " fill");
|
||||||
var defer = q.defer();
|
var defer = q.defer();
|
||||||
this._fillDefers[dir] = defer;
|
this._fillDefers[dir] = defer;
|
||||||
return defer.promise;
|
return defer.promise;
|
||||||
|
@ -80,7 +81,7 @@ var Tester = React.createClass({
|
||||||
|
|
||||||
_onScroll: function(ev) {
|
_onScroll: function(ev) {
|
||||||
var st = ev.target.scrollTop;
|
var st = ev.target.scrollTop;
|
||||||
console.log("Scroll event; scrollTop: " + st);
|
console.log("ScrollPanel Tester: scroll event; scrollTop: " + st);
|
||||||
this.lastScrollEvent = st;
|
this.lastScrollEvent = st;
|
||||||
|
|
||||||
var d = this._scrollDefer;
|
var d = this._scrollDefer;
|
||||||
|
@ -159,10 +160,29 @@ describe('ScrollPanel', function() {
|
||||||
scrollingDiv = ReactTestUtils.findRenderedDOMComponentWithClass(
|
scrollingDiv = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||||
tester, "gm-scroll-view");
|
tester, "gm-scroll-view");
|
||||||
|
|
||||||
// wait for a browser tick to let the initial paginates complete
|
// we need to make sure we don't call done() until q has finished
|
||||||
setTimeout(function() {
|
// running the completion handlers from the fill requests. We can't
|
||||||
done();
|
// just use .done(), because that will end up ahead of those handlers
|
||||||
}, 0);
|
// in the queue. We can't use window.setTimeout(0), because that also might
|
||||||
|
// run ahead of those handlers.
|
||||||
|
const sp = tester.scrollPanel();
|
||||||
|
let retriesRemaining = 1;
|
||||||
|
const awaitReady = function() {
|
||||||
|
return q().then(() => {
|
||||||
|
if (sp._pendingFillRequests.b === false &&
|
||||||
|
sp._pendingFillRequests.f === false
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retriesRemaining == 0) {
|
||||||
|
throw new Error("fillRequests did not complete");
|
||||||
|
}
|
||||||
|
retriesRemaining--;
|
||||||
|
return awaitReady();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
awaitReady().done(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
|
|
@ -99,7 +99,11 @@ describe('TimelinePanel', function() {
|
||||||
// the document so that we can interact with it properly.
|
// the document so that we can interact with it properly.
|
||||||
parentDiv = document.createElement('div');
|
parentDiv = document.createElement('div');
|
||||||
parentDiv.style.width = '800px';
|
parentDiv.style.width = '800px';
|
||||||
parentDiv.style.height = '600px';
|
|
||||||
|
// This has to be slightly carefully chosen. We expect to have to do
|
||||||
|
// exactly one pagination to fill it.
|
||||||
|
parentDiv.style.height = '500px';
|
||||||
|
|
||||||
parentDiv.style.overflow = 'hidden';
|
parentDiv.style.overflow = 'hidden';
|
||||||
document.body.appendChild(parentDiv);
|
document.body.appendChild(parentDiv);
|
||||||
});
|
});
|
||||||
|
@ -235,7 +239,7 @@ describe('TimelinePanel', function() {
|
||||||
expect(client.paginateEventTimeline.callCount).toEqual(0);
|
expect(client.paginateEventTimeline.callCount).toEqual(0);
|
||||||
done();
|
done();
|
||||||
}, 0);
|
}, 0);
|
||||||
}, 0);
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should let you scroll down to the bottom after you've scrolled up", function(done) {
|
it("should let you scroll down to the bottom after you've scrolled up", function(done) {
|
||||||
|
|
|
@ -14,7 +14,15 @@ var MatrixEvent = jssdk.MatrixEvent;
|
||||||
*/
|
*/
|
||||||
export function beforeEach(context) {
|
export function beforeEach(context) {
|
||||||
var desc = context.currentTest.fullTitle();
|
var desc = context.currentTest.fullTitle();
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
|
// this puts a mark in the chrome devtools timeline, which can help
|
||||||
|
// figure out what's been going on.
|
||||||
|
if (console.timeStamp) {
|
||||||
|
console.timeStamp(desc);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(desc);
|
console.log(desc);
|
||||||
console.log(new Array(1 + desc.length).join("="));
|
console.log(new Array(1 + desc.length).join("="));
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,6 +75,16 @@ describe('MegolmExportEncryption', function() {
|
||||||
.toThrow('Trailer line not found');
|
.toThrow('Trailer line not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle a too-short body', function() {
|
||||||
|
const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
|
||||||
|
AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx
|
||||||
|
cissyYBxjsfsAn
|
||||||
|
-----END MEGOLM SESSION DATA-----
|
||||||
|
`);
|
||||||
|
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
|
||||||
|
.toThrow('Invalid file: too short');
|
||||||
|
});
|
||||||
|
|
||||||
it('should decrypt a range of inputs', function(done) {
|
it('should decrypt a range of inputs', function(done) {
|
||||||
function next(i) {
|
function next(i) {
|
||||||
if (i >= TEST_VECTORS.length) {
|
if (i >= TEST_VECTORS.length) {
|
||||||
|
|
Loading…
Reference in a new issue