From e32c325863b2ef3091549927c616d850b8e3bc54 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 11 Aug 2016 13:50:38 +0100 Subject: [PATCH 01/31] Don't use MatrixClientPeg for temporary clients Get rid of MatrixClientPeg.replaceUsingUrls, and instead create local, temporary MatrixClients for the unauthed steps; we therefore only use MatrixClientPeg for logged-in clients. --- src/Lifecycle.js | 21 ++++-- src/MatrixClientPeg.js | 32 ++------- src/Signup.js | 87 +++++++++++-------------- src/components/structures/MatrixChat.js | 18 +---- 4 files changed, 61 insertions(+), 97 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index bc7d24c707..0a2a503ce6 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -142,8 +142,12 @@ function _loginWithToken(queryParams) { function _registerAsGuest(hsUrl, isUrl) { console.log("Doing guest login on %s", hsUrl); - MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl); - return MatrixClientPeg.get().registerGuest().then((creds) => { + // create a temporary MatrixClient to do the login + var client = Matrix.createClient({ + baseUrl: hsUrl, + }); + + return client.registerGuest().then((creds) => { console.log("Registered as guest: %s", creds.user_id); setLoggedIn({ userId: creds.user_id, @@ -286,7 +290,7 @@ export function onLoggedOut() { 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'}); } @@ -294,11 +298,14 @@ export function onLoggedOut() { /** * Stop all the background processes related to the current client */ -function _stopMatrixClient() { +export function stopMatrixClient() { Notifier.stop(); UserActivity.stop(); Presence.stop(); - MatrixClientPeg.get().stopClient(); - MatrixClientPeg.get().removeAllListeners(); - MatrixClientPeg.unset(); + var cli = MatrixClientPeg.get(); + if (cli) { + cli.stopClient(); + cli.removeAllListeners(); + MatrixClientPeg.unset(); + } } diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 4fefc885af..e22990a850 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -67,26 +67,12 @@ class MatrixClientPeg { this.matrixClient = null; } - /** - * Replace this MatrixClientPeg's client with a client instance that has - * Home Server / Identity Server URLs but no credentials - */ - replaceUsingUrls(hs_url, is_url) { - this._replaceClient(hs_url, is_url); - } - /** * Replace this MatrixClientPeg's client with a client instance that has * Home Server / Identity Server URLs and active credentials */ replaceUsingCreds(creds: MatrixClientCreds) { - this._replaceClient( - creds.homeserverUrl, - creds.identityServerUrl, - creds.userId, - creds.accessToken, - creds.guest, - ); + this._createClient(creds); } start() { @@ -96,10 +82,6 @@ class MatrixClientPeg { this.get().startClient(opts); } - _replaceClient(hs_url, is_url, user_id, access_token, isGuest) { - this._createClient(hs_url, is_url, user_id, access_token, isGuest); - } - getCredentials(): MatrixClientCreds { return { homeserverUrl: this.matrixClient.baseUrl, @@ -110,12 +92,12 @@ class MatrixClientPeg { }; } - _createClient(hs_url, is_url, user_id, access_token, isGuest) { + _createClient(creds: MatrixClientCreds) { var opts = { - baseUrl: hs_url, - idBaseUrl: is_url, - accessToken: access_token, - userId: user_id, + baseUrl: creds.homeserverUrl, + idBaseUrl: creds.identityServerUrl, + accessToken: creds.accessToken, + userId: creds.userId, timelineSupport: true, }; @@ -130,7 +112,7 @@ class MatrixClientPeg { // potential number of event listeners is quite high. this.matrixClient.setMaxListeners(500); - this.matrixClient.setGuest(Boolean(isGuest)); + this.matrixClient.setGuest(Boolean(creds.guest)); } } diff --git a/src/Signup.js b/src/Signup.js index 7a46005d11..aa0aee597c 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -1,4 +1,7 @@ "use strict"; + +import Matrix from "matrix-js-sdk"; + var MatrixClientPeg = require("./MatrixClientPeg"); var SignupStages = require("./SignupStages"); var dis = require("./dispatcher"); @@ -31,6 +34,17 @@ class Signup { setIdentityServerUrl(isUrl) { this._isUrl = isUrl; } + + /** + * Get a temporary MatrixClient, which can be used for login or register + * requests. + */ + _createTemporaryClient() { + return Matrix.createClient({ + baseUrl: this._hsUrl, + idBaseUrl: this._isUrl, + }); + } } /** @@ -106,19 +120,11 @@ class Register extends Signup { this.email = email; this.username = username; this.password = password; - - // feels a bit wrong to be clobbering the global client for something we - // don't even know if it'll work, but we'll leave this here for now to - // not complicate matters further. It would be nicer to isolate this - // logic entirely from the rest of the app though. - MatrixClientPeg.replaceUsingUrls( - this._hsUrl, - this._isUrl - ); - return this._tryRegister(); + const client = this._createTemporaryClient(); + return this._tryRegister(client); } - _tryRegister(authDict, poll_for_success) { + _tryRegister(client, authDict, poll_for_success) { var self = this; var bindEmail; @@ -129,7 +135,7 @@ class Register extends Signup { bindEmail = true; } - return MatrixClientPeg.get().register( + return client.register( this.username, this.password, this.params.sessionId, authDict, bindEmail, this.guestAccessToken ).then(function(result) { @@ -152,7 +158,7 @@ class Register extends Signup { console.log("Active flow => %s", JSON.stringify(flow)); var flowStage = self.firstUncompletedStage(flow); if (flowStage != self.activeStage) { - return self.startStage(flowStage).catch(function(err) { + return self._startStage(client, flowStage).catch(function(err) { self.setStep('START'); throw err; }); @@ -161,7 +167,7 @@ class Register extends Signup { } if (poll_for_success) { return q.delay(5000).then(function() { - return self._tryRegister(authDict, poll_for_success); + return self._tryRegister(client, authDict, poll_for_success); }); } else { throw new Error("Authorisation failed!"); @@ -201,7 +207,7 @@ class Register extends Signup { return completed.indexOf(stageType) !== -1; } - startStage(stageName) { + _startStage(client, stageName) { var self = this; this.setStep(`STEP_${stageName}`); var StageClass = SignupStages[stageName]; @@ -210,12 +216,12 @@ class Register extends Signup { throw new Error("Unknown stage: " + stageName); } - var stage = new StageClass(MatrixClientPeg.get(), this); + var stage = new StageClass(client, this); this.activeStage = stage; return stage.complete().then(function(request) { if (request.auth) { console.log("Stage %s is returning an auth dict", stageName); - return self._tryRegister(request.auth, request.poll_for_success); + return self._tryRegister(client, request.auth, request.poll_for_success); } else { // never resolve the promise chain. This is for things like email auth @@ -263,14 +269,6 @@ class Register extends Signup { } recheckState() { - // feels a bit wrong to be clobbering the global client for something we - // don't even know if it'll work, but we'll leave this here for now to - // not complicate matters further. It would be nicer to isolate this - // logic entirely from the rest of the app though. - MatrixClientPeg.replaceUsingUrls( - this._hsUrl, - this._isUrl - ); // We've been given a bunch of data from a previous register step, // this only happens for email auth currently. It's kinda ming we need // to know this though. A better solution would be to ask the stages if @@ -281,7 +279,8 @@ class Register extends Signup { ); if (this.params.hasEmailInfo) { - this.registrationPromise = this.startStage(EMAIL_STAGE_TYPE); + const client = this._createTemporaryClient(); + this.registrationPromise = this._startStage(client, EMAIL_STAGE_TYPE); } return this.registrationPromise; } @@ -305,15 +304,8 @@ class Login extends Signup { getFlows() { var self = this; - // feels a bit wrong to be clobbering the global client for something we - // don't even know if it'll work, but we'll leave this here for now to - // not complicate matters further. It would be nicer to isolate this - // logic entirely from the rest of the app though. - MatrixClientPeg.replaceUsingUrls( - this._hsUrl, - this._isUrl - ); - return MatrixClientPeg.get().loginFlows().then(function(result) { + var client = this._createTemporaryClient(); + return client.loginFlows().then(function(result) { self._flows = result.flows; self._currentFlowIndex = 0; // technically the UI should display options for all flows for the @@ -334,8 +326,8 @@ class Login extends Signup { } loginAsGuest() { - MatrixClientPeg.replaceUsingUrls(this._hsUrl, this._isUrl); - return MatrixClientPeg.get().registerGuest().then((creds) => { + var client = this._createTemporaryClient(); + return client.registerGuest().then((creds) => { return { userId: creds.user_id, accessToken: creds.access_token, @@ -366,7 +358,8 @@ class Login extends Signup { loginParams.user = username; } - return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) { + var client = this._createTemporaryClient(); + return client.login('m.login.password', loginParams).then(function(data) { return q({ homeserverUrl: self._hsUrl, identityServerUrl: self._isUrl, @@ -384,13 +377,12 @@ class Login extends Signup { 'Incorrect username and/or password.' ); if (self._fallbackHsUrl) { - // as per elsewhere, it would be much nicer to not replace the global - // client just to try an alternate HS - MatrixClientPeg.replaceUsingUrls( - self._fallbackHsUrl, - self._isUrl - ); - return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) { + var fbClient = Matrix.createClient({ + baseUrl: self._fallbackHsUrl, + idBaseUrl: this._isUrl, + }); + + return fbClient.login('m.login.password', loginParams).then(function(data) { return q({ homeserverUrl: self._fallbackHsUrl, identityServerUrl: self._isUrl, @@ -398,11 +390,6 @@ class Login extends Signup { accessToken: data.access_token }); }, function(fallback_error) { - // We also have to put the default back again if it fails... - MatrixClientPeg.replaceUsingUrls( - this._hsUrl, - this._isUrl - ); // throw the original error throw error; }); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index ffd4ad0a70..25a7e6a81a 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -193,7 +193,7 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - this._stopMatrixClient(); + Lifecycle.stopMatrixClient(); dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("focus", this.onFocus); @@ -601,16 +601,6 @@ module.exports = React.createClass({ }); }, - // stop all the background processes related to the current client - _stopMatrixClient: function() { - Notifier.stop(); - UserActivity.stop(); - Presence.stop(); - MatrixClientPeg.get().stopClient(); - MatrixClientPeg.get().removeAllListeners(); - MatrixClientPeg.unset(); - }, - onKeyDown: function(ev) { /* // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers @@ -935,10 +925,8 @@ module.exports = React.createClass({ var NewVersionBar = sdk.getComponent('globals.NewVersionBar'); var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword'); - // work out the HS URL prompts we should show for - - console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen + - "; logged_in="+this.state.logged_in+"; ready="+this.state.ready); + // console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen + + // "; logged_in="+this.state.logged_in+"; ready="+this.state.ready); if (this.state.loading) { var Spinner = sdk.getComponent('elements.Spinner'); From 55a28564b9cafff893fd3d7a56657aba0051ceb0 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 11 Aug 2016 16:16:53 +0100 Subject: [PATCH 02/31] Add device_id to devices display (it turns out to be quite useful) --- src/components/views/settings/DevicesPanel.js | 3 ++- src/components/views/settings/DevicesPanelEntry.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js index 8dd6bb9230..2914b0789d 100644 --- a/src/components/views/settings/DevicesPanel.js +++ b/src/components/views/settings/DevicesPanel.js @@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component { (error) => { if (this._unmounted) { return; } var errtxt; - if (err.httpStatus == 404) { + if (error.httpStatus == 404) { // 404 probably means the HS doesn't yet support the API. errtxt = "Your home server does not support device management."; } else { @@ -127,6 +127,7 @@ export default class DevicesPanel extends React.Component { return (
+
ID
Name
Last seen
diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.js index b660f196c8..cc416ace2f 100644 --- a/src/components/views/settings/DevicesPanelEntry.js +++ b/src/components/views/settings/DevicesPanelEntry.js @@ -109,6 +109,9 @@ export default class DevicesPanelEntry extends React.Component { return (
+
+ {device.device_id} +
Date: Thu, 11 Aug 2016 17:28:09 +0100 Subject: [PATCH 03/31] JS SDK 0.5.5 released --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4817d7199..343705999e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "linkifyjs": "^2.0.0-beta.4", "lodash": "^4.13.1", "marked": "^0.3.5", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "0.5.5", "optimist": "^0.6.1", "q": "^1.4.1", "react": "^15.2.1", From 028c652f492422de339bb8533daabfc541b8adcd Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 11 Aug 2016 17:29:10 +0100 Subject: [PATCH 04/31] Prepare changelog for v0.6.4 --- CHANGELOG.md | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f946d7cc..d7a3fe7a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,219 @@ +Changes in [0.6.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4) (2016-08-11) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.3...v0.6.4) + + * Only show Autocomplete if autocomplete is enabled + [\#411](https://github.com/matrix-org/matrix-react-sdk/pull/411) + * Wmwragg/room tag menu + [\#402](https://github.com/matrix-org/matrix-react-sdk/pull/402) + * Move guest registration into the login logic + [\#407](https://github.com/matrix-org/matrix-react-sdk/pull/407) + * Better support for inviting multiple people + [\#403](https://github.com/matrix-org/matrix-react-sdk/pull/403) + * Refactor login token + [\#406](https://github.com/matrix-org/matrix-react-sdk/pull/406) + * Use the current HS for guest login + [\#405](https://github.com/matrix-org/matrix-react-sdk/pull/405) + * Various fixes and improvements to emojification. + [\#395](https://github.com/matrix-org/matrix-react-sdk/pull/395) + * Fix settings resetting on refresh + [\#404](https://github.com/matrix-org/matrix-react-sdk/pull/404) + * Avoid flashing up login screen during guest registration + [\#401](https://github.com/matrix-org/matrix-react-sdk/pull/401) + * Cancel calls to rate-limited funcs on unmount + [\#400](https://github.com/matrix-org/matrix-react-sdk/pull/400) + * Move rehydration of MatrixClients from MatrixClientPeg to SessionLoader + [\#399](https://github.com/matrix-org/matrix-react-sdk/pull/399) + * Don't show integrations header if setting not on + [\#398](https://github.com/matrix-org/matrix-react-sdk/pull/398) + * Start to factor out session-loading magic + [\#397](https://github.com/matrix-org/matrix-react-sdk/pull/397) + * Hack around a react warning + [\#396](https://github.com/matrix-org/matrix-react-sdk/pull/396) + * Add config to hide the labs section + [\#393](https://github.com/matrix-org/matrix-react-sdk/pull/393) + * Dbkr/scalar + [\#392](https://github.com/matrix-org/matrix-react-sdk/pull/392) + * Wmwragg/mute mention state fix + [\#390](https://github.com/matrix-org/matrix-react-sdk/pull/390) + * Fix long freeze when opening 'historical' section + [\#391](https://github.com/matrix-org/matrix-react-sdk/pull/391) + * Refactor UI error effects + [\#388](https://github.com/matrix-org/matrix-react-sdk/pull/388) + * Implement account deactivation + [\#381](https://github.com/matrix-org/matrix-react-sdk/pull/381) + * Don't leave isRoomPublished as undefined + [\#389](https://github.com/matrix-org/matrix-react-sdk/pull/389) + * Call the logout API when we log out + [\#377](https://github.com/matrix-org/matrix-react-sdk/pull/377) + * feat: code cleanup & emoji replacement in composer + [\#335](https://github.com/matrix-org/matrix-react-sdk/pull/335) + * Add more logging to TimelinePanel-test + [\#387](https://github.com/matrix-org/matrix-react-sdk/pull/387) + * DevicesPanel: use device_id as a placeholder + [\#386](https://github.com/matrix-org/matrix-react-sdk/pull/386) + * MemberDeviceInfo: Use the device name, where available + [\#385](https://github.com/matrix-org/matrix-react-sdk/pull/385) + * Wmwragg/mention state menu + [\#369](https://github.com/matrix-org/matrix-react-sdk/pull/369) + * fix upload for video or image files where sniffing fails + [\#383](https://github.com/matrix-org/matrix-react-sdk/pull/383) + * fix: allow up/down normally for no completions + [\#384](https://github.com/matrix-org/matrix-react-sdk/pull/384) + * fix: autocomplete to use tab instead of return + [\#382](https://github.com/matrix-org/matrix-react-sdk/pull/382) + * strip (IRC) displayname suffix from autocomplete + [\#375](https://github.com/matrix-org/matrix-react-sdk/pull/375) + * Include rooms with 1 person invited + [\#379](https://github.com/matrix-org/matrix-react-sdk/pull/379) + * Fix 'start new direct chat' + [\#378](https://github.com/matrix-org/matrix-react-sdk/pull/378) + * Fix warnings from MessageComposer + [\#376](https://github.com/matrix-org/matrix-react-sdk/pull/376) + * New voice and video call buttons + [\#371](https://github.com/matrix-org/matrix-react-sdk/pull/371) + * Silence some more react warnings + [\#373](https://github.com/matrix-org/matrix-react-sdk/pull/373) + * Fix warnings emanating from Velociraptor elements + [\#372](https://github.com/matrix-org/matrix-react-sdk/pull/372) + * Wmwragg/button updates + [\#353](https://github.com/matrix-org/matrix-react-sdk/pull/353) + * Implement device management UI + [\#370](https://github.com/matrix-org/matrix-react-sdk/pull/370) + * Factor EditableTextContainer out of ChangeDisplayName + [\#368](https://github.com/matrix-org/matrix-react-sdk/pull/368) + * Stop the Avatar classes setting properties on s + [\#367](https://github.com/matrix-org/matrix-react-sdk/pull/367) + * Remove relayoutOnUpdate prop on gemini-scrollbar + [\#366](https://github.com/matrix-org/matrix-react-sdk/pull/366) + * Fix bug where vector freezes on power level event + [\#364](https://github.com/matrix-org/matrix-react-sdk/pull/364) + * Refactor MatrixClientPeg + [\#361](https://github.com/matrix-org/matrix-react-sdk/pull/361) + * Fix 'start chat' button on MemberInfo + [\#363](https://github.com/matrix-org/matrix-react-sdk/pull/363) + * Bump dependency versions + [\#362](https://github.com/matrix-org/matrix-react-sdk/pull/362) + * Fix tab complete order properly + [\#360](https://github.com/matrix-org/matrix-react-sdk/pull/360) + * Add removeListener for account data listener + [\#359](https://github.com/matrix-org/matrix-react-sdk/pull/359) + * Set the device_id on pre-login MatrixClient + [\#358](https://github.com/matrix-org/matrix-react-sdk/pull/358) + * Wmwragg/mention state indicator round 2 + [\#357](https://github.com/matrix-org/matrix-react-sdk/pull/357) + * Support for disabling/enabling URL previews per-user, per-room and per-user- + per-room + [\#356](https://github.com/matrix-org/matrix-react-sdk/pull/356) + * Use HS proxy API for requestToken on adding email + [\#336](https://github.com/matrix-org/matrix-react-sdk/pull/336) + * Error if email already in use when resetting pw + [\#337](https://github.com/matrix-org/matrix-react-sdk/pull/337) + * Fix enourmous video bug + [\#355](https://github.com/matrix-org/matrix-react-sdk/pull/355) + * Add support for sending uploaded content as m.video + [\#354](https://github.com/matrix-org/matrix-react-sdk/pull/354) + * Order tab complete by most recently spoke + [\#341](https://github.com/matrix-org/matrix-react-sdk/pull/341) + * Wmwragg/spinner fix + [\#350](https://github.com/matrix-org/matrix-react-sdk/pull/350) + * Now showing three dots when hovering over the badge + [\#352](https://github.com/matrix-org/matrix-react-sdk/pull/352) + * Fix unpublishing room in room settings + [\#351](https://github.com/matrix-org/matrix-react-sdk/pull/351) + * Fix race when creating rooms where invite list can be blank + [\#347](https://github.com/matrix-org/matrix-react-sdk/pull/347) + * improve wording of MemberInfo's start chat button. + [\#348](https://github.com/matrix-org/matrix-react-sdk/pull/348) + * Revert "Amends react template and removes opening image in lightbox" + [\#346](https://github.com/matrix-org/matrix-react-sdk/pull/346) + * Wmwragg/modal restyle + [\#345](https://github.com/matrix-org/matrix-react-sdk/pull/345) + * Amends react template and removes opening image in lightbox + [\#343](https://github.com/matrix-org/matrix-react-sdk/pull/343) + * Remove the member list loading hack + [\#344](https://github.com/matrix-org/matrix-react-sdk/pull/344) + * CSS classes to colour offline users differently + [\#342](https://github.com/matrix-org/matrix-react-sdk/pull/342) + * Listen for the new lastPreseceTs event + [\#340](https://github.com/matrix-org/matrix-react-sdk/pull/340) + * Fix filtering user list by ID + [\#339](https://github.com/matrix-org/matrix-react-sdk/pull/339) + * Update tab completion list when we have a room + [\#338](https://github.com/matrix-org/matrix-react-sdk/pull/338) + * JS code style guide + [\#330](https://github.com/matrix-org/matrix-react-sdk/pull/330) + * Error on registration if email taken + [\#334](https://github.com/matrix-org/matrix-react-sdk/pull/334) + * feat: render unicode emoji as emojione images + [\#332](https://github.com/matrix-org/matrix-react-sdk/pull/332) + * feat: unblacklist img tags with data URIs + [\#333](https://github.com/matrix-org/matrix-react-sdk/pull/333) + * Autocomplete fixes + [\#331](https://github.com/matrix-org/matrix-react-sdk/pull/331) + * Better autocomplete + [\#296](https://github.com/matrix-org/matrix-react-sdk/pull/296) + * feat: add and configure eslint + [\#329](https://github.com/matrix-org/matrix-react-sdk/pull/329) + * Fix user links + [\#326](https://github.com/matrix-org/matrix-react-sdk/pull/326) + * Fix ordering of Memberlist + [\#327](https://github.com/matrix-org/matrix-react-sdk/pull/327) + * Display an error message if room not found + [\#325](https://github.com/matrix-org/matrix-react-sdk/pull/325) + * Implement device blocking + [\#324](https://github.com/matrix-org/matrix-react-sdk/pull/324) + * Remove /encrypt command + [\#322](https://github.com/matrix-org/matrix-react-sdk/pull/322) + * RoomSettings: add encryption setting + [\#321](https://github.com/matrix-org/matrix-react-sdk/pull/321) + * Fix a pair of warnings from RoomSettings + [\#320](https://github.com/matrix-org/matrix-react-sdk/pull/320) + * RoomSettings: refactor permissions calculations + [\#319](https://github.com/matrix-org/matrix-react-sdk/pull/319) + * Fix https://github.com/vector-im/vector-web/issues/1679 + [\#318](https://github.com/matrix-org/matrix-react-sdk/pull/318) + * Fix /join to be consistent with the other code + [\#317](https://github.com/matrix-org/matrix-react-sdk/pull/317) + * UserSettings: fix the displayed version of the react-sdk + [\#316](https://github.com/matrix-org/matrix-react-sdk/pull/316) + * Show canonical alias in URL bar + [\#314](https://github.com/matrix-org/matrix-react-sdk/pull/314) + * Some basic tests for RoomView + [\#313](https://github.com/matrix-org/matrix-react-sdk/pull/313) + * Support for making devices unverified + [\#315](https://github.com/matrix-org/matrix-react-sdk/pull/315) + * Fix eventListener warning + [\#312](https://github.com/matrix-org/matrix-react-sdk/pull/312) + * Fix peeking and member list vanishing + [\#307](https://github.com/matrix-org/matrix-react-sdk/pull/307) + * Use different keys for new MessageComposerInput + [\#311](https://github.com/matrix-org/matrix-react-sdk/pull/311) + * Fix RTE escaping, HTML output with breaks + [\#310](https://github.com/matrix-org/matrix-react-sdk/pull/310) + * Fix cursor bug, persist editor mode & rte default + [\#308](https://github.com/matrix-org/matrix-react-sdk/pull/308) + * Rich Text Editor + [\#292](https://github.com/matrix-org/matrix-react-sdk/pull/292) + * Hide e2e features if not enabled + [\#306](https://github.com/matrix-org/matrix-react-sdk/pull/306) + * Add experimental "Labs" section to settings + [\#305](https://github.com/matrix-org/matrix-react-sdk/pull/305) + * Make the room directory join rooms by alias + [\#304](https://github.com/matrix-org/matrix-react-sdk/pull/304) + * Factor out common parts of room creation + [\#303](https://github.com/matrix-org/matrix-react-sdk/pull/303) + * Fix spinner-of-doom in member info for guests + [\#302](https://github.com/matrix-org/matrix-react-sdk/pull/302) + * Support for marking devices as verified + [\#300](https://github.com/matrix-org/matrix-react-sdk/pull/300) + * Make the config optional + [\#301](https://github.com/matrix-org/matrix-react-sdk/pull/301) + * Pass brand parameter down to Notifications + [\#299](https://github.com/matrix-org/matrix-react-sdk/pull/299) + * Second attempt at fixing the Velocity memory leak + [\#298](https://github.com/matrix-org/matrix-react-sdk/pull/298) + Changes in [0.6.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.3) (2016-06-03) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.2...v0.6.3) From ec9243d4b0cd15e900b2bb167ad05daec7f0e104 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 11 Aug 2016 17:29:10 +0100 Subject: [PATCH 05/31] 0.6.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 343705999e..3a1773e6ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.3", + "version": "0.6.4", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 3bedad69cd9c92516d8e25d3d9e13b5157e6c91c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 11 Aug 2016 17:08:17 -0500 Subject: [PATCH 06/31] unbreak inviting :( --- src/components/views/rooms/MemberList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 7871e9d88e..e1521b65e3 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -283,7 +283,7 @@ module.exports = React.createClass({ if (inputs.length == 1) { // for a single address, we just send the invite promise.done(() => { - this.doInvite(inputs[0]); + this._doInvite(inputs[0]); }); } else { // if there are several, display the confirmation/progress dialog From d1eec57118a5c0188ee13d96c1e9e6c691163791 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 11 Aug 2016 17:11:51 -0500 Subject: [PATCH 07/31] fix inviting some more --- src/components/views/rooms/MemberList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index e1521b65e3..7c13df5f71 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -180,6 +180,7 @@ module.exports = React.createClass({ }, _doInvite(address) { + var self = this; Invite.inviteToRoom(self.props.roomId, address).catch((err) => { if (err !== null) { console.error("Failed to invite: %s", JSON.stringify(err)); From 5440fd750477cb18f4ebf98d4b6691422c2d74d9 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 12 Aug 2016 07:27:53 +0100 Subject: [PATCH 08/31] Fix tests MatrixClientPeg no longer has a replaceUsingUrls method, so don't try to stub it out. --- test/test-utils.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test-utils.js b/test/test-utils.js index e2ff5e8c10..799f04ce54 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -50,8 +50,7 @@ module.exports.stubClient = function() { // // 'sandbox.restore()' doesn't work correctly on inherited methods, // so we do this for each method - var methods = ['get', 'unset', 'replaceUsingUrls', - 'replaceUsingCreds']; + var methods = ['get', 'unset', 'replaceUsingCreds']; for (var i = 0; i < methods.length; i++) { sandbox.stub(peg, methods[i]); } @@ -184,4 +183,3 @@ module.exports.mkStubRoom = function() { }, }; }; - From df22768f1bb3b1f89d72d72582269187e21c8043 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 11 Aug 2016 14:21:52 +0100 Subject: [PATCH 09/31] Use server-generated deviceId --- src/Lifecycle.js | 5 +++++ src/MatrixClientPeg.js | 15 +++------------ src/Signup.js | 3 +++ src/components/structures/login/Registration.js | 3 ++- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0a2a503ce6..175a66bf96 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -128,6 +128,7 @@ function _loginWithToken(queryParams) { console.log("Logged in with token"); setLoggedIn({ userId: data.user_id, + deviceId: data.device_id, accessToken: data.access_token, homeserverUrl: queryParams.homeserver, identityServerUrl: queryParams.identityServer, @@ -151,6 +152,7 @@ function _registerAsGuest(hsUrl, isUrl) { console.log("Registered as guest: %s", creds.user_id); setLoggedIn({ userId: creds.user_id, + deviceId: creds.device_id, accessToken: creds.access_token, homeserverUrl: hsUrl, identityServerUrl: isUrl, @@ -170,6 +172,7 @@ function _restoreFromLocalStorage() { const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org'; const access_token = localStorage.getItem("mx_access_token"); const user_id = localStorage.getItem("mx_user_id"); + const device_id = localStorage.getItem("mx_device_id"); let is_guest; if (localStorage.getItem("mx_is_guest") !== null) { @@ -183,6 +186,7 @@ function _restoreFromLocalStorage() { console.log("Restoring session for %s", user_id); setLoggedIn({ userId: user_id, + deviceId: device_id, accessToken: access_token, homeserverUrl: hs_url, identityServerUrl: is_url, @@ -212,6 +216,7 @@ export function setLoggedIn(credentials) { localStorage.setItem("mx_is_url", credentials.identityServerUrl); localStorage.setItem("mx_user_id", credentials.userId); + localStorage.setItem("mx_device_id", credentials.deviceId); localStorage.setItem("mx_access_token", credentials.accessToken); localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest)); console.log("Session persisted for %s", credentials.userId); diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index e22990a850..190181c875 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -21,21 +21,11 @@ import utils from 'matrix-js-sdk/lib/utils'; const localStorage = window.localStorage; -function deviceId() { - // XXX: is Math.random()'s deterministicity a problem here? - var id = Math.floor(Math.random()*16777215).toString(16); - id = "W" + "000000".substring(id.length) + id; - if (localStorage) { - id = localStorage.getItem("mx_device_id") || id; - localStorage.setItem("mx_device_id", id); - } - return id; -} - interface MatrixClientCreds { homeserverUrl: string, identityServerUrl: string, userId: string, + deviceId: string, accessToken: string, guest: boolean, } @@ -87,6 +77,7 @@ class MatrixClientPeg { homeserverUrl: this.matrixClient.baseUrl, identityServerUrl: this.matrixClient.idBaseUrl, userId: this.matrixClient.credentials.userId, + deviceId: this.matrixClient.getDeviceId(), accessToken: this.matrixClient.getAccessToken(), guest: this.matrixClient.isGuest(), }; @@ -98,12 +89,12 @@ class MatrixClientPeg { idBaseUrl: creds.identityServerUrl, accessToken: creds.accessToken, userId: creds.userId, + deviceId: creds.deviceId, timelineSupport: true, }; if (localStorage) { opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage); - opts.deviceId = deviceId(); } this.matrixClient = Matrix.createClient(opts); diff --git a/src/Signup.js b/src/Signup.js index aa0aee597c..ec7cd5b6cf 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -330,6 +330,7 @@ class Login extends Signup { return client.registerGuest().then((creds) => { return { userId: creds.user_id, + deviceId: creds.device_id, accessToken: creds.access_token, homeserverUrl: this._hsUrl, identityServerUrl: this._isUrl, @@ -364,6 +365,7 @@ class Login extends Signup { homeserverUrl: self._hsUrl, identityServerUrl: self._isUrl, userId: data.user_id, + deviceId: data.device_id, accessToken: data.access_token }); }, function(error) { @@ -387,6 +389,7 @@ class Login extends Signup { homeserverUrl: self._fallbackHsUrl, identityServerUrl: self._isUrl, userId: data.user_id, + deviceId: data.device_id, accessToken: data.access_token }); }, function(fallback_error) { diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 423d62933f..a51ab15d5c 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -154,6 +154,7 @@ module.exports = React.createClass({ } self.props.onLoggedIn({ userId: response.user_id, + deviceId: response.device_id, homeserverUrl: self.registerLogic.getHomeserverUrl(), identityServerUrl: self.registerLogic.getIdentityServerUrl(), accessToken: response.access_token @@ -279,7 +280,7 @@ module.exports = React.createClass({ var returnToAppJsx; if (this.props.onCancelClick) { - returnToAppJsx = + returnToAppJsx = Return to app From fc0c5a5c5804a84dd6a7495016cc95c9381566d9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 12 Aug 2016 10:02:55 +0100 Subject: [PATCH 10/31] Tidy up fix to multi-invite --- src/components/views/rooms/MemberList.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 7c13df5f71..fa3a7504f3 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -180,8 +180,7 @@ module.exports = React.createClass({ }, _doInvite(address) { - var self = this; - Invite.inviteToRoom(self.props.roomId, address).catch((err) => { + Invite.inviteToRoom(this.props.roomId, address).catch((err) => { if (err !== null) { console.error("Failed to invite: %s", JSON.stringify(err)); if (err.errcode == 'M_FORBIDDEN') { @@ -197,7 +196,7 @@ module.exports = React.createClass({ } } }).finally(() => { - self.setState({ + this.setState({ inviting: false }); // XXX: hacky focus on the invite box @@ -208,7 +207,7 @@ module.exports = React.createClass({ } }, 0); }).done(); - self.setState({ + this.setState({ inviting: true }); }, From 10e41d7a261376b47f0626a1b233c104f48695c8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 12 Aug 2016 10:04:35 +0100 Subject: [PATCH 11/31] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a3fe7a88..3dd76494a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Changes in [?.?.?] +================== + * Fix inviting multiple people + Changes in [0.6.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4) (2016-08-11) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.3...v0.6.4) From 6b1e5dfae7189aae186db5b4193e2943916ad59d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 12 Aug 2016 10:08:14 +0100 Subject: [PATCH 12/31] Prepare changelog for v0.6.4-r1 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd76494a4..b99944185e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -Changes in [?.?.?] -================== +Changes in [0.6.4-r1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4-r1) (2016-08-12) +========================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.4...v0.6.4-r1) * Fix inviting multiple people Changes in [0.6.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4) (2016-08-11) From 3c8a7e36aa3fcf6ab8201bcab3f64c8c9aa66a40 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 12 Aug 2016 10:08:15 +0100 Subject: [PATCH 13/31] 0.6.4-r1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a1773e6ed..c6ece7e20d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.4", + "version": "0.6.4-r1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From a29325cc469f424841ec6a3e6836f0721037e45e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 11 Aug 2016 16:15:42 +0100 Subject: [PATCH 14/31] Set initial_device_display_name on login and register Let Vector pass in a default device name, and thread it through everywhere to set it on login and register calls --- src/Lifecycle.js | 25 ++++++++++++++----- src/Signup.js | 21 ++++++++++------ src/components/structures/MatrixChat.js | 3 +++ src/components/structures/login/Login.js | 6 ++++- .../structures/login/Registration.js | 7 +++++- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0a2a503ce6..6403cd6d4d 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -69,6 +69,7 @@ export function loadSession(opts) { let enableGuest = opts.enableGuest || false; const guestHsUrl = opts.guestHsUrl; const guestIsUrl = opts.guestIsUrl; + const defaultDeviceDisplayName = opts.defaultDeviceDisplayName; if (fragmentQueryParams.client_secret && fragmentQueryParams.sid) { // this happens during email validation: the email contains a link to the @@ -87,7 +88,7 @@ export function loadSession(opts) { if (!realQueryParams.homeserver) { console.warn("Cannot log in with token: can't determine HS URL to use"); } else { - return _loginWithToken(realQueryParams); + return _loginWithToken(realQueryParams, defaultDeviceDisplayName); } } @@ -111,20 +112,25 @@ export function loadSession(opts) { } if (enableGuest) { - return _registerAsGuest(guestHsUrl, guestIsUrl); + return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName); } // fall back to login screen return q(); } -function _loginWithToken(queryParams) { +function _loginWithToken(queryParams, defaultDeviceDisplayName) { // create a temporary MatrixClient to do the login var client = Matrix.createClient({ baseUrl: queryParams.homeserver, }); - return client.loginWithToken(queryParams.loginToken).then(function(data) { + return client.login( + "m.login.token", { + token: queryParams.loginToken, + initial_device_display_name: defaultDeviceDisplayName, + }, + ).then(function(data) { console.log("Logged in with token"); setLoggedIn({ userId: data.user_id, @@ -139,15 +145,22 @@ function _loginWithToken(queryParams) { }); } -function _registerAsGuest(hsUrl, isUrl) { +function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { console.log("Doing guest login on %s", hsUrl); + // TODO: we should probably de-duplicate this and Signup.Login.loginAsGuest. + // Not really sure where the right home for it is. + // create a temporary MatrixClient to do the login var client = Matrix.createClient({ baseUrl: hsUrl, }); - return client.registerGuest().then((creds) => { + return client.registerGuest({ + body: { + initial_device_display_name: defaultDeviceDisplayName, + }, + }).then((creds) => { console.log("Registered as guest: %s", creds.user_id); setLoggedIn({ userId: creds.user_id, diff --git a/src/Signup.js b/src/Signup.js index aa0aee597c..9eb19d0702 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -14,9 +14,10 @@ const EMAIL_STAGE_TYPE = "m.login.email.identity"; * storage of HS/IS URLs. */ class Signup { - constructor(hsUrl, isUrl) { + constructor(hsUrl, isUrl, opts) { this._hsUrl = hsUrl; this._isUrl = isUrl; + this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName; } getHomeserverUrl() { @@ -51,8 +52,8 @@ class Signup { * Registration logic class */ class Register extends Signup { - constructor(hsUrl, isUrl) { - super(hsUrl, isUrl); + constructor(hsUrl, isUrl, opts) { + super(hsUrl, isUrl, opts); this.setStep("START"); this.data = null; // from the server // random other stuff (e.g. query params, NOT params from the server) @@ -135,6 +136,7 @@ class Register extends Signup { bindEmail = true; } + // TODO need to figure out how to send the device display name to /register. return client.register( this.username, this.password, this.params.sessionId, authDict, bindEmail, this.guestAccessToken @@ -295,8 +297,8 @@ class Register extends Signup { class Login extends Signup { - constructor(hsUrl, isUrl, fallbackHsUrl) { - super(hsUrl, isUrl); + constructor(hsUrl, isUrl, fallbackHsUrl, opts) { + super(hsUrl, isUrl, opts); this._fallbackHsUrl = fallbackHsUrl; this._currentFlowIndex = 0; this._flows = []; @@ -327,7 +329,11 @@ class Login extends Signup { loginAsGuest() { var client = this._createTemporaryClient(); - return client.registerGuest().then((creds) => { + return client.registerGuest({ + body: { + initial_device_display_name: this._defaultDeviceDisplayName, + }, + }).then((creds) => { return { userId: creds.user_id, accessToken: creds.access_token, @@ -349,7 +355,8 @@ class Login extends Signup { var self = this; var isEmail = username.indexOf("@") > 0; var loginParams = { - password: pass + password: pass, + initial_device_display_name: this._defaultDeviceDisplayName, }; if (isEmail) { loginParams.medium = 'email'; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 25a7e6a81a..4844faa03d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -185,6 +185,7 @@ module.exports = React.createClass({ enableGuest: this.props.enableGuest, guestHsUrl: this.getCurrentHsUrl(), guestIsUrl: this.getCurrentIsUrl(), + defaultDisplayName: this.props.config.default_device_name, }).done(()=>{ // stuff this through the dispatcher so that it happens // after the on_logged_in action. @@ -1039,6 +1040,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} + defaultDeviceDisplayName={this.props.config.default_device_name} onLoggedIn={this.onRegistered} onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} @@ -1065,6 +1067,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} fallbackHsUrl={this.getFallbackHsUrl()} + defaultDeviceDisplayName={this.props.config.default_device_name} onForgotPasswordClick={this.onForgotPasswordClick} enableGuest={this.props.enableGuest} onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null} diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index f7468a745b..8025504857 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -44,6 +44,8 @@ module.exports = React.createClass({ // different home server without confusing users. fallbackHsUrl: React.PropTypes.string, + defaultDeviceDisplayName: React.PropTypes.string, + // login shouldn't know or care how registration is done. onRegisterClick: React.PropTypes.func.isRequired, @@ -136,7 +138,9 @@ module.exports = React.createClass({ var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null; - var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl); + var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl, { + defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, + }); this._loginLogic = loginLogic; loginLogic.getFlows().then(function(flows) { diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 423d62933f..84bfbe9834 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -45,6 +45,9 @@ module.exports = React.createClass({ email: React.PropTypes.string, username: React.PropTypes.string, guestAccessToken: React.PropTypes.string, + + defaultDeviceDisplayName: React.PropTypes.string, + // registration shouldn't know or care how login is done. onLoginClick: React.PropTypes.func.isRequired, onCancelClick: React.PropTypes.func @@ -71,7 +74,9 @@ module.exports = React.createClass({ this.dispatcherRef = dis.register(this.onAction); // attach this to the instance rather than this.state since it isn't UI this.registerLogic = new Signup.Register( - this.props.customHsUrl, this.props.customIsUrl + this.props.customHsUrl, this.props.customIsUrl, { + defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, + } ); this.registerLogic.setClientSecret(this.props.clientSecret); this.registerLogic.setSessionId(this.props.sessionId); From 5fc98ffc49eedf4a39b81803729646bd81d189d5 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 12 Aug 2016 11:20:09 +0100 Subject: [PATCH 15/31] Avoid setting device_id to 'undefined' Deal with the situation where synapse doesn't give us a device_id on login: don't set the device_id to 'undefined' in localstorage. --- src/Lifecycle.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 175a66bf96..c0fd2c13c5 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -214,11 +214,19 @@ export function setLoggedIn(credentials) { try { localStorage.setItem("mx_hs_url", credentials.homeserverUrl); localStorage.setItem("mx_is_url", credentials.identityServerUrl); - localStorage.setItem("mx_user_id", credentials.userId); - localStorage.setItem("mx_device_id", credentials.deviceId); localStorage.setItem("mx_access_token", credentials.accessToken); localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest)); + + // if we didn't get a deviceId from the login, leave mx_device_id unset, + // rather than setting it to "undefined". + // + // (in this case MatrixClient doesn't bother with the crypto stuff + // - that's fine for us). + if (credentials.deviceId) { + localStorage.setItem("mx_device_id", credentials.deviceId); + } + console.log("Session persisted for %s", credentials.userId); } catch (e) { console.warn("Error using local storage: can't persist session!", e); From b9870f2c235facaa32e5fb45b8bf7b7349e5dfb8 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 12 Aug 2016 11:41:06 +0100 Subject: [PATCH 16/31] defaultDeviceDisplayName should be a prop ... not a bit of config. --- src/components/structures/MatrixChat.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4844faa03d..8d84cbf7d0 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -58,6 +58,10 @@ module.exports = React.createClass({ // called when the session load completes onLoadCompleted: React.PropTypes.func, + + // displayname, if any, to set on the device when logging + // in/registering. + defaultDeviceDisplayName: React.PropTypes.string, }, PageTypes: { @@ -185,7 +189,7 @@ module.exports = React.createClass({ enableGuest: this.props.enableGuest, guestHsUrl: this.getCurrentHsUrl(), guestIsUrl: this.getCurrentIsUrl(), - defaultDisplayName: this.props.config.default_device_name, + defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, }).done(()=>{ // stuff this through the dispatcher so that it happens // after the on_logged_in action. @@ -1040,7 +1044,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} - defaultDeviceDisplayName={this.props.config.default_device_name} + defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} onLoggedIn={this.onRegistered} onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} @@ -1067,7 +1071,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} fallbackHsUrl={this.getFallbackHsUrl()} - defaultDeviceDisplayName={this.props.config.default_device_name} + defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} onForgotPasswordClick={this.onForgotPasswordClick} enableGuest={this.props.enableGuest} onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null} From 534d9965825fa112c1e4b2c955dab19c9b86af29 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 15 Aug 2016 16:17:35 +0100 Subject: [PATCH 17/31] ignore local busy - workaround for https://github.com/vector-im/vector-web/issues/1964 --- src/CallHandler.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 9118ee1973..015160a1fe 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -273,8 +273,11 @@ function _onAction(payload) { break; case 'incoming_call': if (module.exports.getAnyActiveCall()) { - payload.call.hangup("busy"); - return; // don't allow >1 call to be received, hangup newer one. + // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup. + // we avoid rejecting with "busy" in case the user wants to answer it on a different device. + // in future we could signal a "local busy" as a warning to the caller. + // see https://github.com/vector-im/vector-web/issues/1964 + return; } // if the runtime env doesn't do VoIP, stop here. From 2a3b0e85ea747e6bd4502931b270d85333465e4d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 15 Aug 2016 21:37:26 +0100 Subject: [PATCH 18/31] add rel='noopener' wherever we do target='_blank' because https://mathiasbynens.github.io/rel-noopener/ --- src/HtmlUtils.js | 5 +++-- src/components/views/messages/MFileBody.js | 2 +- src/components/views/messages/MImageBody.js | 2 +- src/components/views/rooms/LinkPreviewWidget.js | 2 +- src/linkify-matrix.js | 4 ++++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index c0792e6d14..6a8d903df8 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -69,7 +69,7 @@ var sanitizeHtmlParams = { allowedAttributes: { // custom ones first: font: [ 'color' ], // custom to matrix - a: [ 'href', 'name', 'target' ], // remote target: custom to matrix + a: [ 'href', 'name', 'target', 'rel' ], // remote target: custom to matrix // We don't currently allow img itself by default, but this // would make sense if we did img: [ 'src' ], @@ -81,7 +81,7 @@ var sanitizeHtmlParams = { allowedSchemesByTag: { img: [ 'data' ], }, - + transformTags: { // custom to matrix // add blank targets to all hyperlinks except vector URLs 'a': function(tagName, attribs) { @@ -92,6 +92,7 @@ var sanitizeHtmlParams = { else { attribs.target = '_blank'; } + attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/ return { tagName: tagName, attribs : attribs }; }, }, diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 2f416daf95..dbad084024 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -60,7 +60,7 @@ module.exports = React.createClass({ return (
- + Download {text} diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 13f9cf4c19..ec594af2ce 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -134,7 +134,7 @@ module.exports = React.createClass({ onMouseLeave={this.onImageLeave} />
- + Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" }) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index ba438c1d12..60f4f8abc0 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -123,7 +123,7 @@ module.exports = React.createClass({
{ img }
- +
{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }
{ p["og:description"] } diff --git a/src/linkify-matrix.js b/src/linkify-matrix.js index a12ef8eaf5..99b7ee5c33 100644 --- a/src/linkify-matrix.js +++ b/src/linkify-matrix.js @@ -137,6 +137,10 @@ matrixLinkify.options = { } }, + linkAttributes: { + rel: 'noopener', + }, + target: function(href, type) { if (type === 'url') { if (href.match(matrixLinkify.VECTOR_URL_PATTERN)) { From 8777780928d3edacfefaacb3d7ed6e622eb54b0a Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Tue, 16 Aug 2016 03:50:59 +0530 Subject: [PATCH 19/31] strip (IRC) suffix from tabcomplete entries fixes vector-im/vector-web#574 --- src/TabCompleteEntries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TabCompleteEntries.js b/src/TabCompleteEntries.js index 4a28103210..2a8c7b383a 100644 --- a/src/TabCompleteEntries.js +++ b/src/TabCompleteEntries.js @@ -94,7 +94,7 @@ CommandEntry.fromCommands = function(commandArray) { class MemberEntry extends Entry { constructor(member) { - super(member.name || member.userId); + super((member.name || member.userId).replace(' (IRC)', '')); this.member = member; this.kind = 'member'; } From 30168a1b9c60fcda80512e8711ad3a945e5343fb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 17 Aug 2016 09:57:06 +0100 Subject: [PATCH 20/31] Don't download E2E devices if feature disabled If the user hasn't enabled the E2E setting in the labs, there is no point in firing off the device download request when the MemberInfo is opened. --- src/components/views/rooms/MemberInfo.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 0b37847257..b3c61ce807 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -73,6 +73,11 @@ module.exports = React.createClass({ }, componentDidMount: function() { + // only display the devices list if our client supports E2E *and* the + // feature is enabled in the user settings + this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && + UserSettingsStore.isFeatureEnabled("e2e_encryption"); + this._updateStateForNewMember(this.props.member); MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, @@ -147,6 +152,10 @@ module.exports = React.createClass({ }, onDeviceVerificationChanged: function(userId, device) { + if (!this._enableDevices) { + return; + } + if (userId == this.props.member.userId) { // no need to re-download the whole thing; just update our copy of // the list. @@ -170,6 +179,10 @@ module.exports = React.createClass({ }, _downloadDeviceList: function(member) { + if (!this._enableDevices) { + return; + } + var cancelled = false; this._cancelDeviceList = function() { cancelled = true; } @@ -532,7 +545,7 @@ module.exports = React.createClass({ }, _renderDevices: function() { - if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) { + if (!this._enableDevices) { return null; } From 0356f04b9c8378b9657f83e484cf6d85f954b9d4 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 17 Aug 2016 14:40:10 +0100 Subject: [PATCH 21/31] MemberInfo: initialise _enableDevices in componentWillMount ... to avoid referencing it in render() before it is set --- src/components/views/rooms/MemberInfo.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b3c61ce807..59e186da06 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,17 +67,17 @@ module.exports = React.createClass({ componentWillMount: function() { this._cancelDeviceList = null; + // only display the devices list if our client supports E2E *and* the + // feature is enabled in the user settings + this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && + UserSettingsStore.isFeatureEnabled("e2e_encryption"); + this.setState({ existingOneToOneRoomId: this.getExistingOneToOneRoomId() }); }, componentDidMount: function() { - // only display the devices list if our client supports E2E *and* the - // feature is enabled in the user settings - this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && - UserSettingsStore.isFeatureEnabled("e2e_encryption"); - this._updateStateForNewMember(this.props.member); MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, From 46d306a217e6101a2c37f4eeaf6760e96537bd60 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 17 Aug 2016 17:16:19 +0100 Subject: [PATCH 22/31] Change register response access_token to scalar_token --- src/ScalarAuthClient.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 67607b4996..d145cebfe0 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -34,8 +34,10 @@ class ScalarAuthClient { defer.reject(err); } else if (response.statusCode / 100 !== 2) { defer.reject({statusCode: response.statusCode}); + } else if (!body || !body.scalar_token) { + defer.reject(new Error("Missing scalar_token in response")); } else { - defer.resolve(body.access_token); + defer.resolve(body.scalar_token); } }); From 87f94bde62458b585c193dad472730d628ab2516 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 17 Aug 2016 18:26:37 +0100 Subject: [PATCH 23/31] Fix up notification setting listener in roomtile The previous dispatch only did binary muted/non-muted but we now have 4 states. We now just listen for the push rules account data and update on that so it stays in sync if the pishrules are changed elsewhere. Also add util functions used here for getting the notif state and in vector for both getting and setting it. --- src/RoomNotifs.js | 137 +++++++++++++++++++++++++ src/components/views/rooms/RoomTile.js | 52 +++++----- 2 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 src/RoomNotifs.js diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js new file mode 100644 index 0000000000..704fdd17b8 --- /dev/null +++ b/src/RoomNotifs.js @@ -0,0 +1,137 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import MatrixClientPeg from './MatrixClientPeg'; +import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; +import q from 'q'; + +export function getRoomNotifsState(roomId) { + // look through the override rules for a rule affecting this room: + // if one exists, it will take precedence. + const muteRule = findOverrideMuteRule(roomId); + if (muteRule && muteRule.enabled) { + return 'mute'; + } + + // for everything else, look at the room rule. + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + + // XXX: We have to assume the default is to notify for all messages + // (in particular this will be 'wrong' for one to one rooms because + // they will notify loudly for all messages) + if (!roomRule || !roomRule.enabled) return 'all_messages'; + + // a mute at the room level will still allow mentions + // to notify + if (isMuteRule(roomRule)) return 'mentions_only'; + + const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); + if (actionsObject.tweaks.sound) return 'all_messages_loud'; + + return null; +} + +export function setRoomNotifsState(roomId, newState) { + const cli = MatrixClientPeg.get(); + const promises = []; + + if (newState == 'mute') { + // delete the room rule + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } + + // add an override rule to squelch everything in this room + promises.push(cli.addPushRule('global', 'override', roomId, { + conditions: [ + { + kind: 'event_match', + key: 'room_id', + pattern: roomId, + } + ], + actions: [ + 'dont_notify', + ] + })); + } else { + const overrideMuteRule = findOverrideMuteRule(roomId); + if (overrideMuteRule) { + promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + } + + if (newState == 'all_messages') { + promises.push(cli.deletePushRule('global', 'room', roomId)); + } else if (newState == 'mentions_only') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'dont_notify', + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } else if ('all_messages_loud') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'notify', + { + set_tweak: 'sound', + value: 'default', + } + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } + } + + return q.all(promises); +} + +function findOverrideMuteRule(roomId) { + for (const rule of MatrixClientPeg.get().pushRules['global'].override) { + if (isRuleForRoom(roomId, rule)) { + if (isMuteRule(rule)) { + return rule; + } + } + } + return null; +} + +function isRuleForRoom(roomId, rule) { + if (rule.conditions.length !== 1) { + return false; + } + const cond = rule.conditions[0]; + if ( + cond.kind == 'event_match' && + cond.key == 'room_id' && + cond.pattern == roomId + ) { + return true; + } + return false; +} + +function isMuteRule(rule) { + return ( + rule.actions.length == 1 && + rule.actions[0] == 'dont_notify' + ); +} + diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index ae0ffafae5..7248ed7fcd 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -22,6 +22,7 @@ var dis = require("../../../dispatcher"); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); var ContextualMenu = require('../../structures/ContextualMenu'); +var RoomNotifs = require('../../../RoomNotifs'); module.exports = React.createClass({ displayName: 'RoomTile', @@ -43,43 +44,40 @@ module.exports = React.createClass({ }, getInitialState: function() { - var areNotifsMuted = false; - var cli = MatrixClientPeg.get(); - if (!cli.isGuest()) { - var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; - } - } - } - return({ hover : false, badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - areNotifsMuted: areNotifsMuted, + showBadge: this._shouldShowBadge(), }); }, - onAction: function(payload) { - switch (payload.action) { - case 'notification_change': - // Is the notification about this room? - if (payload.roomId === this.props.room.roomId) { - this.setState( { areNotifsMuted : payload.areNotifsMuted }); - } - break; + _shouldShowBadge: function() { + if (MatrixClientPeg.get().isGuest()) return true; + + const showBadgeInStates = ['all_messages', 'all_messages_loud']; + const currentState = RoomNotifs.getRoomNotifsState(this.props.room.roomId); + return showBadgeInStates.indexOf(currentState) > -1; + }, + + onAccountData: function(accountDataEvent) { + if (accountDataEvent.getType() == 'm.push_rules') { + this.setState({ + showBadge: this._shouldShowBadge(), + }); } }, - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); + componentWillMount: function() { + MatrixClientPeg.get().on("accountData", this.onAccountData); }, componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); + var cli = MatrixClientPeg.get(); + if (cli) { + MatrixClientPeg.get().removeListener("accountData", this.onAccountData); + } }, onClick: function() { @@ -183,11 +181,11 @@ module.exports = React.createClass({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && !this.state.areNotifsMuted, + 'mx_RoomTile_unreadNotify': notificationCount > 0 && this.state.showBadge, 'mx_RoomTile_highlight': this.props.highlight, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, - 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) + 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && this.state.showBadge)) }); var avatarClasses = classNames({ @@ -214,7 +212,7 @@ module.exports = React.createClass({ if (this.state.badgeHover || this.state.notificationTagMenu) { badgeContent = "\u00B7\u00B7\u00B7"; - } else if (this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) { + } else if (this.props.highlight || (notificationCount > 0 && this.state.showBadge)) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -230,7 +228,7 @@ module.exports = React.createClass({ var nameClasses = classNames({ 'mx_RoomTile_name': true, 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.notificationTagMenu, + 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && this.state.showBadge) || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) { From dd088794c2ec5df25116860ceb9945b34bbf2c74 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 17 Aug 2016 18:50:34 +0100 Subject: [PATCH 24/31] Remove the mute toggle from room settings As it now incorrectly represents the mute as a binary toggle rather than a quad-state --- src/components/views/rooms/RoomSettings.js | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index abc90ae486..1896207c09 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -47,16 +47,6 @@ module.exports = React.createClass({ tags[tagName] = ['yep']; }); - var areNotifsMuted = false; - if (!MatrixClientPeg.get().isGuest()) { - var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; - } - } - } - return { name: this._yankValueFromEvent("m.room.name", "name"), topic: this._yankValueFromEvent("m.room.topic", "topic"), @@ -66,7 +56,6 @@ module.exports = React.createClass({ power_levels_changed: false, tags_changed: false, tags: tags, - areNotifsMuted: areNotifsMuted, // isRoomPublished is loaded async in componentWillMount so when the component // inits, the saved value will always be undefined, however getInitialState() // is also called from the saving code so we must return the correct value here @@ -188,12 +177,6 @@ module.exports = React.createClass({ } - if (this.state.areNotifsMuted !== originalState.areNotifsMuted) { - promises.push(MatrixClientPeg.get().setRoomMutePushRule( - "global", roomId, this.state.areNotifsMuted - )); - } - // power levels var powerLevels = this._getPowerLevels(); if (powerLevels) { @@ -647,12 +630,6 @@ module.exports = React.createClass({ { tagsSection }
-

Who can access this room?

{ inviteGuestWarning } From 73e486cc583371c111f653238cae9391550b59c6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 11:58:27 +0100 Subject: [PATCH 25/31] Hide red highlight badge in mute mode --- src/components/views/rooms/RoomTile.js | 30 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 7248ed7fcd..8a7b83cb44 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -49,22 +49,29 @@ module.exports = React.createClass({ badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - showBadge: this._shouldShowBadge(), + notifState: this._getNotifState(), }); }, - _shouldShowBadge: function() { - if (MatrixClientPeg.get().isGuest()) return true; + _getNotifState: function() { + if (MatrixClientPeg.get().isGuest()) return 'all_messages'; + return RoomNotifs.getRoomNotifsState(this.props.room.roomId); + }, + _shouldShowNotifBadge: function() { const showBadgeInStates = ['all_messages', 'all_messages_loud']; - const currentState = RoomNotifs.getRoomNotifsState(this.props.room.roomId); + const currentState = this._getNotifState(); return showBadgeInStates.indexOf(currentState) > -1; }, + _shouldShowMentionBadge: function() { + return this._getNotifState() != 'mute'; + }, + onAccountData: function(accountDataEvent) { if (accountDataEvent.getType() == 'm.push_rules') { this.setState({ - showBadge: this._shouldShowBadge(), + notifState: this._getNotifState(), }); } }, @@ -177,15 +184,18 @@ module.exports = React.createClass({ var notificationCount = this.props.room.getUnreadNotificationCount(); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); + var badges = notificationCount > 0 && this._shouldShowNotifBadge(); + badges |= this.props.highlight && this._shouldShowMentionBadge(); + var classes = classNames({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && this.state.showBadge, - 'mx_RoomTile_highlight': this.props.highlight, + 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), + 'mx_RoomTile_highlight': this.props.highlight && badges, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, - 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && this.state.showBadge)) + 'mx_RoomTile_noBadges': !badges, }); var avatarClasses = classNames({ @@ -212,7 +222,7 @@ module.exports = React.createClass({ if (this.state.badgeHover || this.state.notificationTagMenu) { badgeContent = "\u00B7\u00B7\u00B7"; - } else if (this.props.highlight || (notificationCount > 0 && this.state.showBadge)) { + } else if (badges) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -228,7 +238,7 @@ module.exports = React.createClass({ var nameClasses = classNames({ 'mx_RoomTile_name': true, 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && this.state.showBadge) || this.state.badgeHover || this.state.notificationTagMenu, + 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) { From 9e45279894c6d7d31d496393835f546a5134e15b Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 13:44:58 +0100 Subject: [PATCH 26/31] Use enumalike thing --- src/RoomNotifs.js | 13 +++++++++---- src/components/views/rooms/RoomTile.js | 11 ++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 704fdd17b8..6f792df26b 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -18,12 +18,17 @@ import MatrixClientPeg from './MatrixClientPeg'; import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; import q from 'q'; +export const ALL_MESSAGES_LOUD = 'all_messages_loud'; +export const ALL_MESSAGES = 'all_messages'; +export const MENTIONS_ONLY = 'mentions_only'; +export const MUTE = 'mute'; + export function getRoomNotifsState(roomId) { // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. const muteRule = findOverrideMuteRule(roomId); if (muteRule && muteRule.enabled) { - return 'mute'; + return MUTE; } // for everything else, look at the room rule. @@ -32,14 +37,14 @@ export function getRoomNotifsState(roomId) { // XXX: We have to assume the default is to notify for all messages // (in particular this will be 'wrong' for one to one rooms because // they will notify loudly for all messages) - if (!roomRule || !roomRule.enabled) return 'all_messages'; + if (!roomRule || !roomRule.enabled) return ALL_MESSAGES; // a mute at the room level will still allow mentions // to notify - if (isMuteRule(roomRule)) return 'mentions_only'; + if (isMuteRule(roomRule)) return MENTIONS_ONLY; const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); - if (actionsObject.tweaks.sound) return 'all_messages_loud'; + if (actionsObject.tweaks.sound) return ALL_MESSAGES_LOUD; return null; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 8a7b83cb44..5b508b6ad0 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -54,18 +54,18 @@ module.exports = React.createClass({ }, _getNotifState: function() { - if (MatrixClientPeg.get().isGuest()) return 'all_messages'; + if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; return RoomNotifs.getRoomNotifsState(this.props.room.roomId); }, _shouldShowNotifBadge: function() { - const showBadgeInStates = ['all_messages', 'all_messages_loud']; + const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; const currentState = this._getNotifState(); return showBadgeInStates.indexOf(currentState) > -1; }, _shouldShowMentionBadge: function() { - return this._getNotifState() != 'mute'; + return this._getNotifState() != RoomNotifs.MUTE; }, onAccountData: function(accountDataEvent) { @@ -184,8 +184,9 @@ module.exports = React.createClass({ var notificationCount = this.props.room.getUnreadNotificationCount(); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); - var badges = notificationCount > 0 && this._shouldShowNotifBadge(); - badges |= this.props.highlight && this._shouldShowMentionBadge(); + const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); + const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); + const badges = notifBadges || mentionBadges; var classes = classNames({ 'mx_RoomTile': true, From af48b8920ec8d192d82869176b7839b4216fd6e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 14:00:14 +0100 Subject: [PATCH 27/31] Various PR feedback --- src/RoomNotifs.js | 107 ++++++++++++++----------- src/components/views/rooms/RoomTile.js | 16 ++-- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 6f792df26b..563491b695 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -24,10 +24,12 @@ export const MENTIONS_ONLY = 'mentions_only'; export const MUTE = 'mute'; export function getRoomNotifsState(roomId) { + if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; + // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. const muteRule = findOverrideMuteRule(roomId); - if (muteRule && muteRule.enabled) { + if (muteRule) { return MUTE; } @@ -50,58 +52,71 @@ export function getRoomNotifsState(roomId) { } export function setRoomNotifsState(roomId, newState) { + if (newState == 'mute') { + return setRoomNotifsStateMuted(roomId); + } else { + return setRoomNotifsStateUnmuted(roomId, newState); + } +} + +function setRoomNotifsStateMuted(roomId) { const cli = MatrixClientPeg.get(); const promises = []; - if (newState == 'mute') { - // delete the room rule - const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); - if (roomRule) { - promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); - } + // delete the room rule + const roomRule = cli.getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } - // add an override rule to squelch everything in this room - promises.push(cli.addPushRule('global', 'override', roomId, { - conditions: [ - { - kind: 'event_match', - key: 'room_id', - pattern: roomId, - } - ], + // add an override rule to squelch everything in this room + promises.push(cli.addPushRule('global', 'override', roomId, { + conditions: [ + { + kind: 'event_match', + key: 'room_id', + pattern: roomId, + } + ], + actions: [ + 'dont_notify', + ] + })); + + return q.all(promises); +} + +function setRoomNotifsStateUnmuted(roomId, newState) { + const cli = MatrixClientPeg.get(); + const promises = []; + + const overrideMuteRule = findOverrideMuteRule(roomId); + if (overrideMuteRule) { + promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + } + + if (newState == 'all_messages') { + promises.push(cli.deletePushRule('global', 'room', roomId)); + } else if (newState == 'mentions_only') { + promises.push(cli.addPushRule('global', 'room', roomId, { actions: [ 'dont_notify', ] })); - } else { - const overrideMuteRule = findOverrideMuteRule(roomId); - if (overrideMuteRule) { - promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); - } - - if (newState == 'all_messages') { - promises.push(cli.deletePushRule('global', 'room', roomId)); - } else if (newState == 'mentions_only') { - promises.push(cli.addPushRule('global', 'room', roomId, { - actions: [ - 'dont_notify', - ] - })); - // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); - } else if ('all_messages_loud') { - promises.push(cli.addPushRule('global', 'room', roomId, { - actions: [ - 'notify', - { - set_tweak: 'sound', - value: 'default', - } - ] - })); - // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); - } + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } else if ('all_messages_loud') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'notify', + { + set_tweak: 'sound', + value: 'default', + } + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); } return q.all(promises); @@ -110,7 +125,7 @@ export function setRoomNotifsState(roomId, newState) { function findOverrideMuteRule(roomId) { for (const rule of MatrixClientPeg.get().pushRules['global'].override) { if (isRuleForRoom(roomId, rule)) { - if (isMuteRule(rule)) { + if (isMuteRule(rule) && rule.enabled) { return rule; } } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 5b508b6ad0..e520b0bb00 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -49,29 +49,23 @@ module.exports = React.createClass({ badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - notifState: this._getNotifState(), + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), }); }, - _getNotifState: function() { - if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; - return RoomNotifs.getRoomNotifsState(this.props.room.roomId); - }, - _shouldShowNotifBadge: function() { const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; - const currentState = this._getNotifState(); - return showBadgeInStates.indexOf(currentState) > -1; + return showBadgeInStates.indexOf(this.state.notifState) > -1; }, _shouldShowMentionBadge: function() { - return this._getNotifState() != RoomNotifs.MUTE; + return this.state.notifState != RoomNotifs.MUTE; }, onAccountData: function(accountDataEvent) { if (accountDataEvent.getType() == 'm.push_rules') { this.setState({ - notifState: this._getNotifState(), + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), }); } }, @@ -193,7 +187,7 @@ module.exports = React.createClass({ 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), - 'mx_RoomTile_highlight': this.props.highlight && badges, + 'mx_RoomTile_highlight': this.props.highlight && this._shouldShowMentionBadge(), 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, 'mx_RoomTile_noBadges': !badges, From bab2f23db3b8547f87df5f7fb47d7e979dee47ba Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:18:02 +0100 Subject: [PATCH 28/31] Oops, missed a constant --- src/RoomNotifs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 563491b695..f545275641 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -52,7 +52,7 @@ export function getRoomNotifsState(roomId) { } export function setRoomNotifsState(roomId, newState) { - if (newState == 'mute') { + if (newState == MUTE) { return setRoomNotifsStateMuted(roomId); } else { return setRoomNotifsStateUnmuted(roomId, newState); From fc2c62e8964fcaedff8f65fe9d7eea95cf2c6c0d Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:19:24 +0100 Subject: [PATCH 29/31] We can use the new consts here --- src/components/views/rooms/RoomTile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index e520b0bb00..dd1ca125aa 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -186,8 +186,8 @@ module.exports = React.createClass({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), - 'mx_RoomTile_highlight': this.props.highlight && this._shouldShowMentionBadge(), + 'mx_RoomTile_unreadNotify': notifBadges, + 'mx_RoomTile_highlight': mentionBadges, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, 'mx_RoomTile_noBadges': !badges, From d08f7166815a6b100e2145510244f3c504c785fa Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:21:46 +0100 Subject: [PATCH 30/31] Comment override rule stuff --- src/RoomNotifs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index f545275641..ea1e525769 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -69,7 +69,11 @@ function setRoomNotifsStateMuted(roomId) { promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); } - // add an override rule to squelch everything in this room + // add/replace an override rule to squelch everything in this room + // NB. We use the room ID as the name of this rule too, although this + // is an override rule, not a room rule: it still pertains to this room + // though, so using the room ID as the rule ID is logical and prevents + // duplicate copies of the rule. promises.push(cli.addPushRule('global', 'override', roomId, { conditions: [ { From 5495cfaca9a7b35a238cc0f158c852348c355742 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 16:59:25 +0100 Subject: [PATCH 31/31] Only try to delete room rule if it exists --- src/RoomNotifs.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index ea1e525769..00cad23791 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -100,7 +100,10 @@ function setRoomNotifsStateUnmuted(roomId, newState) { } if (newState == 'all_messages') { - promises.push(cli.deletePushRule('global', 'room', roomId)); + const roomRule = cli.getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } } else if (newState == 'mentions_only') { promises.push(cli.addPushRule('global', 'room', roomId, { actions: [