diff options
-rw-r--r-- | CHANGES.rst | 16 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | synapse/__init__.py | 2 | ||||
-rw-r--r-- | synapse/handlers/register.py | 3 | ||||
-rw-r--r-- | webclient/app.js | 1 | ||||
-rw-r--r-- | webclient/components/matrix/event-handler-service.js | 104 | ||||
-rw-r--r-- | webclient/components/matrix/notification-service.js | 104 | ||||
-rw-r--r-- | webclient/index.html | 1 | ||||
-rw-r--r-- | webclient/room/room-controller.js | 38 | ||||
-rw-r--r-- | webclient/room/room.html | 2 |
10 files changed, 172 insertions, 101 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 08efbbf244..78c178bafd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,19 @@ +Changes in synapse 0.4.2 (2014-10-31) +===================================== + +Homeserver: + * Fix bugs where we did not notify users of correct presence updates. + * Fix bug where we did not handle sub second event stream timeouts. + +Webclient: + * Add ability to click on messages to see JSON. + * Add ability to redact messages. + * Add ability to view and edit all room state JSON. + * Handle incoming redactions. + * Improve feedback on errors. + * Fix bugs in mobile CSS. + * Fix bugs with desktop notifications. + Changes in synapse 0.4.1 (2014-10-17) ===================================== Webclient: diff --git a/VERSION b/VERSION index 267577d47e..2b7c5ae018 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.1 +0.4.2 diff --git a/synapse/__init__.py b/synapse/__init__.py index 7067188c5b..23ae5f003f 100644 --- a/synapse/__init__.py +++ b/synapse/__init__.py @@ -16,4 +16,4 @@ """ This is a reference implementation of a synapse home server. """ -__version__ = "0.4.1" +__version__ = "0.4.2" diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index 88eb51a8ed..7df9d9b82d 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -15,7 +15,6 @@ """Contains functions for registering clients.""" from twisted.internet import defer -from twisted.python import log from synapse.types import UserID from synapse.api.errors import ( @@ -129,7 +128,7 @@ class RegistrationHandler(BaseHandler): try: threepid = yield self._threepid_from_creds(c) except: - log.err() + logger.exception("Couldn't validate 3pid") raise RegistrationError(400, "Couldn't validate 3pid") if not threepid: diff --git a/webclient/app.js b/webclient/app.js index 8d9b662ee9..c091f8c6cf 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -30,6 +30,7 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'MatrixCall', 'eventStreamService', 'eventHandlerService', + 'notificationService', 'infinite-scroll', 'ui.bootstrap', 'monospaced.elastic' diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 6f251eec56..e63584510b 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -27,8 +27,8 @@ Typically, this service will store events or broadcast them to any listeners if typically all the $on method would do is update its own $scope. */ angular.module('eventHandlerService', []) -.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', -function(matrixService, $rootScope, $q, $timeout, mPresence) { +.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService', +function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) { var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; var MSG_EVENT = "MSG_EVENT"; var MEMBER_EVENT = "MEMBER_EVENT"; @@ -45,71 +45,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { var eventMap = {}; $rootScope.presence = {}; - - // TODO: This is attached to the rootScope so .html can just go containsBingWord - // for determining classes so it is easy to highlight bing messages. It seems a - // bit strange to put the impl in this service though, but I can't think of a better - // file to put it in. - $rootScope.containsBingWord = function(content) { - if (!content || $.type(content) != "string") { - return false; - } - var bingWords = matrixService.config().bingWords; - var shouldBing = false; - - // case-insensitive name check for user_id OR display_name if they exist - var userRegex = ""; - var myUserId = matrixService.config().user_id; - if (myUserId) { - var localpart = getLocalPartFromUserId(myUserId); - if (localpart) { - localpart = localpart.toLocaleLowerCase(); - userRegex += "\\b" + localpart + "\\b"; - } - } - var myDisplayName = matrixService.config().display_name; - if (myDisplayName) { - myDisplayName = myDisplayName.toLocaleLowerCase(); - if (userRegex.length > 0) { - userRegex += "|"; - } - userRegex += "\\b" + myDisplayName + "\\b"; - } - - var r = new RegExp(userRegex, 'i'); - if (content.search(r) >= 0) { - shouldBing = true; - } - - if ( (myDisplayName && content.toLocaleLowerCase().indexOf(myDisplayName) != -1) || - (myUserId && content.toLocaleLowerCase().indexOf(myUserId) != -1) ) { - shouldBing = true; - } - - // bing word list check - if (bingWords && !shouldBing) { - for (var i=0; i<bingWords.length; i++) { - var re = RegExp(bingWords[i]); - if (content.search(re) != -1) { - shouldBing = true; - break; - } - } - } - return shouldBing; - }; - - var getLocalPartFromUserId = function(user_id) { - if (!user_id) { - return null; - } - var localpartRegex = /@(.*):\w+/i - var results = localpartRegex.exec(user_id); - if (results && results.length == 2) { - return results[1]; - } - return null; - }; var initialSyncDeferred; @@ -228,7 +163,12 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { } if (window.Notification && event.user_id != matrixService.config().user_id) { - var shouldBing = $rootScope.containsBingWord(event.content.body); + var shouldBing = notificationService.containsBingWord( + matrixService.config().user_id, + matrixService.config().display_name, + matrixService.config().bingWords, + event.content.body + ); // Ideally we would notify only when the window is hidden (i.e. document.hidden = true). // @@ -258,6 +198,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { if (event.content.msgtype === "m.emote") { message = "* " + displayname + " " + message; } + else if (event.content.msgtype === "m.image") { + message = displayname + " sent an image."; + } var roomTitle = matrixService.getRoomIdToAliasMapping(event.room_id); var theRoom = $rootScope.events.rooms[event.room_id]; @@ -269,22 +212,15 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { roomTitle = event.room_id; } - var notification = new window.Notification( - displayname + - " (" + roomTitle + ")", - { - "body": message, - "icon": member ? member.avatar_url : undefined - }); - - notification.onclick = function() { - console.log("notification.onclick() room=" + event.room_id); - $rootScope.goToPage('room/' + (event.room_id)); - }; - - $timeout(function() { - notification.close(); - }, 5 * 1000); + notificationService.showNotification( + displayname + " (" + roomTitle + ")", + message, + member ? member.avatar_url : undefined, + function() { + console.log("notification.onclick() room=" + event.room_id); + $rootScope.goToPage('room/' + event.room_id); + } + ); } } } diff --git a/webclient/components/matrix/notification-service.js b/webclient/components/matrix/notification-service.js new file mode 100644 index 0000000000..9a911413c3 --- /dev/null +++ b/webclient/components/matrix/notification-service.js @@ -0,0 +1,104 @@ +/* +Copyright 2014 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. +*/ + +'use strict'; + +/* +This service manages notifications: enabling, creating and showing them. This +also contains 'bing word' logic. +*/ +angular.module('notificationService', []) +.factory('notificationService', ['$timeout', function($timeout) { + + var getLocalPartFromUserId = function(user_id) { + if (!user_id) { + return null; + } + var localpartRegex = /@(.*):\w+/i + var results = localpartRegex.exec(user_id); + if (results && results.length == 2) { + return results[1]; + } + return null; + }; + + return { + + containsBingWord: function(userId, displayName, bingWords, content) { + // case-insensitive name check for user_id OR display_name if they exist + var userRegex = ""; + if (userId) { + var localpart = getLocalPartFromUserId(userId); + if (localpart) { + localpart = localpart.toLocaleLowerCase(); + userRegex += "\\b" + localpart + "\\b"; + } + } + if (displayName) { + displayName = displayName.toLocaleLowerCase(); + if (userRegex.length > 0) { + userRegex += "|"; + } + userRegex += "\\b" + displayName + "\\b"; + } + + var regexList = [new RegExp(userRegex, 'i')]; + + // bing word list check + if (bingWords && bingWords.length > 0) { + for (var i=0; i<bingWords.length; i++) { + var re = RegExp(bingWords[i], 'i'); + regexList.push(re); + } + } + return this.hasMatch(regexList, content); + }, + + hasMatch: function(regExps, content) { + if (!content || $.type(content) != "string") { + return false; + } + + if (regExps && regExps.length > 0) { + for (var i=0; i<regExps.length; i++) { + if (content.search(regExps[i]) != -1) { + return true; + } + } + } + return false; + }, + + showNotification: function(title, body, icon, onclick) { + var notification = new window.Notification( + title, + { + "body": body, + "icon": icon + } + ); + + if (onclick) { + notification.onclick = onclick; + } + + $timeout(function() { + notification.close(); + }, 5 * 1000); + } + }; + +}]); diff --git a/webclient/index.html b/webclient/index.html index d8b9c95353..bc011a6c72 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -41,6 +41,7 @@ <script src="components/matrix/matrix-phone-service.js"></script> <script src="components/matrix/event-stream-service.js"></script> <script src="components/matrix/event-handler-service.js"></script> + <script src="components/matrix/notification-service.js"></script> <script src="components/matrix/presence-service.js"></script> <script src="components/fileInput/file-input-directive.js"></script> <script src="components/fileUpload/file-upload-service.js"></script> diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 59274baccb..486ead0da9 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -15,11 +15,21 @@ limitations under the License. */ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) -.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', - function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall) { +.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', + function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService) { 'use strict'; var MESSAGES_PER_PAGINATION = 30; var THUMBNAIL_SIZE = 320; + + // .html needs this + $scope.containsBingWord = function(content) { + return notificationService.containsBingWord( + matrixService.config().user_id, + matrixService.config().display_name, + matrixService.config().bingWords, + content + ); + }; // Room ids. Computed and resolved in onInit $scope.room_id = undefined; @@ -191,16 +201,20 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // Notify when a user joins if ((document.hidden || matrixService.presence.unavailable === mPresence.getState()) && event.state_key !== $scope.state.user_id && "join" === event.membership) { - var notification = new window.Notification( - event.content.displayname + - " (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", // FIXME: don't leak room_ids here - { - "body": event.content.displayname + " joined", - "icon": event.content.avatar_url ? event.content.avatar_url : undefined - }); - $timeout(function() { - notification.close(); - }, 5 * 1000); + var userName = event.content.displayname; + if (!userName) { + userName = event.state_key; + } + notificationService.showNotification( + userName + + " (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", + userName + " joined", + event.content.avatar_url ? event.content.avatar_url : undefined, + function() { + console.log("notification.onclick() room=" + event.room_id); + $rootScope.goToPage('room/' + event.room_id); + } + ); } } } diff --git a/webclient/room/room.html b/webclient/room/room.html index fac7433a4b..5265f42dd8 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -133,7 +133,7 @@ </div> </td> <td class="avatar"> - <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" + <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> </td> <td ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> |