Merge branch 'develop' into wmwragg/direct-chat-sublist
						commit
						6d1f9003e2
					
				
							
								
								
									
										221
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										221
									
								
								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 <span>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) | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
|  | @ -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 }; | ||||
|         }, | ||||
|     }, | ||||
|  |  | |||
|  | @ -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(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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' | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
|  | @ -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); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										109
									
								
								src/Signup.js
								
								
								
								
							
							
						
						
									
										109
									
								
								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; | ||||
|                     }); | ||||
|  |  | |||
|  | @ -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'; | ||||
|     } | ||||
|  |  | |||
|  | @ -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} | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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 = | ||||
|                 <a className="mx_Login_create" onClick={this.props.onCancelClick} href="#"> | ||||
|                     Return to app | ||||
|                 </a> | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ module.exports = React.createClass({ | |||
|             return ( | ||||
|                 <span className="mx_MFileBody"> | ||||
|                     <div className="mx_MImageBody_download"> | ||||
|                         <a href={cli.mxcUrlToHttp(content.url)} target="_blank"> | ||||
|                         <a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener"> | ||||
|                             <TintableSvg src="img/download.svg" width="12" height="14"/> | ||||
|                             Download {text} | ||||
|                         </a> | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ module.exports = React.createClass({ | |||
|                             onMouseLeave={this.onImageLeave} /> | ||||
|                     </a> | ||||
|                     <div className="mx_MImageBody_download"> | ||||
|                         <a href={cli.mxcUrlToHttp(content.url)} target="_blank"> | ||||
|                         <a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener"> | ||||
|                             <TintableSvg src="img/download.svg" width="12" height="14"/> | ||||
|                             Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" }) | ||||
|                         </a> | ||||
|  |  | |||
|  | @ -123,7 +123,7 @@ module.exports = React.createClass({ | |||
|             <div className="mx_LinkPreviewWidget" > | ||||
|                 { img } | ||||
|                 <div className="mx_LinkPreviewWidget_caption"> | ||||
|                     <div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank">{ p["og:title"] }</a></div> | ||||
|                     <div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank" rel="noopener">{ p["og:title"] }</a></div> | ||||
|                     <div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div> | ||||
|                     <div className="mx_LinkPreviewWidget_description" ref="description"> | ||||
|                         { p["og:description"] } | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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 } | ||||
| 
 | ||||
|                 <div className="mx_RoomSettings_toggles"> | ||||
|                     <label> | ||||
|                         <input type="checkbox" disabled={ cli.isGuest() } | ||||
|                                onChange={this._onToggle.bind(this, "areNotifsMuted", true, false)} | ||||
|                                defaultChecked={this.state.areNotifsMuted}/> | ||||
|                         'Mention only' notifications for this room | ||||
|                     </label> | ||||
|                     <div className="mx_RoomSettings_settings"> | ||||
|                         <h3>Who can access this room?</h3> | ||||
|                         { inviteGuestWarning } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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 ( | ||||
|             <div className={classes}> | ||||
|                 <div className="mx_DevicesPanel_header"> | ||||
|                     <div className="mx_DevicesPanel_deviceId">ID</div> | ||||
|                     <div className="mx_DevicesPanel_deviceName">Name</div> | ||||
|                     <div className="mx_DevicesPanel_deviceLastSeen">Last seen</div> | ||||
|                     <div className="mx_DevicesPanel_deviceButtons"></div> | ||||
|  |  | |||
|  | @ -109,6 +109,9 @@ export default class DevicesPanelEntry extends React.Component { | |||
| 
 | ||||
|         return ( | ||||
|             <div className="mx_DevicesPanel_device"> | ||||
|                 <div className="mx_DevicesPanel_deviceId"> | ||||
|                     {device.device_id} | ||||
|                 </div> | ||||
|                 <div className="mx_DevicesPanel_deviceName"> | ||||
|                     <EditableTextContainer initialValue={device.display_name} | ||||
|                         onSubmit={this._onDisplayNameChanged} | ||||
|  |  | |||
|  | @ -137,6 +137,10 @@ matrixLinkify.options = { | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     linkAttributes: { | ||||
|         rel: 'noopener', | ||||
|     }, | ||||
| 
 | ||||
|     target: function(href, type) { | ||||
|         if (type === 'url') { | ||||
|             if (href.match(matrixLinkify.VECTOR_URL_PATTERN)) { | ||||
|  |  | |||
|  | @ -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() { | |||
|         }, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 wmwragg
						wmwragg