diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f946d7cc..b99944185e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,224 @@ +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) +=================================================================================================== +[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) diff --git a/package.json b/package.json index f4817d7199..c6ece7e20d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.3", + "version": "0.6.4-r1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -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", 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. 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/Lifecycle.js b/src/Lifecycle.js index bc7d24c707..ee82a8890a 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,23 +112,29 @@ 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, + deviceId: data.device_id, accessToken: data.access_token, homeserverUrl: queryParams.homeserver, identityServerUrl: queryParams.identityServer, @@ -139,14 +146,26 @@ function _loginWithToken(queryParams) { }); } -function _registerAsGuest(hsUrl, isUrl) { +function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { console.log("Doing guest login on %s", hsUrl); - MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl); - return MatrixClientPeg.get().registerGuest().then((creds) => { + // 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({ + body: { + initial_device_display_name: defaultDeviceDisplayName, + }, + }).then((creds) => { 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, @@ -166,6 +185,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) { @@ -179,6 +199,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, @@ -206,10 +227,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_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); @@ -286,7 +316,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 +324,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..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, } @@ -67,26 +57,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,32 +72,29 @@ 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, identityServerUrl: this.matrixClient.idBaseUrl, userId: this.matrixClient.credentials.userId, + deviceId: this.matrixClient.getDeviceId(), accessToken: this.matrixClient.getAccessToken(), guest: this.matrixClient.isGuest(), }; } - _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, + deviceId: creds.deviceId, timelineSupport: true, }; if (localStorage) { opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage); - opts.deviceId = deviceId(); } this.matrixClient = Matrix.createClient(opts); @@ -130,7 +103,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/RoomNotifs.js b/src/RoomNotifs.js new file mode 100644 index 0000000000..00cad23791 --- /dev/null +++ b/src/RoomNotifs.js @@ -0,0 +1,164 @@ +/* +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 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) { + 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) { + 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) { + if (newState == MUTE) { + return setRoomNotifsStateMuted(roomId); + } else { + return setRoomNotifsStateUnmuted(roomId, newState); + } +} + +function setRoomNotifsStateMuted(roomId) { + const cli = MatrixClientPeg.get(); + const promises = []; + + // delete the room rule + const roomRule = cli.getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } + + // 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: [ + { + 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') { + 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: [ + '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) && rule.enabled) { + 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/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); } }); diff --git a/src/Signup.js b/src/Signup.js index 7a46005d11..1ac92f3218 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"); @@ -11,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() { @@ -31,14 +35,25 @@ 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, + }); + } } /** * 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) @@ -106,19 +121,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 +136,8 @@ class Register extends Signup { bindEmail = true; } - return MatrixClientPeg.get().register( + // 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 ).then(function(result) { @@ -152,7 +160,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 +169,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 +209,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 +218,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 +271,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 +281,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; } @@ -296,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 = []; @@ -305,15 +306,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,10 +328,15 @@ class Login extends Signup { } loginAsGuest() { - MatrixClientPeg.replaceUsingUrls(this._hsUrl, this._isUrl); - return MatrixClientPeg.get().registerGuest().then((creds) => { + var client = this._createTemporaryClient(); + return client.registerGuest({ + body: { + initial_device_display_name: this._defaultDeviceDisplayName, + }, + }).then((creds) => { return { userId: creds.user_id, + deviceId: creds.device_id, accessToken: creds.access_token, homeserverUrl: this._hsUrl, identityServerUrl: this._isUrl, @@ -357,7 +356,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'; @@ -366,11 +366,13 @@ 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, userId: data.user_id, + deviceId: data.device_id, accessToken: data.access_token }); }, function(error) { @@ -384,25 +386,20 @@ 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, userId: data.user_id, + deviceId: data.device_id, 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/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'; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index ffd4ad0a70..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,6 +189,7 @@ module.exports = React.createClass({ enableGuest: this.props.enableGuest, guestHsUrl: this.getCurrentHsUrl(), guestIsUrl: this.getCurrentIsUrl(), + defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, }).done(()=>{ // stuff this through the dispatcher so that it happens // after the on_logged_in action. @@ -193,7 +198,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 +606,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 +930,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'); @@ -1051,6 +1044,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} + defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} onLoggedIn={this.onRegistered} onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} @@ -1077,6 +1071,7 @@ module.exports = React.createClass({ customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} fallbackHsUrl={this.getFallbackHsUrl()} + defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} 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..0f1b6d331f 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); @@ -154,6 +159,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 +285,7 @@ module.exports = React.createClass({ var returnToAppJsx; if (this.props.onCancelClick) { - returnToAppJsx = + returnToAppJsx = Return to app 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/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 0b37847257..59e186da06 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,6 +67,11 @@ 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() }); @@ -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; } diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 7871e9d88e..fa3a7504f3 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -180,7 +180,7 @@ module.exports = React.createClass({ }, _doInvite(address) { - 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') { @@ -196,7 +196,7 @@ module.exports = React.createClass({ } } }).finally(() => { - self.setState({ + this.setState({ inviting: false }); // XXX: hacky focus on the invite box @@ -207,7 +207,7 @@ module.exports = React.createClass({ } }, 0); }).done(); - self.setState({ + this.setState({ inviting: true }); }, @@ -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 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 } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index ae0ffafae5..dd1ca125aa 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,41 @@ 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, + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), }); }, - 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; + _shouldShowNotifBadge: function() { + const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; + return showBadgeInStates.indexOf(this.state.notifState) > -1; + }, + + _shouldShowMentionBadge: function() { + return this.state.notifState != RoomNotifs.MUTE; + }, + + onAccountData: function(accountDataEvent) { + if (accountDataEvent.getType() == 'm.push_rules') { + this.setState({ + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), + }); } }, - 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() { @@ -179,15 +178,19 @@ module.exports = React.createClass({ var notificationCount = this.props.room.getUnreadNotificationCount(); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); + const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); + const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); + const badges = notifBadges || mentionBadges; + 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.areNotifsMuted, - 'mx_RoomTile_highlight': this.props.highlight, + 'mx_RoomTile_unreadNotify': notifBadges, + 'mx_RoomTile_highlight': mentionBadges, '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': !badges, }); var avatarClasses = classNames({ @@ -214,7 +217,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 (badges) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -230,7 +233,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': badges || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) { 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} +