Merge branch 'develop' of github.com:matrix-org/synapse into federation_authorization

This commit is contained in:
Erik Johnston 2014-11-03 10:17:37 +00:00
commit 7a07263281
10 changed files with 172 additions and 101 deletions

View file

@ -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:

View file

@ -1 +1 @@
0.4.1
0.4.2

View file

@ -16,4 +16,4 @@
""" This is a reference implementation of a synapse home server.
"""
__version__ = "0.4.1"
__version__ = "0.4.2"

View file

@ -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:

View file

@ -30,6 +30,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
'MatrixCall',
'eventStreamService',
'eventHandlerService',
'notificationService',
'infinite-scroll',
'ui.bootstrap',
'monospaced.elastic'

View file

@ -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);
}
);
}
}
}

View file

@ -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);
}
};
}]);

View file

@ -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>

View file

@ -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);
}
);
}
}
}

View file

@ -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'">