From 7dc0a28e17bff5172c303497b6c1ca40af23e7e9 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 11:36:11 +0200 Subject: Created m-file-input. A directive to open a file selection dialog on whatever HTML element --- .../components/fileInput/file-input-directive.js | 43 ++++++++++++++++++++++ webclient/index.html | 1 + 2 files changed, 44 insertions(+) create mode 100644 webclient/components/fileInput/file-input-directive.js (limited to 'webclient') diff --git a/webclient/components/fileInput/file-input-directive.js b/webclient/components/fileInput/file-input-directive.js new file mode 100644 index 0000000000..9b73f877e9 --- /dev/null +++ b/webclient/components/fileInput/file-input-directive.js @@ -0,0 +1,43 @@ +/* + Copyright 2014 matrix.org + + 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. + */ + +'use strict'; + +/* + * Transform an element into an image file input button. + * Watch to the passed variable change. It will contain the selected HTML5 file object. + */ +angular.module('mFileInput', []) +.directive('mFileInput', function() { + return { + restrict: 'A', + transclude: 'true', + template: '
', + scope: { + selectedFile: '=mFileInput' + }, + + link: function(scope, element, attrs, ctrl) { + element.bind("click", function() { + element.find("input")[0].click(); + element.find("input").bind("change", function(e) { + scope.selectedFile = this.files[0]; + scope.$apply(); + }); + }); + } + }; +}); \ No newline at end of file diff --git a/webclient/index.html b/webclient/index.html index ddc9ab5e32..f4b791ecdb 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -14,6 +14,7 @@ + -- cgit 1.5.1 From 28a49a9eaf79f1b05fc2f793ad264d45c017de4c Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 11:39:03 +0200 Subject: Show avatar in profile section and added a button to select a file (not yet wired to upload service) --- webclient/app.css | 14 ++++++++++++++ webclient/rooms/rooms-controller.js | 12 ++++++++++-- webclient/rooms/rooms.html | 25 +++++++++++++++++++++---- 3 files changed, 45 insertions(+), 6 deletions(-) (limited to 'webclient') diff --git a/webclient/app.css b/webclient/app.css index 65049c95c9..122f25c9ff 100644 --- a/webclient/app.css +++ b/webclient/app.css @@ -219,6 +219,20 @@ h1 { background-color: #fff ! important; } +/*** Profile ***/ + +.profile-avatar { + width: 160px; + height: 160px; + display:table-cell; + vertical-align: middle; +} + +.profile-avatar img { + max-width: 100%; + max-height: 100%; +} + /******************************/ .header { diff --git a/webclient/rooms/rooms-controller.js b/webclient/rooms/rooms-controller.js index 293ea8bc8b..b7f19bb2b5 100644 --- a/webclient/rooms/rooms-controller.js +++ b/webclient/rooms/rooms-controller.js @@ -16,7 +16,7 @@ limitations under the License. 'use strict'; -angular.module('RoomsController', ['matrixService']) +angular.module('RoomsController', ['matrixService', 'mFileInput']) .controller('RoomsController', ['$scope', '$location', 'matrixService', function($scope, $location, matrixService) { @@ -40,7 +40,8 @@ angular.module('RoomsController', ['matrixService']) $scope.newProfileInfo = { name: matrixService.config().displayName, - avatar: matrixService.config().avatarUrl + avatar: matrixService.config().avatarUrl, + avatarFile: undefined }; $scope.linkedEmails = { @@ -163,6 +164,13 @@ angular.module('RoomsController', ['matrixService']) ); }; + + $scope.$watch("newProfileInfo.avatarFile", function(newValue, oldValue) { + if ($scope.newProfileInfo.avatarFile) { + //@TODO: Upload this HTML5 image file to somewhere + } + }); + $scope.setAvatar = function(newUrl) { console.log("Updating avatar to "+newUrl); matrixService.setProfilePictureUrl(newUrl).then( diff --git a/webclient/rooms/rooms.html b/webclient/rooms/rooms.html index d303e143b9..66b89caf00 100644 --- a/webclient/rooms/rooms.html +++ b/webclient/rooms/rooms.html @@ -5,16 +5,33 @@
- - + + + + + +
+
+ +
+
+ + or use an existing image URL: +
+ + +
+
+
- - + +
+
-- cgit 1.5.1 From 60b0fca1036fd0cd9db8bcf518171ca55c4d1af8 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 11:51:31 +0200 Subject: Use ng-src --- webclient/rooms/rooms.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'webclient') diff --git a/webclient/rooms/rooms.html b/webclient/rooms/rooms.html index 66b89caf00..5974bd940c 100644 --- a/webclient/rooms/rooms.html +++ b/webclient/rooms/rooms.html @@ -9,7 +9,7 @@
- +
-- cgit 1.5.1 From d5033849a5e42e401ddee3faa731a55ad11d11ca Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 13:51:35 +0200 Subject: BF: Use ng-src --- webclient/room/room.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'webclient') diff --git a/webclient/room/room.html b/webclient/room/room.html index 8fc7d5d360..91e900c678 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -35,7 +35,7 @@
{{ msg.content.msgtype === "m.emote" ? ("* " + (members[msg.user_id].displayname || msg.user_id) + " " + msg.content.body) : "" }} {{ msg.content.msgtype === "m.text" ? msg.content.body : "" }} - {{ msg.content.body }} + {{ msg.content.body }}
-- cgit 1.5.1 From 613e468b89cac37e4537b1798aee98d824b55cb3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 13:57:55 +0100 Subject: Guess the home server URL on the login screen by inspecting the URL of the web client. --- webclient/login/login-controller.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'webclient') diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 26590da686..fa91bf4253 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -3,8 +3,16 @@ angular.module('LoginController', ['matrixService']) function($scope, $location, matrixService) { 'use strict'; + + // Assume that this is hosted on the home server, in which case the URL + // contains the home server. + var hs_url = $location.protocol() + "://" + $location.host(); + if ($location.port()) { + hs_url += ":" + $location.port(); + } + $scope.account = { - homeserver: "http://localhost:8080", + homeserver: hs_url, desired_user_name: "", user_id: "", password: "", -- cgit 1.5.1 From 7143f358f1487d4044cc5ad64056f621a5aa2139 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 14:59:33 +0200 Subject: Detect when the user access token is no more valid and log the user out in this case --- webclient/app-controller.js | 10 ++++++++-- webclient/components/matrix/matrix-service.js | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'webclient') diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 41055bdcd2..086fa3d946 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -55,8 +55,14 @@ angular.module('MatrixWebClientController', ['matrixService']) // And go to the login page $location.path("login"); - }; - + }; + + // Listen to the event indicating that the access token is no more valid. + // In this case, the user needs to log in again. + $scope.$on("M_UNKNOWN_TOKEN", function() { + console.log("Invalid access token -> log user out"); + $scope.logout(); + }); }]); \ No newline at end of file diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index f054bf301e..81ccdc2cc0 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; angular.module('matrixService', []) -.factory('matrixService', ['$http', '$q', function($http, $q) { +.factory('matrixService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) { /* * Permanent storage of user information @@ -60,7 +60,6 @@ angular.module('matrixService', []) headers: headers }) .success(function(data, status, headers, config) { - // @TODO: We could detect a bad access token here and make an automatic logout deferred.resolve(data, status, headers, config); }) .error(function(data, status, headers, config) { @@ -70,6 +69,11 @@ angular.module('matrixService', []) reason = JSON.stringify(data); } deferred.reject(reason, data, status, headers, config); + + if (403 === status && "M_UNKNOWN_TOKEN" === data.errcode) { + // The access token is no more valid, broadcast the issue + $rootScope.$broadcast("M_UNKNOWN_TOKEN"); + } }); return deferred.promise; @@ -301,6 +305,12 @@ angular.module('matrixService', []) return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, + + // + testLogin: function() { + + }, + /****** Permanent storage of user information ******/ // Returns the current config -- cgit 1.5.1 From 76005c44f7ec4ea28ba0d5eecccfa64f4df6d664 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 15:21:39 +0100 Subject: Added an access token interceptor to check unknown tokens. --- webclient/app.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'webclient') diff --git a/webclient/app.js b/webclient/app.js index 651aeeaa77..f869309449 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -23,8 +23,8 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'matrixService' ]); -matrixWebClient.config(['$routeProvider', - function($routeProvider) { +matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', + function($routeProvider, $provide, $httpProvider) { $routeProvider. when('/login', { templateUrl: 'login/login.html', @@ -41,6 +41,22 @@ matrixWebClient.config(['$routeProvider', otherwise({ redirectTo: '/rooms' }); + + $provide.factory('AccessTokenInterceptor', function ($q) { + return { + responseError: function(rejection) { + console.log("Rejection: " + JSON.stringify(rejection)); + if (rejection.status === 403 && "data" in rejection && + "errcode" in rejection.data && + rejection.data.errcode === "M_UNKNOWN_TOKEN") { + console.log("TODO: Got a 403 with an unknown token. Logging out.") + // TODO logout + } + return $q.reject(rejection); + } + }; + }); + $httpProvider.interceptors.push('AccessTokenInterceptor'); }]); matrixWebClient.run(['$location', 'matrixService' , function($location, matrixService) { @@ -75,4 +91,4 @@ matrixWebClient return function(text) { return $sce.trustAsHtml(text); }; - }]); \ No newline at end of file + }]); -- cgit 1.5.1 From db3e1d73c6a81bda3b2624596ea9b3f113242d38 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 15:36:40 +0100 Subject: Move the unknown token broadcast to the interceptor. Return the $http promise and not a wrapped one via $q. Everything now needs a level deeper nesting. Fixed registration and login. --- webclient/app.js | 10 +++++----- webclient/components/matrix/matrix-service.js | 21 +-------------------- webclient/login/login-controller.js | 14 +++++++++----- webclient/login/login.html | 1 + 4 files changed, 16 insertions(+), 30 deletions(-) (limited to 'webclient') diff --git a/webclient/app.js b/webclient/app.js index f869309449..0b613fa206 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -42,20 +42,20 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', redirectTo: '/rooms' }); - $provide.factory('AccessTokenInterceptor', function ($q) { + $provide.factory('AccessTokenInterceptor', ['$q', '$rootScope', + function ($q, $rootScope) { return { responseError: function(rejection) { - console.log("Rejection: " + JSON.stringify(rejection)); if (rejection.status === 403 && "data" in rejection && "errcode" in rejection.data && rejection.data.errcode === "M_UNKNOWN_TOKEN") { - console.log("TODO: Got a 403 with an unknown token. Logging out.") - // TODO logout + console.log("Got a 403 with an unknown token. Logging out.") + $rootScope.$broadcast("M_UNKNOWN_TOKEN"); } return $q.reject(rejection); } }; - }); + }]); $httpProvider.interceptors.push('AccessTokenInterceptor'); }]); diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 81ccdc2cc0..132c996f7a 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -49,32 +49,13 @@ angular.module('matrixService', []) if (path.indexOf(prefixPath) !== 0) { path = prefixPath + path; } - // Do not directly return the $http instance but return a promise - // with enriched or cleaned information - var deferred = $q.defer(); - $http({ + return $http({ method: method, url: baseUrl + path, params: params, data: data, headers: headers }) - .success(function(data, status, headers, config) { - deferred.resolve(data, status, headers, config); - }) - .error(function(data, status, headers, config) { - // Enrich the error callback with an human readable error reason - var reason = data.error; - if (!data.error) { - reason = JSON.stringify(data); - } - deferred.reject(reason, data, status, headers, config); - - if (403 === status && "M_UNKNOWN_TOKEN" === data.errcode) { - // The access token is no more valid, broadcast the issue - $rootScope.$broadcast("M_UNKNOWN_TOKEN"); - } - }); return deferred.promise; }; diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index fa91bf4253..c519f7698c 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -39,14 +39,13 @@ angular.module('LoginController', ['matrixService']) } matrixService.register($scope.account.desired_user_name, $scope.account.pwd1).then( - function(data) { + function(response) { $scope.feedback = "Success"; - // Update the current config var config = matrixService.config(); angular.extend(config, { - access_token: data.access_token, - user_id: data.user_id + access_token: response.data.access_token, + user_id: response.data.user_id }); matrixService.setConfig(config); @@ -74,7 +73,7 @@ angular.module('LoginController', ['matrixService']) matrixService.setConfig({ homeserver: $scope.account.homeserver, user_id: $scope.account.user_id, - access_token: response.access_token + access_token: response.data.access_token }); matrixService.saveConfig(); $location.path("rooms"); @@ -82,6 +81,11 @@ angular.module('LoginController', ['matrixService']) else { $scope.feedback = "Failed to login: " + JSON.stringify(response); } + }, + function(error) { + if (error.data.errcode === "M_FORBIDDEN") { + $scope.login_error_msg = "Incorrect username or password."; + } } ); }; diff --git a/webclient/login/login.html b/webclient/login/login.html index 508ff5e4bf..f02dde89a6 100644 --- a/webclient/login/login.html +++ b/webclient/login/login.html @@ -22,6 +22,7 @@

Got an account?

+
{{ login_error_msg }}

-- cgit 1.5.1 From 24bd133d9d4c9a30c4609cf6d55f02ab6f05c142 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 15:43:16 +0100 Subject: Added extra nesting .data and rename callback to be response not data --- webclient/login/login-controller.js | 4 +-- webclient/rooms/rooms-controller.js | 58 ++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 31 deletions(-) (limited to 'webclient') diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index c519f7698c..015868b0b9 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -68,7 +68,7 @@ angular.module('LoginController', ['matrixService']) // try to login matrixService.login($scope.account.user_id, $scope.account.password).then( function(response) { - if ("access_token" in response) { + if ("access_token" in response.data) { $scope.feedback = "Login successful."; matrixService.setConfig({ homeserver: $scope.account.homeserver, @@ -79,7 +79,7 @@ angular.module('LoginController', ['matrixService']) $location.path("rooms"); } else { - $scope.feedback = "Failed to login: " + JSON.stringify(response); + $scope.feedback = "Failed to login: " + JSON.stringify(response.data); } }, function(error) { diff --git a/webclient/rooms/rooms-controller.js b/webclient/rooms/rooms-controller.js index b7f19bb2b5..d0924f5887 100644 --- a/webclient/rooms/rooms-controller.js +++ b/webclient/rooms/rooms-controller.js @@ -75,18 +75,18 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) // List all rooms joined or been invited to $scope.rooms = matrixService.rooms(); matrixService.rooms().then( - function(data) { - data = assignRoomAliases(data); + function(response) { + var data = assignRoomAliases(response.data); $scope.feedback = "Success"; $scope.rooms = data; }, - function(reason) { - $scope.feedback = "Failure: " + reason; + function(error) { + $scope.feedback = "Failure: " + error.data; }); matrixService.publicRooms().then( - function(data) { - $scope.public_rooms = assignRoomAliases(data.chunk); + function(response) { + $scope.public_rooms = assignRoomAliases(response.data.chunk); } ); }; @@ -101,14 +101,14 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) matrixService.create(room_id, visibility).then( function(response) { // This room has been created. Refresh the rooms list - console.log("Created room " + response.room_alias + " with id: "+ - response.room_id); + console.log("Created room " + response.data.room_alias + " with id: "+ + response.data.room_id); matrixService.createRoomIdToAliasMapping( - response.room_id, response.room_alias); + response.data.room_id, response.data.room_alias); $scope.refresh(); }, - function(reason) { - $scope.feedback = "Failure: " + reason; + function(error) { + $scope.feedback = "Failure: " + error.data; }); }; @@ -118,17 +118,17 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) //$location.path("room/" + room_id); matrixService.join(room_id).then( function(response) { - if (response.hasOwnProperty("room_id")) { - if (response.room_id != room_id) { - $location.path("room/" + response.room_id); + if (response.data.hasOwnProperty("room_id")) { + if (response.data.room_id != room_id) { + $location.path("room/" + response.data.room_id); return; } } $location.path("room/" + room_id); }, - function(reason) { - $scope.feedback = "Can't join room: " + reason; + function(error) { + $scope.feedback = "Can't join room: " + error.data; } ); }; @@ -136,15 +136,15 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) $scope.joinAlias = function(room_alias) { matrixService.joinAlias(room_alias).then( function(response) { - if (response.hasOwnProperty("room_id")) { - $location.path("room/" + response.room_id); + if (response.data.hasOwnProperty("room_id")) { + $location.path("room/" + response.data.room_id); return; } else { // TODO (erikj): Do something here? } }, - function(reason) { - $scope.feedback = "Can't join room: " + reason; + function(error) { + $scope.feedback = "Can't join room: " + error.data; } ); }; @@ -158,8 +158,8 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) matrixService.setConfig(config); matrixService.saveConfig(); }, - function(reason) { - $scope.feedback = "Can't update display name: " + reason; + function(error) { + $scope.feedback = "Can't update display name: " + error.data; } ); }; @@ -182,8 +182,8 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) matrixService.setConfig(config); matrixService.saveConfig(); }, - function(reason) { - $scope.feedback = "Can't update avatar: " + reason; + function(error) { + $scope.feedback = "Can't update avatar: " + error.data; } ); }; @@ -191,8 +191,8 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) $scope.linkEmail = function(email) { matrixService.linkEmail(email).then( function(response) { - if (response.success === true) { - $scope.linkedEmails.authTokenId = response.tokenId; + if (response.data.success === true) { + $scope.linkedEmails.authTokenId = response.data.tokenId; $scope.emailFeedback = "You have been sent an email."; $scope.linkedEmails.emailBeingAuthed = email; } @@ -200,8 +200,8 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) $scope.emailFeedback = "Failed to send email."; } }, - function(reason) { - $scope.emailFeedback = "Can't send email: " + reason; + function(error) { + $scope.emailFeedback = "Can't send email: " + error.data; } ); }; @@ -214,7 +214,7 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) } matrixService.authEmail(matrixService.config().user_id, tokenId, code).then( function(response) { - if ("success" in response && response.success === false) { + if ("success" in response.data && response.data.success === false) { $scope.emailFeedback = "Failed to authenticate email."; return; } -- cgit 1.5.1 From 40c998336d3562018162bfc14b4dade1139f414c Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 15:47:38 +0100 Subject: Finish up room controller too. May have missed one or two, but testing didn't pick anything up. --- webclient/room/room-controller.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'webclient') diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 470f41521a..cec19f7994 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -99,8 +99,8 @@ angular.module('RoomController', []) function(response) { var member = $scope.members[chunk.target_user_id]; if (member !== undefined) { - console.log("Updated displayname "+chunk.target_user_id+" to " + response.displayname); - member.displayname = response.displayname; + console.log("Updated displayname "+chunk.target_user_id+" to " + response.data.displayname); + member.displayname = response.data.displayname; } } ); @@ -108,8 +108,8 @@ angular.module('RoomController', []) function(response) { var member = $scope.members[chunk.target_user_id]; if (member !== undefined) { - console.log("Updated image for "+chunk.target_user_id+" to " + response.avatar_url); - member.avatar_url = response.avatar_url; + console.log("Updated image for "+chunk.target_user_id+" to " + response.data.avatar_url); + member.avatar_url = response.data.avatar_url; } } ); @@ -171,8 +171,8 @@ angular.module('RoomController', []) console.log("Sent message"); $scope.textInput = ""; }, - function(reason) { - $scope.feedback = "Failed to send: " + reason; + function(error) { + $scope.feedback = "Failed to send: " + error.data.error; }); }; @@ -190,13 +190,13 @@ angular.module('RoomController', []) // Get the current member list matrixService.getMemberList($scope.room_id).then( function(response) { - for (var i = 0; i < response.chunk.length; i++) { - var chunk = response.chunk[i]; + for (var i = 0; i < response.data.chunk.length; i++) { + var chunk = response.data.chunk[i]; updateMemberList(chunk); } }, - function(reason) { - $scope.feedback = "Failed get member list: " + reason; + function(error) { + $scope.feedback = "Failed get member list: " + error.data.error; } ); }, @@ -224,8 +224,8 @@ angular.module('RoomController', []) console.log("Left room "); $location.path("rooms"); }, - function(reason) { - $scope.feedback = "Failed to leave room: " + reason; + function(error) { + $scope.feedback = "Failed to leave room: " + error.data.error; }); }; @@ -234,8 +234,8 @@ angular.module('RoomController', []) function() { console.log("Image sent"); }, - function(reason) { - $scope.feedback = "Failed to send image: " + reason; + function(error) { + $scope.feedback = "Failed to send image: " + error.data.error; }); }; -- cgit 1.5.1 From fb93e14e530d6dfddf22d77eb42be5758f2d50f5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 16:03:04 +0100 Subject: Be more helpful when failing to register/login, stating why (communication error, user in user, wrong credentials, etc). Make the HS send M_USER_IN_USE. --- synapse/storage/registration.py | 4 ++-- webclient/login/login-controller.js | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'webclient') diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py index 68cdfbb4ca..b1e4196435 100644 --- a/synapse/storage/registration.py +++ b/synapse/storage/registration.py @@ -17,7 +17,7 @@ from twisted.internet import defer from sqlite3 import IntegrityError -from synapse.api.errors import StoreError +from synapse.api.errors import StoreError, Codes from ._base import SQLBaseStore @@ -73,7 +73,7 @@ class RegistrationStore(SQLBaseStore): "VALUES (?,?,?)", [user_id, password_hash, now]) except IntegrityError: - raise StoreError(400, "User ID already taken.") + raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE) # it's possible for this to get a conflict, but only for a single user # since tokens are namespaced based on their user ID diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 015868b0b9..53756be9ea 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -55,8 +55,15 @@ angular.module('LoginController', ['matrixService']) // Go to the user's rooms list page $location.path("rooms"); }, - function(reason) { - $scope.feedback = "Failure: " + reason; + function(error) { + if (error.data) { + if (error.data.errcode === "M_USER_IN_USE") { + $scope.feedback = "Username already taken."; + } + } + else if (error.status === 0) { + $scope.feedback = "Unable to talk to the server."; + } }); }; @@ -83,8 +90,13 @@ angular.module('LoginController', ['matrixService']) } }, function(error) { - if (error.data.errcode === "M_FORBIDDEN") { - $scope.login_error_msg = "Incorrect username or password."; + if (error.data) { + if (error.data.errcode === "M_FORBIDDEN") { + $scope.login_error_msg = "Incorrect username or password."; + } + } + else if (error.status === 0) { + $scope.login_error_msg = "Unable to talk to the server."; } } ); -- cgit 1.5.1 From 6f925f61ff69392d1fbc478150de99ecad7ca6f5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 16:08:14 +0100 Subject: Auto-correct the username when logging in if there isn't an @ --- webclient/login/login-controller.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'webclient') diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 53756be9ea..826a533873 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -68,6 +68,12 @@ angular.module('LoginController', ['matrixService']) }; $scope.login = function() { + if ($scope.account.user_id.indexOf("@") !== 0) { + // technically should be the host of account.homeserver + $scope.account.user_id = "@" + $scope.account.user_id + ":" + + $location.host() + } + matrixService.setConfig({ homeserver: $scope.account.homeserver, user_id: $scope.account.user_id -- cgit 1.5.1 From ca3747fb2f9a1e2e6d5d9e55cd0760e373b57a90 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 16:29:24 +0100 Subject: hs: Make /login accept full user IDs or just local parts. webclient: Only enable Register button when both password fields match. --- synapse/handlers/login.py | 6 +++++- webclient/login/login-controller.js | 6 ------ webclient/login/login.html | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) (limited to 'webclient') diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py index 0220fa0604..5c7d503a24 100644 --- a/synapse/handlers/login.py +++ b/synapse/handlers/login.py @@ -16,6 +16,7 @@ from twisted.internet import defer from ._base import BaseHandler +from synapse.types import UserID from synapse.api.errors import LoginError, Codes import bcrypt @@ -35,7 +36,7 @@ class LoginHandler(BaseHandler): """Login as the specified user with the specified password. Args: - user (str): The user ID. + user (str): The user ID or username. password (str): The password. Returns: The newly allocated access token. @@ -47,6 +48,9 @@ class LoginHandler(BaseHandler): if not hasattr(self, "reg_handler"): self.reg_handler = self.hs.get_handlers().registration_handler + if not user.startswith('@'): + user = UserID.create_local(user, self.hs).to_string() + # pull out the hash for this user if they exist user_info = yield self.store.get_user_by_id(user_id=user) if not user_info: diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 826a533873..53756be9ea 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -68,12 +68,6 @@ angular.module('LoginController', ['matrixService']) }; $scope.login = function() { - if ($scope.account.user_id.indexOf("@") !== 0) { - // technically should be the host of account.homeserver - $scope.account.user_id = "@" + $scope.account.user_id + ":" + - $location.host() - } - matrixService.setConfig({ homeserver: $scope.account.homeserver, user_id: $scope.account.user_id diff --git a/webclient/login/login.html b/webclient/login/login.html index f02dde89a6..0fbeeabed7 100644 --- a/webclient/login/login.html +++ b/webclient/login/login.html @@ -15,7 +15,7 @@

- +
-- cgit 1.5.1 From fef3183461a60715cb5fb0638abcde335c61db82 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 16:40:15 +0100 Subject: Pass back the user_id in the response to /login in case it has changed. Store and use that on the webclient rather than the input field. --- synapse/handlers/login.py | 6 +----- synapse/rest/login.py | 6 ++++++ webclient/login/login-controller.js | 2 +- webclient/login/login.html | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) (limited to 'webclient') diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py index 5c7d503a24..0220fa0604 100644 --- a/synapse/handlers/login.py +++ b/synapse/handlers/login.py @@ -16,7 +16,6 @@ from twisted.internet import defer from ._base import BaseHandler -from synapse.types import UserID from synapse.api.errors import LoginError, Codes import bcrypt @@ -36,7 +35,7 @@ class LoginHandler(BaseHandler): """Login as the specified user with the specified password. Args: - user (str): The user ID or username. + user (str): The user ID. password (str): The password. Returns: The newly allocated access token. @@ -48,9 +47,6 @@ class LoginHandler(BaseHandler): if not hasattr(self, "reg_handler"): self.reg_handler = self.hs.get_handlers().registration_handler - if not user.startswith('@'): - user = UserID.create_local(user, self.hs).to_string() - # pull out the hash for this user if they exist user_info = yield self.store.get_user_by_id(user_id=user) if not user_info: diff --git a/synapse/rest/login.py b/synapse/rest/login.py index 88a3218332..bcf63fd2ab 100644 --- a/synapse/rest/login.py +++ b/synapse/rest/login.py @@ -16,6 +16,7 @@ from twisted.internet import defer from synapse.api.errors import SynapseError +from synapse.types import UserID from base import RestServlet, client_path_pattern import json @@ -45,12 +46,17 @@ class LoginRestServlet(RestServlet): @defer.inlineCallbacks def do_password_login(self, login_submission): + if not login_submission["user"].startswith('@'): + login_submission["user"] = UserID.create_local( + login_submission["user"], self.hs).to_string() + handler = self.handlers.login_handler token = yield handler.login( user=login_submission["user"], password=login_submission["password"]) result = { + "user_id": login_submission["user"], # may have changed "access_token": token, "home_server": self.hs.hostname, } diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 53756be9ea..8bd6a4e84f 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -79,7 +79,7 @@ angular.module('LoginController', ['matrixService']) $scope.feedback = "Login successful."; matrixService.setConfig({ homeserver: $scope.account.homeserver, - user_id: $scope.account.user_id, + user_id: response.data.user_id, access_token: response.data.access_token }); matrixService.saveConfig(); diff --git a/webclient/login/login.html b/webclient/login/login.html index 0fbeeabed7..a8b2b1f12d 100644 --- a/webclient/login/login.html +++ b/webclient/login/login.html @@ -24,7 +24,7 @@
{{ login_error_msg }}
- +


-- cgit 1.5.1 From 30da8c81c761a1f58c9643f41450240bfe1d6cc5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 17:23:47 +0100 Subject: webclient: You can now paginate in rooms. Defaults to 10 messages, with a button to get more (needs to be hooked into infini-scrolling). --- docs/client-server/specification.rst | 3 + webclient/components/matrix/matrix-service.js | 11 ++++ webclient/room/room-controller.js | 80 ++++++++++++++++++++------- webclient/room/room.html | 1 + 4 files changed, 74 insertions(+), 21 deletions(-) (limited to 'webclient') diff --git a/docs/client-server/specification.rst b/docs/client-server/specification.rst index 97c8587a6d..b82093f2d3 100644 --- a/docs/client-server/specification.rst +++ b/docs/client-server/specification.rst @@ -414,6 +414,9 @@ The server checks this, finds it is valid, and returns: { "access_token": "abcdef0123456789" } +The server may optionally return "user_id" to confirm or change the user's ID. +This is particularly useful if the home server wishes to support localpart entry +of usernames (e.g. "bob" rather than "@bob:matrix.org"). OAuth2-based ------------ diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 132c996f7a..6d66111469 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -212,6 +212,17 @@ angular.module('matrixService', []) path = path.replace("$room_id", room_id); return doRequest("GET", path); }, + + paginateBackMessages: function(room_id, from_token, limit) { + var path = "/rooms/$room_id/messages/list"; + path = path.replace("$room_id", room_id); + var params = { + from: from_token, + to: "START", + limit: limit + }; + return doRequest("GET", path, params); + }, // get a list of public rooms on your home server publicRooms: function() { diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index cec19f7994..8003105654 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -18,11 +18,14 @@ angular.module('RoomController', []) .controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', function($scope, $http, $timeout, $routeParams, $location, matrixService) { 'use strict'; + var MESSAGES_PER_PAGINATION = 10; $scope.room_id = $routeParams.room_id; $scope.room_alias = matrixService.getRoomIdToAliasMapping($scope.room_id); $scope.state = { user_id: matrixService.config().user_id, - events_from: "START" + events_from: "END", // when to start the event stream from. + earliest_token: "END", // stores how far back we've paginated. + can_paginate: true }; $scope.messages = []; $scope.members = {}; @@ -30,6 +33,53 @@ angular.module('RoomController', []) $scope.imageURLToSend = ""; $scope.userIDToInvite = ""; + + var scrollToBottom = function() { + $timeout(function() { + var objDiv = document.getElementsByClassName("messageTableWrapper")[0]; + objDiv.scrollTop = objDiv.scrollHeight; + },0); + }; + + var parseChunk = function(chunks, appendToStart) { + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + if (chunk.room_id == $scope.room_id && chunk.type == "m.room.message") { + if ("membership_target" in chunk.content) { + chunk.user_id = chunk.content.membership_target; + } + if (appendToStart) { + $scope.messages.unshift(chunk); + } + else { + $scope.messages.push(chunk); + scrollToBottom(); + } + } + else if (chunk.room_id == $scope.room_id && chunk.type == "m.room.member") { + updateMemberList(chunk); + } + else if (chunk.type === "m.presence") { + updatePresence(chunk); + } + } + }; + + var paginate = function(numItems) { + matrixService.paginateBackMessages($scope.room_id, $scope.state.earliest_token, numItems).then( + function(response) { + parseChunk(response.data.chunk, true); + $scope.state.earliest_token = response.data.end; + if (response.data.chunk.length < MESSAGES_PER_PAGINATION) { + // no more messages to paginate :( + $scope.state.can_paginate = false; + } + }, + function(error) { + console.log("paginateBackMessages Ruh roh: " + JSON.stringify(error)); + } + ) + }; var shortPoll = function() { $http.get(matrixService.config().homeserver + matrixService.prefix + "/events", { @@ -41,28 +91,10 @@ angular.module('RoomController', []) .then(function(response) { console.log("Got response from "+$scope.state.events_from+" to "+response.data.end); $scope.state.events_from = response.data.end; - $scope.feedback = ""; - for (var i = 0; i < response.data.chunk.length; i++) { - var chunk = response.data.chunk[i]; - if (chunk.room_id == $scope.room_id && chunk.type == "m.room.message") { - if ("membership_target" in chunk.content) { - chunk.user_id = chunk.content.membership_target; - } - $scope.messages.push(chunk); - $timeout(function() { - var objDiv = document.getElementsByClassName("messageTableWrapper")[0]; - objDiv.scrollTop = objDiv.scrollHeight; - },0); - } - else if (chunk.room_id == $scope.room_id && chunk.type == "m.room.member") { - updateMemberList(chunk); - } - else if (chunk.type === "m.presence") { - updatePresence(chunk); - } - } + parseChunk(response.data.chunk, false); + if ($scope.stopPoll) { console.log("Stopping polling."); } @@ -199,6 +231,8 @@ angular.module('RoomController', []) $scope.feedback = "Failed get member list: " + error.data.error; } ); + + paginate(MESSAGES_PER_PAGINATION); }, function(reason) { $scope.feedback = "Can't join room: " + reason; @@ -238,6 +272,10 @@ angular.module('RoomController', []) $scope.feedback = "Failed to send image: " + error.data.error; }); }; + + $scope.loadMoreHistory = function() { + paginate(MESSAGES_PER_PAGINATION); + }; $scope.$on('$destroy', function(e) { console.log("onDestroyed: Stopping poll."); diff --git a/webclient/room/room.html b/webclient/room/room.html index 91e900c678..0f86a158ec 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -86,6 +86,7 @@ +
-- cgit 1.5.1 From f5973d8ddb1d972221370022459e9c750c079ad8 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 18:38:42 +0200 Subject: Create a temporary upload service server side (by hacking demos/webserver.py) and client side with an angularjs service component. --- demo/webserver.py | 25 +++++++++++- .../components/fileUpload/file-upload-service.js | 47 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 webclient/components/fileUpload/file-upload-service.js (limited to 'webclient') diff --git a/demo/webserver.py b/demo/webserver.py index 78f3213540..875095c877 100644 --- a/demo/webserver.py +++ b/demo/webserver.py @@ -2,9 +2,32 @@ import argparse import BaseHTTPServer import os import SimpleHTTPServer +import cgi, logging from daemonize import Daemonize +class SimpleHTTPRequestHandlerWithPOST(SimpleHTTPServer.SimpleHTTPRequestHandler): + UPLOAD_PATH = "upload" + + """ + Accept all post request as file upload + """ + def do_POST(self): + + path = os.path.join(self.UPLOAD_PATH, os.path.basename(self.path)) + length = self.headers['content-length'] + data = self.rfile.read(int(length)) + + with open(path, 'wb') as fh: + fh.write(data) + + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + + # Return the absolute path of the uploaded file + self.wfile.write('{"url":"/%s"}' % path) + def setup(): parser = argparse.ArgumentParser() @@ -19,7 +42,7 @@ def setup(): httpd = BaseHTTPServer.HTTPServer( ('', args.port), - SimpleHTTPServer.SimpleHTTPRequestHandler + SimpleHTTPRequestHandlerWithPOST ) def run(): diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js new file mode 100644 index 0000000000..5729d5da48 --- /dev/null +++ b/webclient/components/fileUpload/file-upload-service.js @@ -0,0 +1,47 @@ +/* + Copyright 2014 matrix.org + + 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. + */ + +'use strict'; + +/* + * Upload an HTML5 file to a server + */ +angular.module('mFileUpload', []) +.service('mFileUpload', ['$http', '$q', function ($http, $q) { + + /* + * Upload an HTML5 file to a server and returned a promise + * that will provide the URL of the uploaded file. + */ + this.uploadFile = function(file) { + var deferred = $q.defer(); + + // @TODO: This service runs with the do_POST hacky implementation of /synapse/demos/webserver.py. + // This is temporary until we have a true file upload service + console.log("Uploading " + file.name + "..."); + $http.post(file.name, file) + .success(function(data, status, headers, config) { + deferred.resolve(location.origin + data.url); + console.log(" -> Successfully uploaded! Available at " + location.origin + data.url); + }). + error(function(data, status, headers, config) { + console.log(" -> Failed to upload" + file.name); + deferred.reject(); + }); + + return deferred.promise; + }; +}]); \ No newline at end of file -- cgit 1.5.1 From deae7f4f5d5cbb5e396465166bcb282c2a25a294 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 18:39:23 +0200 Subject: Create a temporary upload service server side (by hacking demos/webserver.py) and client side with an angularjs service component. --- webclient/index.html | 1 + 1 file changed, 1 insertion(+) (limited to 'webclient') diff --git a/webclient/index.html b/webclient/index.html index f4b791ecdb..e62ec39669 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -15,6 +15,7 @@ + -- cgit 1.5.1 From e6c62d5d7f45234ee574595eeb5ab8b7df41a1ed Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Thu, 14 Aug 2014 18:40:20 +0200 Subject: We can now upload avatar image somewhere --- webclient/rooms/rooms-controller.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'webclient') diff --git a/webclient/rooms/rooms-controller.js b/webclient/rooms/rooms-controller.js index d0924f5887..2ce14e1d49 100644 --- a/webclient/rooms/rooms-controller.js +++ b/webclient/rooms/rooms-controller.js @@ -16,9 +16,9 @@ limitations under the License. 'use strict'; -angular.module('RoomsController', ['matrixService', 'mFileInput']) -.controller('RoomsController', ['$scope', '$location', 'matrixService', - function($scope, $location, matrixService) { +angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload']) +.controller('RoomsController', ['$scope', '$location', 'matrixService', 'mFileUpload', + function($scope, $location, matrixService, mFileUpload) { $scope.rooms = []; $scope.public_rooms = []; @@ -167,7 +167,16 @@ angular.module('RoomsController', ['matrixService', 'mFileInput']) $scope.$watch("newProfileInfo.avatarFile", function(newValue, oldValue) { if ($scope.newProfileInfo.avatarFile) { - //@TODO: Upload this HTML5 image file to somewhere + console.log("Uploading new avatar file..."); + mFileUpload.uploadFile($scope.newProfileInfo.avatarFile).then( + function(url) { + $scope.newProfileInfo.avatar = url; + $scope.setAvatar($scope.newProfileInfo.avatar); + }, + function(error) { + $scope.feedback = "Can't upload image"; + } + ); } }); -- cgit 1.5.1 From 5de086b736218d43bd51c3b83ca26118806488a2 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 14 Aug 2014 17:40:27 +0100 Subject: More helpful display when the event stream fails, wiping it when the connection is regained. --- webclient/room/room-controller.js | 10 ++++++---- webclient/room/room.html | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'webclient') diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 8003105654..fb6e2025fc 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -25,7 +25,8 @@ angular.module('RoomController', []) user_id: matrixService.config().user_id, events_from: "END", // when to start the event stream from. earliest_token: "END", // stores how far back we've paginated. - can_paginate: true + can_paginate: true, // this is toggled off when we run out of items + stream_failure: undefined // the response when the stream fails }; $scope.messages = []; $scope.members = {}; @@ -76,7 +77,7 @@ angular.module('RoomController', []) } }, function(error) { - console.log("paginateBackMessages Ruh roh: " + JSON.stringify(error)); + console.log("Failed to paginateBackMessages: " + JSON.stringify(error)); } ) }; @@ -89,6 +90,7 @@ angular.module('RoomController', []) "timeout": 5000 }}) .then(function(response) { + $scope.state.stream_failure = undefined; console.log("Got response from "+$scope.state.events_from+" to "+response.data.end); $scope.state.events_from = response.data.end; $scope.feedback = ""; @@ -102,7 +104,7 @@ angular.module('RoomController', []) $timeout(shortPoll, 0); } }, function(response) { - $scope.feedback = "Can't stream: " + response.data; + $scope.state.stream_failure = response; if (response.status == 403) { $scope.stopPoll = true; @@ -215,7 +217,7 @@ angular.module('RoomController', []) // Join the room matrixService.join($scope.room_id).then( function() { - console.log("Joined room"); + console.log("Joined room "+$scope.room_id); // Now start reading from the stream $timeout(shortPoll, 0); diff --git a/webclient/room/room.html b/webclient/room/room.html index 0f86a158ec..3b9ba713de 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -86,7 +86,10 @@ - + +
+ {{ state.stream_failure.data.error || "Connection failure" }} +
-- cgit 1.5.1