Merge branch 'develop' of github.com:matrix-org/synapse into federation_authorization
2
.gitignore
vendored
|
@ -24,7 +24,7 @@ graph/*.svg
|
|||
graph/*.png
|
||||
graph/*.dot
|
||||
|
||||
webclient/config.js
|
||||
**/webclient/config.js
|
||||
webclient/test/environment-protractor.js
|
||||
|
||||
uploads
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
recursive-include docs *
|
||||
recursive-include tests *.py
|
||||
recursive-include synapse/persistence/schema *.sql
|
||||
recursive-include synapse/storage/schema *.sql
|
||||
recursive-include syweb/webclient *
|
||||
|
|
57
README.rst
|
@ -122,12 +122,12 @@ Thanks for trying Matrix!
|
|||
|
||||
[2] End-to-end encryption is currently in development
|
||||
|
||||
|
||||
Homeserver Installation
|
||||
=======================
|
||||
|
||||
First, the dependencies need to be installed. Start by installing
|
||||
'python2.7-dev' and the various tools of the compiler toolchain.
|
||||
Synapse is written in python but some of the libraries is uses are written in
|
||||
C. So before we can install synapse itself we need a working C compiler and the
|
||||
header files for python C extensions.
|
||||
|
||||
Installing prerequisites on Ubuntu::
|
||||
|
||||
|
@ -137,30 +137,35 @@ Installing prerequisites on Mac OS X::
|
|||
|
||||
$ xcode-select --install
|
||||
|
||||
Synapse uses NaCl (http://nacl.cr.yp.to/) for encryption and digital
|
||||
signatures. Unfortunately PyNACL currently has a few issues
|
||||
(https://github.com/pyca/pynacl/issues/53) and
|
||||
(https://github.com/pyca/pynacl/issues/79) that mean it may not install
|
||||
correctly. To fix try re-installing from PyPI or directly from (https://github.com/pyca/pynacl)::
|
||||
|
||||
$ # Install from PyPI
|
||||
$ pip install --user --upgrade --force pynacl
|
||||
$ # Install from github
|
||||
$ pip install --user https://github.com/pyca/pynacl/tarball/master
|
||||
|
||||
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
||||
you will need to ``export CFLAGS=-Qunused-arguments``.
|
||||
|
||||
To install the synapse homeserver run::
|
||||
|
||||
$ pip install --user --process-dependency-links https://github.com/matrix-org/synapse/tarball/master
|
||||
|
||||
This installs synapse, along with the libraries it uses, into
|
||||
``$HOME/.local/lib/``.
|
||||
|
||||
Homeserver Development
|
||||
======================
|
||||
|
||||
The homeserver has a number of external dependencies, that are easiest
|
||||
to install by making setup.py do so, in --user mode::
|
||||
|
||||
$ python setup.py develop --user
|
||||
|
||||
You'll need a version of setuptools new enough to know about git, so you
|
||||
may need to also run::
|
||||
|
||||
$ sudo apt-get install python-pip
|
||||
$ sudo pip install --upgrade setuptools
|
||||
|
||||
If you don't have access to github, then you may need to install ``syutil``
|
||||
manually by checking it out and running ``python setup.py develop --user`` on
|
||||
it too.
|
||||
|
||||
If you get errors about ``sodium.h`` being missing, you may also need to
|
||||
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
|
||||
you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
|
||||
installing it. Installing PyNaCl using pip may also work (remember to remove
|
||||
any other versions installed by setuputils in, for example, ~/.local/lib).
|
||||
|
||||
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'``
|
||||
you will need to ``export CFLAGS=-Qunused-arguments``.
|
||||
|
||||
This will run a process of downloading and installing into your
|
||||
user's .local/lib directory all of the required dependencies that are
|
||||
missing.
|
||||
|
@ -204,11 +209,11 @@ IDs:
|
|||
For the first form, simply pass the required hostname (of the machine) as the
|
||||
--host parameter::
|
||||
|
||||
$ python synapse/app/homeserver.py \
|
||||
$ python -m synapse.app.homeserver \
|
||||
--server-name machine.my.domain.name \
|
||||
--config-path homeserver.config \
|
||||
--generate-config
|
||||
$ python synapse/app/homeserver.py --config-path homeserver.config
|
||||
$ python -m synapse.app.homeserver --config-path homeserver.config
|
||||
|
||||
Alternatively, you can run synapse via synctl - running ``synctl start`` to
|
||||
generate a homeserver.yaml config file, where you can then edit server-name to
|
||||
|
@ -226,12 +231,12 @@ record would then look something like::
|
|||
At this point, you should then run the homeserver with the hostname of this
|
||||
SRV record, as that is the name other machines will expect it to have::
|
||||
|
||||
$ python synapse/app/homeserver.py \
|
||||
$ python -m synapse.app.homeserver \
|
||||
--server-name YOURDOMAIN \
|
||||
--bind-port 8448 \
|
||||
--config-path homeserver.config \
|
||||
--generate-config
|
||||
$ python synapse/app/homeserver.py --config-path homeserver.config
|
||||
$ python -m synapse.app.homeserver --config-path homeserver.config
|
||||
|
||||
|
||||
You may additionally want to pass one or more "-v" options, in order to
|
||||
|
|
|
@ -41,6 +41,6 @@ for port in 8080 8081 8082; do
|
|||
done
|
||||
|
||||
echo "Starting webclient on port 8000..."
|
||||
python "demo/webserver.py" -p 8000 -P "$DIR/webserver.pid" "webclient"
|
||||
python "demo/webserver.py" -p 8000 -P "$DIR/webserver.pid" "syweb/webclient"
|
||||
|
||||
cd "$CWD"
|
||||
|
|
3
setup.py
|
@ -28,7 +28,7 @@ def read(fname):
|
|||
setup(
|
||||
name="SynapseHomeServer",
|
||||
version="0.0.1",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
packages=find_packages(exclude=["tests", "tests.*"]),
|
||||
description="Reference Synapse Home Server",
|
||||
install_requires=[
|
||||
"syutil==0.0.2",
|
||||
|
@ -43,6 +43,7 @@ setup(
|
|||
],
|
||||
dependency_links=[
|
||||
"https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2",
|
||||
"https://github.com/pyca/pynacl/tarball/52dbe2dc33f1#egg=pynacl-0.3.0",
|
||||
],
|
||||
setup_requires=[
|
||||
"setuptools_trial",
|
||||
|
|
|
@ -42,6 +42,7 @@ import os
|
|||
import re
|
||||
import sys
|
||||
import sqlite3
|
||||
import syweb
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -58,7 +59,9 @@ class SynapseHomeServer(HomeServer):
|
|||
return JsonResource()
|
||||
|
||||
def build_resource_for_web_client(self):
|
||||
return File("webclient") # TODO configurable?
|
||||
syweb_path = os.path.dirname(syweb.__file__)
|
||||
webclient_path = os.path.join(syweb_path, "webclient")
|
||||
return File(webclient_path) # TODO configurable?
|
||||
|
||||
def build_resource_for_content_repo(self):
|
||||
return ContentRepoResource(
|
||||
|
|
|
@ -148,7 +148,7 @@ class RoomStateEventRestServlet(RestServlet):
|
|||
content = _parse_json(request)
|
||||
|
||||
event = self.event_factory.create_event(
|
||||
etype=event_type,
|
||||
etype=urllib.unquote(event_type),
|
||||
content=content,
|
||||
room_id=urllib.unquote(room_id),
|
||||
user_id=user.to_string(),
|
||||
|
@ -182,7 +182,7 @@ class RoomSendEventRestServlet(RestServlet):
|
|||
content = _parse_json(request)
|
||||
|
||||
event = self.event_factory.create_event(
|
||||
etype=event_type,
|
||||
etype=urllib.unquote(event_type),
|
||||
room_id=urllib.unquote(room_id),
|
||||
user_id=user.to_string(),
|
||||
content=content
|
||||
|
@ -458,7 +458,7 @@ class RoomRedactEventRestServlet(RestServlet):
|
|||
room_id=urllib.unquote(room_id),
|
||||
user_id=user.to_string(),
|
||||
content=content,
|
||||
redacts=event_id,
|
||||
redacts=urllib.unquote(event_id),
|
||||
)
|
||||
|
||||
msg_handler = self.handlers.message_handler
|
||||
|
|
0
syweb/__init__.py
Normal file
|
@ -21,8 +21,8 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService',
|
||||
function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService) {
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', 'modelService',
|
||||
function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService, modelService) {
|
||||
|
||||
// Check current URL to avoid to display the logout button on the login page
|
||||
$scope.location = $location.path();
|
||||
|
@ -117,7 +117,7 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||
return;
|
||||
}
|
||||
|
||||
var roomMembers = angular.copy($rootScope.events.rooms[$rootScope.currentCall.room_id].members);
|
||||
var roomMembers = angular.copy(modelService.getRoom($rootScope.currentCall.room_id).current_room_state.members);
|
||||
delete roomMembers[matrixService.config().user_id];
|
||||
|
||||
$rootScope.currentCall.user_id = Object.keys(roomMembers)[0];
|
|
@ -29,10 +29,10 @@ angular.module('matrixWebClient')
|
|||
return s + "s";
|
||||
}
|
||||
if (t < 60 * 60) {
|
||||
return m + "m "; // + s + "s";
|
||||
return m + "m"; // + s + "s";
|
||||
}
|
||||
if (t < 24 * 60 * 60) {
|
||||
return h + "h "; // + m + "m";
|
||||
return h + "h"; // + m + "m";
|
||||
}
|
||||
return d + "d "; // + h + "h";
|
||||
};
|
||||
|
@ -76,17 +76,6 @@ angular.module('matrixWebClient')
|
|||
return filtered;
|
||||
};
|
||||
})
|
||||
.filter('stateEventsFilter', function($sce) {
|
||||
return function(events) {
|
||||
var filtered = {};
|
||||
angular.forEach(events, function(value, key) {
|
||||
if (value && typeof(value.state_key) === "string") {
|
||||
filtered[key] = value;
|
||||
}
|
||||
});
|
||||
return filtered;
|
||||
};
|
||||
})
|
||||
.filter('unsafe', ['$sce', function($sce) {
|
||||
return function(text) {
|
||||
return $sce.trustAsHtml(text);
|
|
@ -31,6 +31,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
|||
'eventStreamService',
|
||||
'eventHandlerService',
|
||||
'notificationService',
|
||||
'modelService',
|
||||
'infinite-scroll',
|
||||
'ui.bootstrap',
|
||||
'monospaced.elastic'
|
|
@ -64,7 +64,8 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
|||
var imageMessage = {
|
||||
msgtype: "m.image",
|
||||
url: undefined,
|
||||
body: {
|
||||
body: "Image",
|
||||
info: {
|
||||
size: undefined,
|
||||
w: undefined,
|
||||
h: undefined,
|
||||
|
@ -90,7 +91,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
|||
function(url) {
|
||||
// Update message metadata
|
||||
imageMessage.url = url;
|
||||
imageMessage.body = {
|
||||
imageMessage.info = {
|
||||
size: imageFile.size,
|
||||
w: size.width,
|
||||
h: size.height,
|
||||
|
@ -101,7 +102,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
|||
// reuse the original image info for thumbnail data
|
||||
if (!imageMessage.thumbnail_url) {
|
||||
imageMessage.thumbnail_url = imageMessage.url;
|
||||
imageMessage.thumbnail_info = imageMessage.body;
|
||||
imageMessage.thumbnail_info = imageMessage.info;
|
||||
}
|
||||
|
||||
// We are done
|
|
@ -22,13 +22,12 @@ not care where the event came from, it only needs enough context to be able to
|
|||
process them. Events may be coming from the event stream, the REST API (via
|
||||
direct GETs or via a pagination stream API), etc.
|
||||
|
||||
Typically, this service will store events or broadcast them to any listeners
|
||||
(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
|
||||
if typically all the $on method would do is update its own $scope.
|
||||
Typically, this service will store events and broadcast them to any listeners
|
||||
(e.g. controllers) via $broadcast.
|
||||
*/
|
||||
angular.module('eventHandlerService', [])
|
||||
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService',
|
||||
function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) {
|
||||
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', '$filter', 'mPresence', 'notificationService', 'modelService',
|
||||
function(matrixService, $rootScope, $q, $timeout, $filter, mPresence, notificationService, modelService) {
|
||||
var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
|
||||
var MSG_EVENT = "MSG_EVENT";
|
||||
var MEMBER_EVENT = "MEMBER_EVENT";
|
||||
|
@ -44,94 +43,113 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
// of the app, given we never try to reap memory yet)
|
||||
var eventMap = {};
|
||||
|
||||
// TODO: Remove this and replace with modelService.User objects.
|
||||
$rootScope.presence = {};
|
||||
|
||||
var initialSyncDeferred;
|
||||
|
||||
var reset = function() {
|
||||
initialSyncDeferred = $q.defer();
|
||||
|
||||
$rootScope.events = {
|
||||
rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
|
||||
};
|
||||
|
||||
|
||||
$rootScope.presence = {};
|
||||
|
||||
eventMap = {};
|
||||
};
|
||||
reset();
|
||||
|
||||
var initRoom = function(room_id, room) {
|
||||
if (!(room_id in $rootScope.events.rooms)) {
|
||||
console.log("Creating new rooms entry for " + room_id);
|
||||
$rootScope.events.rooms[room_id] = {
|
||||
room_id: room_id,
|
||||
messages: [],
|
||||
members: {},
|
||||
// Pagination information
|
||||
pagination: {
|
||||
earliest_token: "END" // how far back we've paginated
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (room) { // we got an existing room object from initialsync, seemingly.
|
||||
// Report all other metadata of the room object (membership, inviter, visibility, ...)
|
||||
for (var field in room) {
|
||||
if (!room.hasOwnProperty(field)) continue;
|
||||
|
||||
if (-1 === ["room_id", "messages", "state"].indexOf(field)) { // why indexOf - why not ===? --Matthew
|
||||
$rootScope.events.rooms[room_id][field] = room[field];
|
||||
}
|
||||
}
|
||||
$rootScope.events.rooms[room_id].membership = room.membership;
|
||||
}
|
||||
};
|
||||
|
||||
var resetRoomMessages = function(room_id) {
|
||||
if ($rootScope.events.rooms[room_id]) {
|
||||
$rootScope.events.rooms[room_id].messages = [];
|
||||
}
|
||||
var room = modelService.getRoom(room_id);
|
||||
room.events = [];
|
||||
};
|
||||
|
||||
// Generic method to handle events data
|
||||
var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) {
|
||||
// Add topic changes as if they were a room message
|
||||
var handleRoomStateEvent = function(event, isLiveEvent, addToRoomMessages) {
|
||||
var room = modelService.getRoom(event.room_id);
|
||||
if (addToRoomMessages) {
|
||||
if (isLiveEvent) {
|
||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||
}
|
||||
else {
|
||||
$rootScope.events.rooms[event.room_id].messages.unshift(event);
|
||||
}
|
||||
// some state events are displayed as messages, so add them.
|
||||
room.addMessageEvent(event, !isLiveEvent);
|
||||
}
|
||||
|
||||
// live events always update, but non-live events only update if the
|
||||
// ts is later.
|
||||
var latestData = true;
|
||||
if (!isLiveEvent) {
|
||||
|
||||
if (isLiveEvent) {
|
||||
// update the current room state with the latest state
|
||||
room.current_room_state.storeStateEvent(event);
|
||||
}
|
||||
else {
|
||||
var eventTs = event.origin_server_ts;
|
||||
var storedEvent = $rootScope.events.rooms[event.room_id][event.type];
|
||||
var storedEvent = room.current_room_state.getStateEvent(event.type, event.state_key);
|
||||
if (storedEvent) {
|
||||
if (storedEvent.origin_server_ts > eventTs) {
|
||||
// ignore it, we have a newer one already.
|
||||
latestData = false;
|
||||
if (storedEvent.origin_server_ts < eventTs) {
|
||||
// the incoming event is newer, use it.
|
||||
room.current_room_state.storeStateEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (latestData) {
|
||||
$rootScope.events.rooms[event.room_id][event.type] = event;
|
||||
}
|
||||
// TODO: handle old_room_state
|
||||
};
|
||||
|
||||
var handleRoomCreate = function(event, isLiveEvent) {
|
||||
// For now, we do not use the event data. Simply signal it to the app controllers
|
||||
$rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
|
||||
};
|
||||
|
||||
var handleRoomAliases = function(event, isLiveEvent) {
|
||||
matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
|
||||
};
|
||||
|
||||
var displayNotification = function(event) {
|
||||
if (window.Notification && event.user_id != matrixService.config().user_id) {
|
||||
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).
|
||||
//
|
||||
// However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is
|
||||
// explicitly showing a different tab. So we need another metric to determine hiddenness - we
|
||||
// simply use idle time. If the user has been idle enough that their presence goes to idle, then
|
||||
// we also display notifs when things happen.
|
||||
//
|
||||
// This is far far better than notifying whenever anything happens anyway, otherwise you get spammed
|
||||
// to death with notifications when the window is in the foreground, which is horrible UX (especially
|
||||
// if you have not defined any bingers and so get notified for everything).
|
||||
var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState());
|
||||
|
||||
// We need a way to let people get notifications for everything, if they so desire. The way to do this
|
||||
// is to specify zero bingwords.
|
||||
var bingWords = matrixService.config().bingWords;
|
||||
if (bingWords === undefined || bingWords.length === 0) {
|
||||
shouldBing = true;
|
||||
}
|
||||
|
||||
if (shouldBing && isIdle) {
|
||||
console.log("Displaying notification for "+JSON.stringify(event));
|
||||
var member = modelService.getMember(event.room_id, event.user_id);
|
||||
var displayname = getUserDisplayName(event.room_id, event.user_id);
|
||||
|
||||
var message = event.content.body;
|
||||
if (event.content.msgtype === "m.emote") {
|
||||
message = "* " + displayname + " " + message;
|
||||
}
|
||||
else if (event.content.msgtype === "m.image") {
|
||||
message = displayname + " sent an image.";
|
||||
}
|
||||
|
||||
var roomTitle = $filter("mRoomName")(event.room_id);
|
||||
|
||||
notificationService.showNotification(
|
||||
displayname + " (" + roomTitle + ")",
|
||||
message,
|
||||
member ? member.event.content.avatar_url : undefined,
|
||||
function() {
|
||||
console.log("notification.onclick() room=" + event.room_id);
|
||||
$rootScope.goToPage('room/' + event.room_id);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var handleMessage = function(event, isLiveEvent) {
|
||||
// Check for empty event content
|
||||
|
@ -144,135 +162,79 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
// empty json object is a redacted event, so ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLiveEvent) {
|
||||
if (event.user_id === matrixService.config().user_id &&
|
||||
(event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") ) {
|
||||
// Assume we've already echoed it. So, there is a fake event in the messages list of the room
|
||||
// Replace this fake event by the true one
|
||||
var index = getRoomEventIndex(event.room_id, event.event_id);
|
||||
if (index) {
|
||||
$rootScope.events.rooms[event.room_id].messages[index] = event;
|
||||
}
|
||||
else {
|
||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||
}
|
||||
|
||||
if (window.Notification && event.user_id != matrixService.config().user_id) {
|
||||
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).
|
||||
//
|
||||
// However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is
|
||||
// explicitly showing a different tab. So we need another metric to determine hiddenness - we
|
||||
// simply use idle time. If the user has been idle enough that their presence goes to idle, then
|
||||
// we also display notifs when things happen.
|
||||
//
|
||||
// This is far far better than notifying whenever anything happens anyway, otherwise you get spammed
|
||||
// to death with notifications when the window is in the foreground, which is horrible UX (especially
|
||||
// if you have not defined any bingers and so get notified for everything).
|
||||
var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState());
|
||||
|
||||
// We need a way to let people get notifications for everything, if they so desire. The way to do this
|
||||
// is to specify zero bingwords.
|
||||
var bingWords = matrixService.config().bingWords;
|
||||
if (bingWords === undefined || bingWords.length === 0) {
|
||||
shouldBing = true;
|
||||
}
|
||||
|
||||
if (shouldBing && isIdle) {
|
||||
console.log("Displaying notification for "+JSON.stringify(event));
|
||||
var member = getMember(event.room_id, event.user_id);
|
||||
var displayname = getUserDisplayName(event.room_id, event.user_id);
|
||||
|
||||
var message = event.content.body;
|
||||
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];
|
||||
if (!roomTitle && theRoom && theRoom["m.room.name"] && theRoom["m.room.name"].content) {
|
||||
roomTitle = theRoom["m.room.name"].content.name;
|
||||
}
|
||||
|
||||
if (!roomTitle) {
|
||||
roomTitle = event.room_id;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =======================
|
||||
|
||||
var room = modelService.getRoom(event.room_id);
|
||||
|
||||
if (event.user_id !== matrixService.config().user_id) {
|
||||
room.addMessageEvent(event, !isLiveEvent);
|
||||
displayNotification(event);
|
||||
}
|
||||
else {
|
||||
$rootScope.events.rooms[event.room_id].messages.unshift(event);
|
||||
// we may have locally echoed this, so we should replace the event
|
||||
// instead of just adding.
|
||||
room.addOrReplaceMessageEvent(event, !isLiveEvent);
|
||||
}
|
||||
|
||||
// TODO send delivery receipt if isLiveEvent
|
||||
|
||||
// $broadcast this, as controllers may want to do funky things such as
|
||||
// scroll to the bottom, etc which cannot be expressed via simple $scope
|
||||
// updates.
|
||||
$rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
|
||||
};
|
||||
|
||||
var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
|
||||
var room = modelService.getRoom(event.room_id);
|
||||
|
||||
// add membership changes as if they were a room message if something interesting changed
|
||||
// Exception: Do not do this if the event is a room state event because such events already come
|
||||
// as room messages events. Moreover, when they come as room messages events, they are relatively ordered
|
||||
// with other other room messages
|
||||
// did something change?
|
||||
var memberChanges = undefined;
|
||||
if (!isStateEvent) {
|
||||
// could be a membership change, display name change, etc.
|
||||
// Find out which one.
|
||||
var memberChanges = undefined;
|
||||
if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) {
|
||||
memberChanges = "membership";
|
||||
}
|
||||
else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) {
|
||||
memberChanges = "displayname";
|
||||
}
|
||||
|
||||
// mark the key which changed
|
||||
event.changedKey = memberChanges;
|
||||
|
||||
// If there was a change we want to display, dump it in the message
|
||||
// list.
|
||||
if (memberChanges) {
|
||||
if (isLiveEvent) {
|
||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||
}
|
||||
else {
|
||||
$rootScope.events.rooms[event.room_id].messages.unshift(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use data from state event or the latest data from the stream.
|
||||
// Do not care of events that come when paginating back
|
||||
|
||||
// modify state before adding the message so it points to the right thing.
|
||||
// The events are copied to avoid referencing the same event when adding
|
||||
// the message (circular json structures)
|
||||
if (isStateEvent || isLiveEvent) {
|
||||
$rootScope.events.rooms[event.room_id].members[event.state_key] = event;
|
||||
var newEvent = angular.copy(event);
|
||||
newEvent.cnt = event.content;
|
||||
room.current_room_state.storeStateEvent(newEvent);
|
||||
}
|
||||
else if (!isLiveEvent) {
|
||||
// mutate the old room state
|
||||
var oldEvent = angular.copy(event);
|
||||
oldEvent.cnt = event.content;
|
||||
if (event.prev_content) {
|
||||
// the m.room.member event we are handling is the NEW event. When
|
||||
// we keep going back in time, we want the PREVIOUS value for displaying
|
||||
// names/etc, hence the clobber here.
|
||||
oldEvent.cnt = event.prev_content;
|
||||
}
|
||||
|
||||
if (event.changedKey === "membership" && event.content.membership === "join") {
|
||||
// join has a prev_content but it doesn't contain all the info unlike the join, so use that.
|
||||
oldEvent.cnt = event.content;
|
||||
}
|
||||
|
||||
room.old_room_state.storeStateEvent(oldEvent);
|
||||
}
|
||||
|
||||
// If there was a change we want to display, dump it in the message
|
||||
// list. This has to be done after room state is updated.
|
||||
if (memberChanges) {
|
||||
room.addMessageEvent(event, !isLiveEvent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent);
|
||||
};
|
||||
|
@ -283,30 +245,28 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
};
|
||||
|
||||
var handlePowerLevels = function(event, isLiveEvent) {
|
||||
// Keep the latest data. Do not care of events that come when paginating back
|
||||
if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
|
||||
$rootScope.events.rooms[event.room_id][event.type] = event;
|
||||
$rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
|
||||
}
|
||||
handleRoomStateEvent(event, isLiveEvent);
|
||||
$rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
|
||||
};
|
||||
|
||||
var handleRoomName = function(event, isLiveEvent, isStateEvent) {
|
||||
console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name);
|
||||
handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
|
||||
handleRoomStateEvent(event, isLiveEvent, !isStateEvent);
|
||||
$rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
|
||||
};
|
||||
|
||||
|
||||
var handleRoomTopic = function(event, isLiveEvent, isStateEvent) {
|
||||
console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic);
|
||||
handleRoomDateEvent(event, isLiveEvent, !isStateEvent);
|
||||
handleRoomStateEvent(event, isLiveEvent, !isStateEvent);
|
||||
$rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent);
|
||||
};
|
||||
|
||||
var handleCallEvent = function(event, isLiveEvent) {
|
||||
$rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
|
||||
if (event.type === 'm.call.invite') {
|
||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||
var room = modelService.getRoom(event.room_id);
|
||||
room.addMessageEvent(event, !isLiveEvent);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -320,8 +280,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
// we need to remove something possibly: do we know the redacted
|
||||
// event ID?
|
||||
if (eventMap[event.redacts]) {
|
||||
var room = modelService.getRoom(event.room_id);
|
||||
// remove event from list of messages in this room.
|
||||
var eventList = $rootScope.events.rooms[event.room_id].messages;
|
||||
var eventList = room.events;
|
||||
for (var i=0; i<eventList.length; i++) {
|
||||
if (eventList[i].event_id === event.redacts) {
|
||||
console.log("Removing event " + event.redacts);
|
||||
|
@ -330,50 +291,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
}
|
||||
}
|
||||
|
||||
// broadcast the redaction so controllers can nuke this
|
||||
console.log("Redacted an event.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the event in $rootScope.events.rooms[room_id].messages
|
||||
* @param {type} room_id the room id
|
||||
* @param {type} event_id the event id to look for
|
||||
* @returns {Number | undefined} the index. undefined if not found.
|
||||
*/
|
||||
var getRoomEventIndex = function(room_id, event_id) {
|
||||
var index;
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
// Start looking from the tail since the first goal of this function
|
||||
// is to find a messaged among the latest ones
|
||||
for (var i = room.messages.length - 1; i > 0; i--) {
|
||||
var message = room.messages[i];
|
||||
if (event_id === message.event_id) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the member object of a room member
|
||||
* @param {String} room_id the room id
|
||||
* @param {String} user_id the id of the user
|
||||
* @returns {undefined | Object} the member object of this user in this room if he is part of the room
|
||||
*/
|
||||
var getMember = function(room_id, user_id) {
|
||||
var member;
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
member = room.members[user_id];
|
||||
}
|
||||
return member;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the display name of an user acccording to data already downloaded
|
||||
|
@ -385,17 +305,20 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
var displayName;
|
||||
|
||||
// Get the user display name from the member list of the room
|
||||
var member = getMember(room_id, user_id);
|
||||
var member = modelService.getMember(room_id, user_id);
|
||||
if (member) {
|
||||
member = member.event;
|
||||
}
|
||||
if (member && member.content.displayname) { // Do not consider null displayname
|
||||
displayName = member.content.displayname;
|
||||
|
||||
// Disambiguate users who have the same displayname in the room
|
||||
if (user_id !== matrixService.config().user_id) {
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
var room = modelService.getRoom(room_id);
|
||||
|
||||
for (var member_id in room.members) {
|
||||
if (room.members.hasOwnProperty(member_id) && member_id !== user_id) {
|
||||
var member2 = room.members[member_id];
|
||||
for (var member_id in room.current_room_state.members) {
|
||||
if (room.current_room_state.members.hasOwnProperty(member_id) && member_id !== user_id) {
|
||||
var member2 = room.current_room_state.members[member_id].event;
|
||||
if (member2.content.displayname && member2.content.displayname === displayName) {
|
||||
displayName = displayName + " (" + user_id + ")";
|
||||
break;
|
||||
|
@ -433,19 +356,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
reset();
|
||||
$rootScope.$broadcast(RESET_EVENT);
|
||||
},
|
||||
|
||||
initRoom: function(room) {
|
||||
initRoom(room.room_id, room);
|
||||
},
|
||||
|
||||
handleEvent: function(event, isLiveEvent, isStateEvent) {
|
||||
|
||||
// FIXME: /initialSync on a particular room is not yet available
|
||||
// So initRoom on a new room is not called. Make sure the room data is initialised here
|
||||
if (event.room_id) {
|
||||
initRoom(event.room_id);
|
||||
}
|
||||
|
||||
// Avoid duplicated events
|
||||
// Needed for rooms where initialSync has not been done.
|
||||
// In this case, we do not know where to start pagination. So, it starts from the END
|
||||
|
@ -504,11 +417,11 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
// displays on the Room Info screen.
|
||||
if (typeof(event.state_key) === "string") { // incls. 0-len strings
|
||||
if (event.room_id) {
|
||||
handleRoomDateEvent(event, isLiveEvent, false);
|
||||
handleRoomStateEvent(event, isLiveEvent, false);
|
||||
}
|
||||
}
|
||||
console.log("Unable to handle event type " + event.type);
|
||||
console.log(JSON.stringify(event, undefined, 4));
|
||||
// console.log(JSON.stringify(event, undefined, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -524,8 +437,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
|
||||
// Handle messages from /initialSync or /messages
|
||||
handleRoomMessages: function(room_id, messages, isLiveEvents, dir) {
|
||||
initRoom(room_id);
|
||||
|
||||
var events = messages.chunk;
|
||||
|
||||
// Handles messages according to their time order
|
||||
|
@ -536,21 +447,67 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
}
|
||||
|
||||
// Store how far back we've paginated
|
||||
$rootScope.events.rooms[room_id].pagination.earliest_token = messages.end;
|
||||
var room = modelService.getRoom(room_id);
|
||||
room.old_room_state.pagination_token = messages.end;
|
||||
|
||||
}
|
||||
else {
|
||||
// InitialSync returns messages in chronological order
|
||||
// InitialSync returns messages in chronological order, so invert
|
||||
// it to get most recent > oldest
|
||||
for (var i=events.length - 1; i>=0; i--) {
|
||||
this.handleEvent(events[i], isLiveEvents, isLiveEvents);
|
||||
}
|
||||
// Store where to start pagination
|
||||
$rootScope.events.rooms[room_id].pagination.earliest_token = messages.start;
|
||||
var room = modelService.getRoom(room_id);
|
||||
room.old_room_state.pagination_token = messages.start;
|
||||
}
|
||||
},
|
||||
|
||||
handleInitialSyncDone: function(initialSyncData) {
|
||||
handleInitialSyncDone: function(response) {
|
||||
console.log("# handleInitialSyncDone");
|
||||
initialSyncDeferred.resolve(initialSyncData);
|
||||
|
||||
var rooms = response.data.rooms;
|
||||
for (var i = 0; i < rooms.length; ++i) {
|
||||
var room = rooms[i];
|
||||
|
||||
// FIXME: This is ming: the HS should be sending down the m.room.member
|
||||
// event for the invite in .state but it isn't, so fudge it for now.
|
||||
if (room.inviter && room.membership === "invite") {
|
||||
var me = matrixService.config().user_id;
|
||||
var fakeEvent = {
|
||||
event_id: "__FAKE__" + room.room_id,
|
||||
user_id: room.inviter,
|
||||
origin_server_ts: 0,
|
||||
room_id: room.room_id,
|
||||
state_key: me,
|
||||
type: "m.room.member",
|
||||
content: {
|
||||
membership: "invite"
|
||||
}
|
||||
};
|
||||
if (!room.state) {
|
||||
room.state = [];
|
||||
}
|
||||
room.state.push(fakeEvent);
|
||||
console.log("RECV /initialSync invite >> "+room.room_id);
|
||||
}
|
||||
|
||||
var newRoom = modelService.getRoom(room.room_id);
|
||||
newRoom.current_room_state.storeStateEvents(room.state);
|
||||
newRoom.old_room_state.storeStateEvents(room.state);
|
||||
|
||||
// this should be done AFTER storing state events since these
|
||||
// messages may make the old_room_state diverge.
|
||||
if ("messages" in room) {
|
||||
this.handleRoomMessages(room.room_id, room.messages, false);
|
||||
newRoom.current_room_state.pagination_token = room.messages.end;
|
||||
newRoom.old_room_state.pagination_token = room.messages.start;
|
||||
}
|
||||
}
|
||||
var presence = response.data.presence;
|
||||
this.handleEvents(presence, false);
|
||||
|
||||
initialSyncDeferred.resolve(response);
|
||||
},
|
||||
|
||||
// Returns a promise that resolves when the initialSync request has been processed
|
||||
|
@ -571,15 +528,13 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
getLastMessage: function(room_id, filterEcho) {
|
||||
var lastMessage;
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
for (var i = room.messages.length - 1; i >= 0; i--) {
|
||||
var message = room.messages[i];
|
||||
var events = modelService.getRoom(room_id).events;
|
||||
for (var i = events.length - 1; i >= 0; i--) {
|
||||
var message = events[i];
|
||||
|
||||
if (!filterEcho || undefined === message.echo_msg_state) {
|
||||
lastMessage = message;
|
||||
break;
|
||||
}
|
||||
if (!filterEcho || undefined === message.echo_msg_state) {
|
||||
lastMessage = message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,18 +549,15 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
getUsersCountInRoom: function(room_id) {
|
||||
var memberCount;
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
memberCount = 0;
|
||||
var room = modelService.getRoom(room_id);
|
||||
memberCount = 0;
|
||||
for (var i in room.current_room_state.members) {
|
||||
if (!room.current_room_state.members.hasOwnProperty(i)) continue;
|
||||
|
||||
for (var i in room.members) {
|
||||
if (!room.members.hasOwnProperty(i)) continue;
|
||||
var member = room.current_room_state.members[i].event;
|
||||
|
||||
var member = room.members[i];
|
||||
|
||||
if ("join" === member.membership) {
|
||||
memberCount = memberCount + 1;
|
||||
}
|
||||
if ("join" === member.content.membership) {
|
||||
memberCount = memberCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,13 +565,24 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
},
|
||||
|
||||
/**
|
||||
* Get the member object of a room member
|
||||
* Return the power level of an user in a particular room
|
||||
* @param {String} room_id the room id
|
||||
* @param {String} user_id the id of the user
|
||||
* @returns {undefined | Object} the member object of this user in this room if he is part of the room
|
||||
* @param {String} user_id the user id
|
||||
* @returns {Number} a value between 0 and 10
|
||||
*/
|
||||
getMember: function(room_id, user_id) {
|
||||
return getMember(room_id, user_id);
|
||||
getUserPowerLevel: function(room_id, user_id) {
|
||||
var powerLevel = 0;
|
||||
var room = modelService.getRoom(room_id).current_room_state;
|
||||
if (room.state("m.room.power_levels")) {
|
||||
if (user_id in room.state("m.room.power_levels").content) {
|
||||
powerLevel = room.state("m.room.power_levels").content[user_id];
|
||||
}
|
||||
else {
|
||||
// Use the room default user power
|
||||
powerLevel = room.state("m.room.power_levels").content["default"];
|
||||
}
|
||||
}
|
||||
return powerLevel;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -630,18 +593,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
|||
*/
|
||||
getUserDisplayName: function(room_id, user_id) {
|
||||
return getUserDisplayName(room_id, user_id);
|
||||
},
|
||||
|
||||
setRoomVisibility: function(room_id, visible) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
initRoom(room_id);
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
room.visibility = visible;
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
|
@ -109,25 +109,6 @@ angular.module('eventStreamService', [])
|
|||
// without requiring to make an additional request
|
||||
matrixService.initialSync(30, false).then(
|
||||
function(response) {
|
||||
var rooms = response.data.rooms;
|
||||
for (var i = 0; i < rooms.length; ++i) {
|
||||
var room = rooms[i];
|
||||
|
||||
eventHandlerService.initRoom(room);
|
||||
|
||||
if ("messages" in room) {
|
||||
eventHandlerService.handleRoomMessages(room.room_id, room.messages, false);
|
||||
}
|
||||
|
||||
if ("state" in room) {
|
||||
eventHandlerService.handleEvents(room.state, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
var presence = response.data.presence;
|
||||
eventHandlerService.handleEvents(presence, false);
|
||||
|
||||
// Initial sync is done
|
||||
eventHandlerService.handleInitialSyncDone(response);
|
||||
|
||||
// Start event streaming from that point
|
|
@ -40,14 +40,11 @@ window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConne
|
|||
window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
|
||||
window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
|
||||
|
||||
// Returns true if the browser supports all required features to make WebRTC call
|
||||
var isWebRTCSupported = function () {
|
||||
return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate);
|
||||
};
|
||||
|
||||
angular.module('MatrixCall', [])
|
||||
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) {
|
||||
$rootScope.isWebRTCSupported = isWebRTCSupported();
|
||||
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $rootScope, $timeout) {
|
||||
$rootScope.isWebRTCSupported = function () {
|
||||
return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate);
|
||||
};
|
||||
|
||||
var MatrixCall = function(room_id) {
|
||||
this.room_id = room_id;
|
||||
|
@ -213,8 +210,8 @@ angular.module('MatrixCall', [])
|
|||
|
||||
var self = this;
|
||||
|
||||
var roomMembers = $rootScope.events.rooms[this.room_id].members;
|
||||
if (roomMembers[matrixService.config().user_id].membership != 'join') {
|
||||
var roomMembers = modelService.getRoom(this.room_id).current_room_state.members;
|
||||
if (roomMembers[matrixService.config().user_id].event.content.membership != 'join') {
|
||||
console.log("We need to join the room before we can accept this call");
|
||||
matrixService.join(this.room_id).then(function() {
|
||||
self.answer();
|
120
syweb/webclient/components/matrix/matrix-filter.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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';
|
||||
|
||||
angular.module('matrixFilter', [])
|
||||
|
||||
// Compute the room name according to information we have
|
||||
// TODO: It would be nice if this was stateless and had no dependencies. That would
|
||||
// make the business logic here a lot easier to see.
|
||||
.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', 'modelService',
|
||||
function($rootScope, matrixService, eventHandlerService, modelService) {
|
||||
return function(room_id) {
|
||||
var roomName;
|
||||
|
||||
// If there is an alias, use it
|
||||
// TODO: only one alias is managed for now
|
||||
var alias = matrixService.getRoomIdToAliasMapping(room_id);
|
||||
var room = modelService.getRoom(room_id).current_room_state;
|
||||
|
||||
var room_name_event = room.state("m.room.name");
|
||||
|
||||
// Determine if it is a public room
|
||||
var isPublicRoom = false;
|
||||
if (room.state("m.room.join_rules") && room.state("m.room.join_rules").content) {
|
||||
isPublicRoom = ("public" === room.state("m.room.join_rules").content.join_rule);
|
||||
}
|
||||
|
||||
if (room_name_event) {
|
||||
roomName = room_name_event.content.name;
|
||||
}
|
||||
else if (alias) {
|
||||
roomName = alias;
|
||||
}
|
||||
else if (Object.keys(room.members).length > 0 && !isPublicRoom) { // Do not rename public room
|
||||
var user_id = matrixService.config().user_id;
|
||||
|
||||
// this is a "one to one" room and should have the name of the other user.
|
||||
if (Object.keys(room.members).length === 2) {
|
||||
for (var i in room.members) {
|
||||
if (!room.members.hasOwnProperty(i)) continue;
|
||||
|
||||
var member = room.members[i].event;
|
||||
if (member.state_key !== user_id) {
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, member.state_key);
|
||||
if (!roomName) {
|
||||
roomName = member.state_key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Object.keys(room.members).length === 1) {
|
||||
// this could be just us (self-chat) or could be the other person
|
||||
// in a room if they have invited us to the room. Find out which.
|
||||
var otherUserId = Object.keys(room.members)[0];
|
||||
if (otherUserId === user_id) {
|
||||
// it's us, we may have been invited to this room or it could
|
||||
// be a self chat.
|
||||
if (room.members[otherUserId].event.content.membership === "invite") {
|
||||
// someone invited us, use the right ID.
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, room.members[otherUserId].event.user_id);
|
||||
if (!roomName) {
|
||||
roomName = room.members[otherUserId].event.user_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
|
||||
if (!roomName) {
|
||||
roomName = user_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // it isn't us, so use their name if we know it.
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
|
||||
if (!roomName) {
|
||||
roomName = otherUserId;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Object.keys(room.members).length === 0) {
|
||||
// this shouldn't be possible
|
||||
console.error("0 members in room >> " + room_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Always show the alias in the room displayed name
|
||||
if (roomName && alias && alias !== roomName) {
|
||||
roomName += " (" + alias + ")";
|
||||
}
|
||||
|
||||
if (undefined === roomName) {
|
||||
// By default, use the room ID
|
||||
roomName = room_id;
|
||||
}
|
||||
|
||||
return roomName;
|
||||
};
|
||||
}])
|
||||
|
||||
// Return the user display name
|
||||
.filter('mUserDisplayName', ['eventHandlerService', function(eventHandlerService) {
|
||||
return function(user_id, room_id) {
|
||||
return eventHandlerService.getUserDisplayName(room_id, user_id);
|
||||
};
|
||||
}]);
|
|
@ -60,7 +60,7 @@ angular.module('matrixPhoneService', [])
|
|||
var MatrixCall = $injector.get('MatrixCall');
|
||||
var call = new MatrixCall(event.room_id);
|
||||
|
||||
if (!isWebRTCSupported()) {
|
||||
if (!$rootScope.isWebRTCSupported()) {
|
||||
console.log("Incoming call ID "+msg.call_id+" but this browser doesn't support WebRTC");
|
||||
// don't hang up the call: there could be other clients connected that do support WebRTC and declining the
|
||||
// the call on their behalf would be really annoying.
|
|
@ -267,7 +267,7 @@ angular.module('matrixService', [])
|
|||
|
||||
// get room state for a specific room
|
||||
roomState: function(room_id) {
|
||||
var path = "/rooms/" + room_id + "/state";
|
||||
var path = "/rooms/" + encodeURIComponent(room_id) + "/state";
|
||||
return doRequest("GET", path);
|
||||
},
|
||||
|
||||
|
@ -375,9 +375,11 @@ angular.module('matrixService', [])
|
|||
|
||||
|
||||
sendStateEvent: function(room_id, eventType, content, state_key) {
|
||||
var path = "/rooms/$room_id/state/"+eventType;
|
||||
var path = "/rooms/$room_id/state/"+ eventType;
|
||||
// TODO: uncomment this when matrix.org is updated, else all state events 500.
|
||||
// var path = "/rooms/$room_id/state/"+ encodeURIComponent(eventType);
|
||||
if (state_key !== undefined) {
|
||||
path += "/" + state_key;
|
||||
path += "/" + encodeURIComponent(state_key);
|
||||
}
|
||||
room_id = encodeURIComponent(room_id);
|
||||
path = path.replace("$room_id", room_id);
|
||||
|
@ -422,7 +424,8 @@ angular.module('matrixService', [])
|
|||
var content = {
|
||||
msgtype: "m.image",
|
||||
url: image_url,
|
||||
body: image_body
|
||||
info: image_body,
|
||||
body: "Image"
|
||||
};
|
||||
|
||||
return this.sendMessage(room_id, msg_id, content);
|
||||
|
@ -440,7 +443,8 @@ angular.module('matrixService', [])
|
|||
|
||||
redactEvent: function(room_id, event_id) {
|
||||
var path = "/rooms/$room_id/redact/$event_id";
|
||||
path = path.replace("$room_id", room_id);
|
||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||
// TODO: encodeURIComponent when HS updated.
|
||||
path = path.replace("$event_id", event_id);
|
||||
var content = {};
|
||||
return doRequest("POST", path, undefined, content);
|
||||
|
@ -458,7 +462,7 @@ angular.module('matrixService', [])
|
|||
|
||||
paginateBackMessages: function(room_id, from_token, limit) {
|
||||
var path = "/rooms/$room_id/messages";
|
||||
path = path.replace("$room_id", room_id);
|
||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||
var params = {
|
||||
from: from_token,
|
||||
limit: limit,
|
||||
|
@ -506,12 +510,12 @@ angular.module('matrixService', [])
|
|||
|
||||
setProfileInfo: function(data, info_segment) {
|
||||
var path = "/profile/$user/" + info_segment;
|
||||
path = path.replace("$user", config.user_id);
|
||||
path = path.replace("$user", encodeURIComponent(config.user_id));
|
||||
return doRequest("PUT", path, undefined, data);
|
||||
},
|
||||
|
||||
getProfileInfo: function(userId, info_segment) {
|
||||
var path = "/profile/"+userId
|
||||
var path = "/profile/"+encodeURIComponent(userId);
|
||||
if (info_segment) path += '/' + info_segment;
|
||||
return doRequest("GET", path);
|
||||
},
|
||||
|
@ -630,7 +634,7 @@ angular.module('matrixService', [])
|
|||
// Set the logged in user presence state
|
||||
setUserPresence: function(presence) {
|
||||
var path = "/presence/$user_id/status";
|
||||
path = path.replace("$user_id", config.user_id);
|
||||
path = path.replace("$user_id", encodeURIComponent(config.user_id));
|
||||
return doRequest("PUT", path, undefined, {
|
||||
presence: presence
|
||||
});
|
||||
|
@ -724,57 +728,30 @@ angular.module('matrixService', [])
|
|||
//console.log("looking for roomId for " + alias + "; found: " + roomId);
|
||||
return roomId;
|
||||
},
|
||||
|
||||
/****** Power levels management ******/
|
||||
|
||||
/**
|
||||
* Return the power level of an user in a particular room
|
||||
* @param {String} room_id the room id
|
||||
* @param {String} user_id the user id
|
||||
* @returns {Number} a value between 0 and 10
|
||||
*/
|
||||
getUserPowerLevel: function(room_id, user_id) {
|
||||
var powerLevel = 0;
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room && room["m.room.power_levels"]) {
|
||||
if (user_id in room["m.room.power_levels"].content) {
|
||||
powerLevel = room["m.room.power_levels"].content[user_id];
|
||||
}
|
||||
else {
|
||||
// Use the room default user power
|
||||
powerLevel = room["m.room.power_levels"].content["default"];
|
||||
}
|
||||
}
|
||||
return powerLevel;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change or reset the power level of a user
|
||||
* @param {String} room_id the room id
|
||||
* @param {String} user_id the user id
|
||||
* @param {Number} powerLevel a value between 0 and 10
|
||||
* @param {Number} powerLevel The desired power level.
|
||||
* If undefined, the user power level will be reset, ie he will use the default room user power level
|
||||
* @param event The existing m.room.power_levels event if one exists.
|
||||
* @returns {promise} an $http promise
|
||||
*/
|
||||
setUserPowerLevel: function(room_id, user_id, powerLevel) {
|
||||
|
||||
// Hack: currently, there is no home server API so do it by hand by updating
|
||||
// the current m.room.power_levels of the room and send it to the server
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room && room["m.room.power_levels"]) {
|
||||
var content = angular.copy(room["m.room.power_levels"].content);
|
||||
content[user_id] = powerLevel;
|
||||
|
||||
var path = "/rooms/$room_id/state/m.room.power_levels";
|
||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||
|
||||
return doRequest("PUT", path, undefined, content);
|
||||
setUserPowerLevel: function(room_id, user_id, powerLevel, event) {
|
||||
var content = {};
|
||||
if (event) {
|
||||
// if there is an existing event, copy the content as it contains
|
||||
// the power level values for other members which we do not want
|
||||
// to modify.
|
||||
content = angular.copy(event.content);
|
||||
}
|
||||
|
||||
// The room does not exist or does not contain power_levels data
|
||||
var deferred = $q.defer();
|
||||
deferred.reject({data:{error: "Invalid room: " + room_id}});
|
||||
return deferred.promise;
|
||||
content[user_id] = powerLevel;
|
||||
|
||||
var path = "/rooms/$room_id/state/m.room.power_levels";
|
||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||
|
||||
return doRequest("PUT", path, undefined, content);
|
||||
},
|
||||
|
||||
getTurnServer: function() {
|
172
syweb/webclient/components/matrix/model-service.js
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
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 serves as the entry point for all models in the app. If access to
|
||||
underlying data in a room is required, then this service should be used as the
|
||||
dependency.
|
||||
*/
|
||||
// NB: This is more explicit than linking top-level models to $rootScope
|
||||
// in that by adding this service as a dep you are clearly saying "this X
|
||||
// needs access to the underlying data store", rather than polluting the
|
||||
// $rootScope.
|
||||
angular.module('modelService', [])
|
||||
.factory('modelService', ['matrixService', function(matrixService) {
|
||||
|
||||
/***** Room Object *****/
|
||||
var Room = function Room(room_id) {
|
||||
this.room_id = room_id;
|
||||
this.old_room_state = new RoomState();
|
||||
this.current_room_state = new RoomState();
|
||||
this.events = []; // events which can be displayed on the UI. TODO move?
|
||||
};
|
||||
Room.prototype = {
|
||||
addMessageEvents: function addMessageEvents(events, toFront) {
|
||||
for (var i=0; i<events.length; i++) {
|
||||
this.addMessageEvent(events[i], toFront);
|
||||
}
|
||||
},
|
||||
|
||||
addMessageEvent: function addMessageEvent(event, toFront) {
|
||||
// every message must reference the RoomMember which made it *at
|
||||
// that time* so things like display names display correctly.
|
||||
var stateAtTheTime = toFront ? this.old_room_state : this.current_room_state;
|
||||
event.__room_member = stateAtTheTime.getStateEvent("m.room.member", event.user_id);
|
||||
if (event.type === "m.room.member" && event.content.membership === "invite") {
|
||||
// give information on both the inviter and invitee
|
||||
event.__target_room_member = stateAtTheTime.getStateEvent("m.room.member", event.state_key);
|
||||
}
|
||||
|
||||
if (toFront) {
|
||||
this.events.unshift(event);
|
||||
}
|
||||
else {
|
||||
this.events.push(event);
|
||||
}
|
||||
},
|
||||
|
||||
addOrReplaceMessageEvent: function addOrReplaceMessageEvent(event, toFront) {
|
||||
// Start looking from the tail since the first goal of this function
|
||||
// is to find a message among the latest ones
|
||||
for (var i = this.events.length - 1; i >= 0; i--) {
|
||||
var storedEvent = this.events[i];
|
||||
if (storedEvent.event_id === event.event_id) {
|
||||
// It's clobbering time!
|
||||
this.events[i] = event;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.addMessageEvent(event, toFront);
|
||||
},
|
||||
|
||||
leave: function leave() {
|
||||
return matrixService.leave(this.room_id);
|
||||
}
|
||||
};
|
||||
|
||||
/***** Room State Object *****/
|
||||
var RoomState = function RoomState() {
|
||||
// list of RoomMember
|
||||
this.members = {};
|
||||
// state events, the key is a compound of event type + state_key
|
||||
this.state_events = {};
|
||||
this.pagination_token = "";
|
||||
};
|
||||
RoomState.prototype = {
|
||||
// get a state event for this room from this.state_events. State events
|
||||
// are unique per type+state_key tuple, with a lot of events using 0-len
|
||||
// state keys. To make it not Really Annoying to access, this method is
|
||||
// provided which can just be given the type and it will return the
|
||||
// 0-len event by default.
|
||||
state: function state(type, state_key) {
|
||||
if (!type) {
|
||||
return undefined; // event type MUST be specified
|
||||
}
|
||||
if (!state_key) {
|
||||
return this.state_events[type]; // treat as 0-len state key
|
||||
}
|
||||
return this.state_events[type + state_key];
|
||||
},
|
||||
|
||||
storeStateEvent: function storeState(event) {
|
||||
this.state_events[event.type + event.state_key] = event;
|
||||
if (event.type === "m.room.member") {
|
||||
var rm = new RoomMember();
|
||||
rm.event = event;
|
||||
this.members[event.state_key] = rm;
|
||||
}
|
||||
},
|
||||
|
||||
storeStateEvents: function storeState(events) {
|
||||
if (!events) {
|
||||
return;
|
||||
}
|
||||
for (var i=0; i<events.length; i++) {
|
||||
this.storeStateEvent(events[i]);
|
||||
}
|
||||
},
|
||||
|
||||
getStateEvent: function getStateEvent(event_type, state_key) {
|
||||
return this.state_events[event_type + state_key];
|
||||
}
|
||||
};
|
||||
|
||||
/***** Room Member Object *****/
|
||||
var RoomMember = function RoomMember() {
|
||||
this.event = {}; // the m.room.member event representing the RoomMember.
|
||||
this.user = undefined; // the User
|
||||
};
|
||||
|
||||
/***** User Object *****/
|
||||
var User = function User() {
|
||||
this.event = {}; // the m.presence event representing the User.
|
||||
};
|
||||
|
||||
// rooms are stored here when they come in.
|
||||
var rooms = {
|
||||
// roomid: <Room>
|
||||
};
|
||||
|
||||
console.log("Models inited.");
|
||||
|
||||
return {
|
||||
|
||||
getRoom: function(roomId) {
|
||||
if(!rooms[roomId]) {
|
||||
rooms[roomId] = new Room(roomId);
|
||||
}
|
||||
return rooms[roomId];
|
||||
},
|
||||
|
||||
getRooms: function() {
|
||||
return rooms;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the member object of a room member
|
||||
* @param {String} room_id the room id
|
||||
* @param {String} user_id the id of the user
|
||||
* @returns {undefined | Object} the member object of this user in this room if he is part of the room
|
||||
*/
|
||||
getMember: function(room_id, user_id) {
|
||||
var room = this.getRoom(room_id);
|
||||
return room.current_room_state.members[user_id];
|
||||
}
|
||||
|
||||
};
|
||||
}]);
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
@ -58,7 +58,6 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
|
|||
// Add room_alias & room_display_name members
|
||||
angular.extend(room, matrixService.getRoomAliasAndDisplayName(room));
|
||||
|
||||
eventHandlerService.setRoomVisibility(room.room_id, "public");
|
||||
}
|
||||
}
|
||||
);
|
Before Width: | Height: | Size: 397 B After Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 194 B |
Before Width: | Height: | Size: 434 B After Width: | Height: | Size: 434 B |
Before Width: | Height: | Size: 910 B After Width: | Height: | Size: 910 B |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
|
@ -13,7 +13,7 @@
|
|||
|
||||
<script type='text/javascript' src='js/jquery-1.8.3.min.js'></script>
|
||||
<script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
|
||||
<script src="js/angular.min.js"></script>
|
||||
<script src="js/angular.js"></script>
|
||||
<script src="js/angular-route.min.js"></script>
|
||||
<script src="js/angular-sanitize.min.js"></script>
|
||||
<script src="js/angular-animate.min.js"></script>
|
||||
|
@ -42,6 +42,7 @@
|
|||
<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/model-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>
|
||||
|
@ -84,7 +85,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<span ng-show="currentCall.state == 'ringing'">
|
||||
<button ng-click="answerCall()" ng-disabled="!isWebRTCSupported" title="{{isWebRTCSupported ? '' : 'Your browser does not support VoIP' }}">Answer {{ currentCall.type }} call</button>
|
||||
<button ng-click="answerCall()" ng-disabled="!isWebRTCSupported()" title="{{isWebRTCSupported() ? '' : 'Your browser does not support VoIP' }}">Answer {{ currentCall.type }} call</button>
|
||||
<button ng-click="hangupCall()">Reject</button>
|
||||
</span>
|
||||
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
|
|
@ -1,10 +1,3 @@
|
|||
/**
|
||||
* @license AngularJS v1.2.22
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
|
@ -63,6 +56,8 @@ angular.mock.$Browser = function() {
|
|||
return listener;
|
||||
};
|
||||
|
||||
self.$$checkUrlChange = angular.noop;
|
||||
|
||||
self.cookieHash = {};
|
||||
self.lastCookieHash = {};
|
||||
self.deferredFns = [];
|
||||
|
@ -125,7 +120,7 @@ angular.mock.$Browser = function() {
|
|||
}
|
||||
};
|
||||
|
||||
self.$$baseHref = '';
|
||||
self.$$baseHref = '/';
|
||||
self.baseHref = function() {
|
||||
return this.$$baseHref;
|
||||
};
|
||||
|
@ -774,13 +769,22 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
|||
};
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', function($delegate, $$asyncCallback) {
|
||||
$provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser',
|
||||
function($delegate, $$asyncCallback, $timeout, $browser) {
|
||||
var animate = {
|
||||
queue : [],
|
||||
cancel : $delegate.cancel,
|
||||
enabled : $delegate.enabled,
|
||||
triggerCallbacks : function() {
|
||||
triggerCallbackEvents : function() {
|
||||
$$asyncCallback.flush();
|
||||
},
|
||||
triggerCallbackPromise : function() {
|
||||
$timeout.flush(0);
|
||||
},
|
||||
triggerCallbacks : function() {
|
||||
this.triggerCallbackEvents();
|
||||
this.triggerCallbackPromise();
|
||||
},
|
||||
triggerReflow : function() {
|
||||
angular.forEach(reflowQueue, function(fn) {
|
||||
fn();
|
||||
|
@ -797,12 +801,12 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
|||
element : arguments[0],
|
||||
args : arguments
|
||||
});
|
||||
$delegate[method].apply($delegate, arguments);
|
||||
return $delegate[method].apply($delegate, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
return animate;
|
||||
});
|
||||
}]);
|
||||
|
||||
}]);
|
||||
|
||||
|
@ -888,7 +892,7 @@ angular.mock.dump = function(object) {
|
|||
* development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
|
||||
*
|
||||
* During unit testing, we want our unit tests to run quickly and have no external dependencies so
|
||||
* we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or
|
||||
* we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or
|
||||
* [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is
|
||||
* to verify whether a certain request has been sent or not, or alternatively just let the
|
||||
* application make requests, respond with pre-trained responses and assert that the end result is
|
||||
|
@ -1007,13 +1011,14 @@ angular.mock.dump = function(object) {
|
|||
```js
|
||||
// testing controller
|
||||
describe('MyController', function() {
|
||||
var $httpBackend, $rootScope, createController;
|
||||
var $httpBackend, $rootScope, createController, authRequestHandler;
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
// Set up the mock http service responses
|
||||
$httpBackend = $injector.get('$httpBackend');
|
||||
// backend definition common for all tests
|
||||
$httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
|
||||
authRequestHandler = $httpBackend.when('GET', '/auth.py')
|
||||
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
|
||||
|
||||
// Get hold of a scope (i.e. the root scope)
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
|
@ -1039,11 +1044,23 @@ angular.mock.dump = function(object) {
|
|||
});
|
||||
|
||||
|
||||
it('should fail authentication', function() {
|
||||
|
||||
// Notice how you can change the response even after it was set
|
||||
authRequestHandler.respond(401, '');
|
||||
|
||||
$httpBackend.expectGET('/auth.py');
|
||||
var controller = createController();
|
||||
$httpBackend.flush();
|
||||
expect($rootScope.status).toBe('Failed...');
|
||||
});
|
||||
|
||||
|
||||
it('should send msg to server', function() {
|
||||
var controller = createController();
|
||||
$httpBackend.flush();
|
||||
|
||||
// now you don’t care about the authentication, but
|
||||
// now you don’t care about the authentication, but
|
||||
// the controller will still send the request and
|
||||
// $httpBackend will respond without you having to
|
||||
// specify the expectation and response for this request
|
||||
|
@ -1186,32 +1203,39 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*
|
||||
* - respond –
|
||||
* - respond –
|
||||
* `{function([status,] data[, headers, statusText])
|
||||
* | function(function(method, url, data, headers)}`
|
||||
* – The respond method takes a set of static data to be returned or a function that can
|
||||
* – The respond method takes a set of static data to be returned or a function that can
|
||||
* return an array containing response status (number), response data (string), response
|
||||
* headers (Object), and the text for the status (string).
|
||||
* headers (Object), and the text for the status (string). The respond method returns the
|
||||
* `requestHandler` object for possible overrides.
|
||||
*/
|
||||
$httpBackend.when = function(method, url, data, headers) {
|
||||
var definition = new MockHttpExpectation(method, url, data, headers),
|
||||
chain = {
|
||||
respond: function(status, data, headers, statusText) {
|
||||
definition.passThrough = undefined;
|
||||
definition.response = createResponse(status, data, headers, statusText);
|
||||
return chain;
|
||||
}
|
||||
};
|
||||
|
||||
if ($browser) {
|
||||
chain.passThrough = function() {
|
||||
definition.response = undefined;
|
||||
definition.passThrough = true;
|
||||
return chain;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1225,10 +1249,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1237,10 +1263,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1249,10 +1277,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1261,12 +1291,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1275,12 +1307,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1289,9 +1323,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
createShortMethods('when');
|
||||
|
||||
|
@ -1303,30 +1339,36 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* Creates a new request expectation.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*
|
||||
* - respond –
|
||||
* - respond –
|
||||
* `{function([status,] data[, headers, statusText])
|
||||
* | function(function(method, url, data, headers)}`
|
||||
* – The respond method takes a set of static data to be returned or a function that can
|
||||
* – The respond method takes a set of static data to be returned or a function that can
|
||||
* return an array containing response status (number), response data (string), response
|
||||
* headers (Object), and the text for the status (string).
|
||||
* headers (Object), and the text for the status (string). The respond method returns the
|
||||
* `requestHandler` object for possible overrides.
|
||||
*/
|
||||
$httpBackend.expect = function(method, url, data, headers) {
|
||||
var expectation = new MockHttpExpectation(method, url, data, headers);
|
||||
var expectation = new MockHttpExpectation(method, url, data, headers),
|
||||
chain = {
|
||||
respond: function (status, data, headers, statusText) {
|
||||
expectation.response = createResponse(status, data, headers, statusText);
|
||||
return chain;
|
||||
}
|
||||
};
|
||||
|
||||
expectations.push(expectation);
|
||||
return {
|
||||
respond: function (status, data, headers, statusText) {
|
||||
expectation.response = createResponse(status, data, headers, statusText);
|
||||
}
|
||||
};
|
||||
return chain;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1336,10 +1378,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled. See #expect for more info.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled. See #expect for more info.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1348,10 +1392,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1360,10 +1406,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1372,13 +1420,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1387,13 +1437,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1402,13 +1454,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1417,9 +1471,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* request is handled.
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
createShortMethods('expect');
|
||||
|
||||
|
@ -1434,11 +1490,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* all pending requests will be flushed. If there are no pending requests when the flush method
|
||||
* is called an exception is thrown (as this typically a sign of programming error).
|
||||
*/
|
||||
$httpBackend.flush = function(count) {
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush = function(count, digest) {
|
||||
if (digest !== false) $rootScope.$digest();
|
||||
if (!responses.length) throw new Error('No pending request to flush !');
|
||||
|
||||
if (angular.isDefined(count)) {
|
||||
if (angular.isDefined(count) && count !== null) {
|
||||
while (count--) {
|
||||
if (!responses.length) throw new Error('No more pending request to flush !');
|
||||
responses.shift()();
|
||||
|
@ -1448,7 +1504,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
responses.shift()();
|
||||
}
|
||||
}
|
||||
$httpBackend.verifyNoOutstandingExpectation();
|
||||
$httpBackend.verifyNoOutstandingExpectation(digest);
|
||||
};
|
||||
|
||||
|
||||
|
@ -1466,8 +1522,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
* afterEach($httpBackend.verifyNoOutstandingExpectation);
|
||||
* ```
|
||||
*/
|
||||
$httpBackend.verifyNoOutstandingExpectation = function() {
|
||||
$rootScope.$digest();
|
||||
$httpBackend.verifyNoOutstandingExpectation = function(digest) {
|
||||
if (digest !== false) $rootScope.$digest();
|
||||
if (expectations.length) {
|
||||
throw new Error('Unsatisfied requests: ' + expectations.join(', '));
|
||||
}
|
||||
|
@ -1511,7 +1567,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
|||
|
||||
|
||||
function createShortMethods(prefix) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
|
||||
$httpBackend[prefix + method] = function(url, headers) {
|
||||
return $httpBackend[prefix](method, url, undefined, headers);
|
||||
};
|
||||
|
@ -1541,6 +1597,7 @@ function MockHttpExpectation(method, url, data, headers) {
|
|||
this.matchUrl = function(u) {
|
||||
if (!url) return true;
|
||||
if (angular.isFunction(url.test)) return url.test(u);
|
||||
if (angular.isFunction(url)) return url(u);
|
||||
return url == u;
|
||||
};
|
||||
|
||||
|
@ -1627,7 +1684,7 @@ function MockXhr() {
|
|||
* that adds a "flush" and "verifyNoPendingTasks" methods.
|
||||
*/
|
||||
|
||||
angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
||||
angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -1666,9 +1723,9 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
|||
}
|
||||
|
||||
return $delegate;
|
||||
};
|
||||
}];
|
||||
|
||||
angular.mock.$RAFDecorator = function($delegate) {
|
||||
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||
var queue = [];
|
||||
var rafFn = function(fn) {
|
||||
var index = queue.length;
|
||||
|
@ -1694,9 +1751,9 @@ angular.mock.$RAFDecorator = function($delegate) {
|
|||
};
|
||||
|
||||
return rafFn;
|
||||
};
|
||||
}];
|
||||
|
||||
angular.mock.$AsyncCallbackDecorator = function($delegate) {
|
||||
angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) {
|
||||
var callbacks = [];
|
||||
var addFn = function(fn) {
|
||||
callbacks.push(fn);
|
||||
|
@ -1708,7 +1765,7 @@ angular.mock.$AsyncCallbackDecorator = function($delegate) {
|
|||
callbacks = [];
|
||||
};
|
||||
return addFn;
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1822,22 +1879,25 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*
|
||||
* - respond –
|
||||
* - respond –
|
||||
* `{function([status,] data[, headers, statusText])
|
||||
* | function(function(method, url, data, headers)}`
|
||||
* – The respond method takes a set of static data to be returned or a function that can return
|
||||
* – The respond method takes a set of static data to be returned or a function that can return
|
||||
* an array containing response status (number), response data (string), response headers
|
||||
* (Object), and the text for the status (string).
|
||||
* - passThrough – `{function()}` – Any request matching a backend definition with
|
||||
* - passThrough – `{function()}` – Any request matching a backend definition with
|
||||
* `passThrough` handler will be passed through to the real backend (an XHR request will be made
|
||||
* to the server.)
|
||||
* - Both methods return the `requestHandler` object for possible overrides.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1847,10 +1907,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1860,10 +1922,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1873,10 +1937,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1886,11 +1952,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1900,11 +1968,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1914,11 +1984,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1928,30 +2000,17 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled.
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
*/
|
||||
angular.mock.e2e = {};
|
||||
angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
|
||||
angular.mock.clearDataCache = function() {
|
||||
var key,
|
||||
cache = angular.element.cache;
|
||||
|
||||
for(key in cache) {
|
||||
if (Object.prototype.hasOwnProperty.call(cache,key)) {
|
||||
var handle = cache[key].handle;
|
||||
|
||||
handle && angular.element(handle.elem).off();
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if(window.jasmine || window.mocha) {
|
||||
|
||||
var currentSpec = null,
|
||||
|
@ -1982,8 +2041,6 @@ if(window.jasmine || window.mocha) {
|
|||
injector.get('$browser').pollFns.length = 0;
|
||||
}
|
||||
|
||||
angular.mock.clearDataCache();
|
||||
|
||||
// clean up jquery's fragment cache
|
||||
angular.forEach(angular.element.fragments, function(val, key) {
|
||||
delete angular.element.fragments[key];
|
||||
|
@ -2003,6 +2060,7 @@ if(window.jasmine || window.mocha) {
|
|||
* @description
|
||||
*
|
||||
* *NOTE*: This function is also published on window for easy access.<br>
|
||||
* *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha
|
||||
*
|
||||
* This function registers a module configuration code. It collects the configuration information
|
||||
* which will be used when the injector is created by {@link angular.mock.inject inject}.
|
||||
|
@ -2045,6 +2103,7 @@ if(window.jasmine || window.mocha) {
|
|||
* @description
|
||||
*
|
||||
* *NOTE*: This function is also published on window for easy access.<br>
|
||||
* *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha
|
||||
*
|
||||
* The inject function wraps a function into an injectable function. The inject() creates new
|
||||
* instance of {@link auto.$injector $injector} per test, which is then used for
|
||||
|
@ -2144,14 +2203,28 @@ if(window.jasmine || window.mocha) {
|
|||
/////////////////////
|
||||
function workFn() {
|
||||
var modules = currentSpec.$modules || [];
|
||||
|
||||
var strictDi = !!currentSpec.$injectorStrict;
|
||||
modules.unshift('ngMock');
|
||||
modules.unshift('ng');
|
||||
var injector = currentSpec.$injector;
|
||||
if (!injector) {
|
||||
injector = currentSpec.$injector = angular.injector(modules);
|
||||
if (strictDi) {
|
||||
// If strictDi is enabled, annotate the providerInjector blocks
|
||||
angular.forEach(modules, function(moduleFn) {
|
||||
if (typeof moduleFn === "function") {
|
||||
angular.injector.$$annotate(moduleFn);
|
||||
}
|
||||
});
|
||||
}
|
||||
injector = currentSpec.$injector = angular.injector(modules, strictDi);
|
||||
currentSpec.$injectorStrict = strictDi;
|
||||
}
|
||||
for(var i = 0, ii = blockFns.length; i < ii; i++) {
|
||||
if (currentSpec.$injectorStrict) {
|
||||
// If the injector is strict / strictDi, and the spec wants to inject using automatic
|
||||
// annotation, then annotate the function here.
|
||||
injector.annotate(blockFns[i]);
|
||||
}
|
||||
try {
|
||||
/* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
|
||||
injector.invoke(blockFns[i] || angular.noop, this);
|
||||
|
@ -2167,7 +2240,20 @@ if(window.jasmine || window.mocha) {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
angular.mock.inject.strictDi = function(value) {
|
||||
value = arguments.length ? !!value : true;
|
||||
return isSpecRunning() ? workFn() : workFn;
|
||||
|
||||
function workFn() {
|
||||
if (value !== currentSpec.$injectorStrict) {
|
||||
if (currentSpec.$injector) {
|
||||
throw new Error('Injector already created, can not modify strict annotations');
|
||||
} else {
|
||||
currentSpec.$injectorStrict = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
})(window, window.angular);
|
|
@ -124,7 +124,7 @@ angular.module('RegisterController', ['matrixService'])
|
|||
$location.url("home");
|
||||
},
|
||||
function(error) {
|
||||
console.trace("Registration error: "+error);
|
||||
console.error("Registration error: "+JSON.stringify(error));
|
||||
if (useCaptcha) {
|
||||
Recaptcha.reload();
|
||||
}
|
|
@ -17,11 +17,14 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('RecentsController', ['matrixService', 'matrixFilter'])
|
||||
.controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService',
|
||||
function($rootScope, $scope, eventHandlerService) {
|
||||
.controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService', 'modelService',
|
||||
function($rootScope, $scope, eventHandlerService, modelService) {
|
||||
|
||||
// Expose the service to the view
|
||||
$scope.eventHandlerService = eventHandlerService;
|
||||
|
||||
// retrieve all rooms and expose them
|
||||
$scope.rooms = modelService.getRooms();
|
||||
|
||||
// $rootScope of the parent where the recents component is included can override this value
|
||||
// in order to highlight a specific room in the list
|
|
@ -17,7 +17,7 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('RecentsController')
|
||||
.filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) {
|
||||
.filter('orderRecents', ["matrixService", "eventHandlerService", "modelService", function(matrixService, eventHandlerService, modelService) {
|
||||
return function(rooms) {
|
||||
var user_id = matrixService.config().user_id;
|
||||
|
||||
|
@ -25,26 +25,33 @@ angular.module('RecentsController')
|
|||
// The key, room_id, is already in value objects
|
||||
var filtered = [];
|
||||
angular.forEach(rooms, function(room, room_id) {
|
||||
|
||||
room.recent = {};
|
||||
var meEvent = room.current_room_state.state("m.room.member", user_id);
|
||||
// Show the room only if the user has joined it or has been invited
|
||||
// (ie, do not show it if he has been banned)
|
||||
var member = eventHandlerService.getMember(room_id, user_id);
|
||||
if (member && ("invite" === member.membership || "join" === member.membership)) {
|
||||
|
||||
var member = modelService.getMember(room_id, user_id);
|
||||
if (member) {
|
||||
member = member.event;
|
||||
}
|
||||
room.recent.me = member;
|
||||
if (member && ("invite" === member.content.membership || "join" === member.content.membership)) {
|
||||
if ("invite" === member.content.membership) {
|
||||
room.recent.inviter = member.user_id;
|
||||
}
|
||||
// Count users here
|
||||
// TODO: Compute it directly in eventHandlerService
|
||||
room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
|
||||
room.recent.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
|
||||
|
||||
filtered.push(room);
|
||||
}
|
||||
else if ("invite" === room.membership) {
|
||||
else if (meEvent && "invite" === meEvent.content.membership) {
|
||||
// The only information we have about the room is that the user has been invited
|
||||
filtered.push(room);
|
||||
}
|
||||
});
|
||||
|
||||
// And time sort them
|
||||
// The room with the lastest message at first
|
||||
// The room with the latest message at first
|
||||
filtered.sort(function (roomA, roomB) {
|
||||
|
||||
var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true);
|
|
@ -1,16 +1,16 @@
|
|||
<div ng-controller="RecentsController">
|
||||
<table class="recentsTable">
|
||||
<tbody ng-repeat="(index, room) in events.rooms | orderRecents"
|
||||
<tbody ng-repeat="(index, room) in rooms | orderRecents"
|
||||
ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )"
|
||||
class="recentsRoom"
|
||||
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
||||
<tr>
|
||||
<td ng-class="room['m.room.join_rules'].content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'">
|
||||
<td ng-class="room.current_room_state.state('m.room.join_rules').content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'">
|
||||
{{ room.room_id | mRoomName }}
|
||||
</td>
|
||||
<td class="recentsRoomSummaryUsersCount">
|
||||
<span ng-show="undefined !== room.numUsersInRoom">
|
||||
{{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }}
|
||||
<span ng-show="undefined !== room.recent.numUsersInRoom">
|
||||
{{ room.recent.numUsersInRoom || '1' }} {{ room.recent.numUsersInRoom == 1 ? 'user' : 'users' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="recentsRoomSummaryTS">
|
||||
|
@ -27,11 +27,11 @@
|
|||
<tr>
|
||||
<td colspan="3" class="recentsRoomSummary">
|
||||
|
||||
<div ng-show="room.membership === 'invite'">
|
||||
{{ room.inviter | mUserDisplayName: room.room_id }} invited you
|
||||
<div ng-show="room.recent.me.content.membership === 'invite'">
|
||||
{{ room.recent.inviter | mUserDisplayName: room.room_id }} invited you
|
||||
</div>
|
||||
|
||||
<div ng-hide="room.membership === 'invite'" ng-switch="lastMsg.type">
|
||||
<div ng-hide="room.recent.me.membership === 'invite'" ng-switch="lastMsg.type">
|
||||
<div ng-switch-when="m.room.member">
|
||||
<span ng-switch="lastMsg.changedKey">
|
||||
<span ng-switch-when="membership">
|
|
@ -15,8 +15,8 @@ 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', 'notificationService',
|
||||
function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService) {
|
||||
.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', 'modelService',
|
||||
function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService) {
|
||||
'use strict';
|
||||
var MESSAGES_PER_PAGINATION = 30;
|
||||
var THUMBNAIL_SIZE = 320;
|
||||
|
@ -64,7 +64,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
return;
|
||||
};
|
||||
|
||||
var nameEvent = $rootScope.events.rooms[$scope.room_id]['m.room.name'];
|
||||
var nameEvent = $scope.room.current_room_state.state_events['m.room.name'];
|
||||
if (nameEvent) {
|
||||
$scope.name.newNameText = nameEvent.content.name;
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
console.log("Warning: Already editing topic.");
|
||||
return;
|
||||
}
|
||||
var topicEvent = $rootScope.events.rooms[$scope.room_id]['m.room.topic'];
|
||||
var topicEvent = $scope.room.current_room_state.state_events['m.room.topic'];
|
||||
if (topicEvent) {
|
||||
$scope.topic.newTopicText = topicEvent.content.topic;
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
}
|
||||
notificationService.showNotification(
|
||||
userName +
|
||||
" (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")",
|
||||
" (" + $filter("mRoomName")(event.room_id) + ")",
|
||||
userName + " joined",
|
||||
event.content.avatar_url ? event.content.avatar_url : undefined,
|
||||
function() {
|
||||
|
@ -254,11 +254,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
$scope.state.paginating = true;
|
||||
}
|
||||
|
||||
console.log("paginateBackMessages from " + $rootScope.events.rooms[$scope.room_id].pagination.earliest_token + " for " + numItems);
|
||||
console.log("paginateBackMessages from " + $scope.room.old_room_state.pagination_token + " for " + numItems);
|
||||
var originalTopRow = $("#messageTable>tbody>tr:first")[0];
|
||||
|
||||
// Paginate events from the point in cache
|
||||
matrixService.paginateBackMessages($scope.room_id, $rootScope.events.rooms[$scope.room_id].pagination.earliest_token, numItems).then(
|
||||
matrixService.paginateBackMessages($scope.room_id, $scope.room.old_room_state.pagination_token, numItems).then(
|
||||
function(response) {
|
||||
|
||||
eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b');
|
||||
|
@ -404,7 +404,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
var updateUserPowerLevel = function(user_id) {
|
||||
var member = $scope.members[user_id];
|
||||
if (member) {
|
||||
member.powerLevel = matrixService.getUserPowerLevel($scope.room_id, user_id);
|
||||
member.powerLevel = eventHandlerService.getUserPowerLevel($scope.room_id, user_id);
|
||||
|
||||
normaliseMembersPowerLevels();
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
|
||||
var room_id = matrixService.getAliasToRoomIdMapping(room_alias);
|
||||
console.log("joining " + room_alias + " id=" + room_id);
|
||||
if ($rootScope.events.rooms[room_id]) {
|
||||
if ($scope.room) { // TODO actually check that you = join
|
||||
// don't send a join event for a room you're already in.
|
||||
$location.url("room/" + room_alias);
|
||||
}
|
||||
|
@ -576,7 +576,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
powerLevel = parseInt(matches[3]);
|
||||
}
|
||||
if (powerLevel !== NaN) {
|
||||
promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel);
|
||||
var powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels");
|
||||
promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel, powerLevelEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -591,7 +592,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined);
|
||||
var powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels");
|
||||
promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined, powerLevelEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,7 +631,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
};
|
||||
|
||||
$('#mainInput').val('');
|
||||
$rootScope.events.rooms[$scope.room_id].messages.push(echoMessage);
|
||||
$scope.room.addMessageEvent(echoMessage);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
|
@ -717,6 +719,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
|
||||
var onInit2 = function() {
|
||||
console.log("onInit2");
|
||||
// =============================
|
||||
$scope.room = modelService.getRoom($scope.room_id);
|
||||
// =============================
|
||||
|
||||
// Scroll down as soon as possible so that we point to the last message
|
||||
// if it already exists in memory
|
||||
|
@ -729,9 +734,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
var needsToJoin = true;
|
||||
|
||||
// The room members is available in the data fetched by initialSync
|
||||
if ($rootScope.events.rooms[$scope.room_id]) {
|
||||
if ($scope.room) {
|
||||
|
||||
var messages = $rootScope.events.rooms[$scope.room_id].messages;
|
||||
var messages = $scope.room.events;
|
||||
|
||||
if (0 === messages.length
|
||||
|| (1 === messages.length && "m.room.member" === messages[0].type && "invite" === messages[0].content.membership && $scope.state.user_id === messages[0].state_key)) {
|
||||
|
@ -743,19 +748,19 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
$scope.state.first_pagination = false;
|
||||
}
|
||||
|
||||
var members = $rootScope.events.rooms[$scope.room_id].members;
|
||||
var members = $scope.room.current_room_state.members;
|
||||
|
||||
// Update the member list
|
||||
for (var i in members) {
|
||||
if (!members.hasOwnProperty(i)) continue;
|
||||
|
||||
var member = members[i];
|
||||
var member = members[i].event;
|
||||
updateMemberList(member);
|
||||
}
|
||||
|
||||
// Check if the user has already join the room
|
||||
if ($scope.state.user_id in members) {
|
||||
if ("join" === members[$scope.state.user_id].membership) {
|
||||
if ("join" === members[$scope.state.user_id].event.content.membership) {
|
||||
needsToJoin = false;
|
||||
}
|
||||
}
|
||||
|
@ -999,10 +1004,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
};
|
||||
|
||||
$scope.openJson = function(content) {
|
||||
$scope.event_selected = content;
|
||||
$scope.event_selected = angular.copy(content);
|
||||
|
||||
// FIXME: Pre-calculated event data should be stripped in a nicer way.
|
||||
$scope.event_selected.__room_member = undefined;
|
||||
$scope.event_selected.__target_room_member = undefined;
|
||||
|
||||
// scope this so the template can check power levels and enable/disable
|
||||
// buttons
|
||||
$scope.pow = matrixService.getUserPowerLevel;
|
||||
$scope.pow = eventHandlerService.getUserPowerLevel;
|
||||
|
||||
var modalInstance = $modal.open({
|
||||
templateUrl: 'eventInfoTemplate.html',
|
||||
|
@ -1039,8 +1049,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
state_key: ""
|
||||
};
|
||||
|
||||
var stateFilter = $filter("stateEventsFilter");
|
||||
var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]);
|
||||
var stateEvents = $scope.room.current_room_state.state_events;
|
||||
// The modal dialog will 2-way bind this field, so we MUST make a deep
|
||||
// copy of the state events else we will be *actually adjusing our view
|
||||
// of the world* when fiddling with the JSON!! Apparently parse/stringify
|
||||
|
@ -1059,7 +1068,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
|||
console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected));
|
||||
$scope.redact = function() {
|
||||
console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+
|
||||
" Redact level = "+$scope.events.rooms[$scope.room_id]["m.room.ops_levels"].content.redact_level);
|
||||
" Redact level = "+$scope.room.current_room_state.state_events["m.room.ops_levels"].content.redact_level);
|
||||
console.log("Redact event >> " + JSON.stringify($scope.event_selected));
|
||||
$modalInstance.close("redact");
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="redact()" type="button" class="btn btn-danger"
|
||||
ng-disabled="!events.rooms[room_id]['m.room.ops_levels'].content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < events.rooms[room_id]['m.room.ops_levels'].content.redact_level"
|
||||
ng-disabled="!room.current_room_state.state('m.room.ops_levels').content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < room.current_room_state.state('m.room.ops_levels').content.redact_level"
|
||||
title="Delete this event on all home servers. This cannot be undone.">
|
||||
Redact
|
||||
</button>
|
||||
|
@ -18,7 +18,8 @@
|
|||
<table class="room-info">
|
||||
<tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event">
|
||||
<td class="room-info-event-meta" width="30%">
|
||||
<span class="monospace">{{ key }}</span>
|
||||
<span class="monospace">{{ event.type }}</span>
|
||||
<span ng-show="event.state_key" class="monospace"> ({{event.state_key}})</span>
|
||||
<br/>
|
||||
{{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
|
||||
<br/>
|
||||
|
@ -68,13 +69,13 @@
|
|||
</div>
|
||||
|
||||
<div class="roomTopicSection">
|
||||
<button ng-hide="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing"
|
||||
<button ng-hide="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing"
|
||||
ng-click="topic.editTopic()" class="roomTopicSetNew">
|
||||
Set Topic
|
||||
</button>
|
||||
<div ng-show="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing">
|
||||
<div ng-show="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing">
|
||||
<div ng-hide="topic.isEditing" ng-dblclick="topic.editTopic()" id="roomTopic">
|
||||
{{ events.rooms[room_id]['m.room.topic'].content.topic | limitTo: 200}}
|
||||
{{ room.current_room_state.state_events['m.room.topic'].content.topic | limitTo: 200}}
|
||||
</div>
|
||||
<form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm">
|
||||
<input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/>
|
||||
|
@ -123,32 +124,34 @@
|
|||
ng-style="{ 'visibility': state.messages_visibility }"
|
||||
keep-scroll>
|
||||
<table id="messageTable" infinite-scroll="paginateMore()">
|
||||
<tr ng-repeat="msg in events.rooms[room_id].messages"
|
||||
ng-class="(events.rooms[room_id].messages[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item>
|
||||
<tr ng-repeat="msg in room.events"
|
||||
ng-class="(room.events[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item>
|
||||
<td class="leftBlock">
|
||||
<div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id"> {{ msg.user_id | mUserDisplayName: room_id }}</div>
|
||||
<div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName: room_id }}</div>
|
||||
<div class="timestamp"
|
||||
ng-class="msg.echo_msg_state">
|
||||
{{ (msg.origin_server_ts) | date:'MMM d HH:mm' }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="avatar">
|
||||
<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"/>
|
||||
<!-- msg.__room_member.avatar_url is just backwards compat, and can be removed in the future. -->
|
||||
<img class="avatarImage" ng-src="{{ msg.__room_member.cnt.avatar_url || msg.__room_member.avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}"
|
||||
ng-hide="room.events[$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'">
|
||||
<div class="bubble" ng-click="openJson(msg)">
|
||||
<div class="bubble" ng-dblclick="openJson(msg)">
|
||||
<span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'">
|
||||
{{ members[msg.state_key].displayname || msg.state_key }} joined
|
||||
{{ msg.content.displayname || members[msg.state_key].displayname || msg.state_key }} joined
|
||||
</span>
|
||||
<span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'">
|
||||
<span ng-if="msg.user_id === msg.state_key">
|
||||
{{ members[msg.state_key].displayname || msg.state_key }} left
|
||||
<!-- FIXME: This seems like a synapse bug that the 'leave' content doesn't give the displayname... -->
|
||||
{{ msg.__room_member.cnt.displayname || members[msg.state_key].displayname || msg.state_key }} left
|
||||
</span>
|
||||
<span ng-if="msg.user_id !== msg.state_key && msg.prev_content">
|
||||
{{ members[msg.user_id].displayname || msg.user_id }}
|
||||
{{ msg.content.displayname || members[msg.user_id].displayname || msg.user_id }}
|
||||
{{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }}
|
||||
{{ members[msg.state_key].displayname || msg.state_key }}
|
||||
{{ msg.__target_room_member.content.displayname || msg.state_key }}
|
||||
<span ng-if="'join' === msg.prev_content.membership && msg.content.reason">
|
||||
: {{ msg.content.reason }}
|
||||
</span>
|
||||
|
@ -156,9 +159,9 @@
|
|||
</span>
|
||||
<span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' ||
|
||||
'ban' === msg.content.membership && msg.changedKey === 'membership'">
|
||||
{{ members[msg.user_id].displayname || msg.user_id }}
|
||||
{{ msg.__room_member.cnt.displayname || msg.user_id }}
|
||||
{{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }}
|
||||
{{ members[msg.state_key].displayname || msg.state_key }}
|
||||
{{ msg.__target_room_member.cnt.displayname || msg.state_key }}
|
||||
<span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason">
|
||||
: {{ msg.content.reason }}
|
||||
</span>
|
||||
|
@ -179,8 +182,8 @@
|
|||
(msg.content.formatted_body | unsanitizedLinky) :
|
||||
(msg.content.msgtype === 'm.text' && msg.type === 'm.room.message') ? (msg.content.body | linky:'_blank') : '' "/>
|
||||
|
||||
<span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span>
|
||||
<span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span>
|
||||
<span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span>
|
||||
<span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span>
|
||||
|
||||
<div ng-show='msg.content.msgtype === "m.image"'>
|
||||
<div ng-hide='msg.content.thumbnail_url' ng-style="msg.content.body.h && { 'height' : (msg.content.body.h < 320) ? msg.content.body.h : 320}">
|
||||
|
@ -204,7 +207,7 @@
|
|||
</td>
|
||||
<td class="rightBlock">
|
||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
||||
ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -245,15 +248,15 @@
|
|||
<button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave</button>
|
||||
<button ng-click="startVoiceCall()"
|
||||
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
||||
ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2"
|
||||
title ="{{ !isWebRTCSupported ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}"
|
||||
ng-disabled="state.permission_denied || !isWebRTCSupported() || memberCount() != 2"
|
||||
title ="{{ !isWebRTCSupported() ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}"
|
||||
>
|
||||
Voice Call
|
||||
</button>
|
||||
<button ng-click="startVideoCall()"
|
||||
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
||||
ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2"
|
||||
title ="{{ !isWebRTCSupported ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}"
|
||||
ng-disabled="state.permission_denied || !isWebRTCSupported() || memberCount() != 2"
|
||||
title ="{{ !isWebRTCSupported() ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}"
|
||||
>
|
||||
Video Call
|
||||
</button>
|
|
@ -1,13 +1,31 @@
|
|||
Requires:
|
||||
- nodejs/npm
|
||||
- npm install karma
|
||||
- npm install jasmine
|
||||
- npm install protractor (e2e testing)
|
||||
Testing is done using Karma.
|
||||
|
||||
Setting up continuous integration / run the unit tests (make sure you're in
|
||||
this directory so it can find the config file):
|
||||
|
||||
UNIT TESTING
|
||||
============
|
||||
|
||||
Requires the following:
|
||||
- npm/nodejs
|
||||
- phantomjs
|
||||
|
||||
Requires the following node packages:
|
||||
- npm install jasmine
|
||||
- npm install karma
|
||||
- npm install karma-jasmine
|
||||
- npm install karma-phantomjs-launcher
|
||||
- npm install karma-junit-reporter
|
||||
|
||||
Make sure you're in this directory so it can find the config file and run:
|
||||
karma start
|
||||
|
||||
You should see all the tests pass.
|
||||
|
||||
|
||||
E2E TESTING
|
||||
===========
|
||||
|
||||
npm install protractor
|
||||
|
||||
|
||||
Setting up e2e tests (only if you don't have a selenium server to run the tests
|
||||
on. If you do, edit the config to point to that url):
|
|
@ -23,18 +23,24 @@ module.exports = function(config) {
|
|||
'../js/angular-animate.js',
|
||||
'../js/angular-sanitize.js',
|
||||
'../js/ng-infinite-scroll-matrix.js',
|
||||
'../login/**/*.*',
|
||||
'../room/**/*.*',
|
||||
'../components/**/*.*',
|
||||
'../user/**/*.*',
|
||||
'../home/**/*.*',
|
||||
'../recents/**/*.*',
|
||||
'../settings/**/*.*',
|
||||
'../js/ui-bootstrap*',
|
||||
'../js/elastic.js',
|
||||
'../login/**/*.js',
|
||||
'../room/**/*.js',
|
||||
'../components/**/*.js',
|
||||
'../user/**/*.js',
|
||||
'../home/**/*.js',
|
||||
'../recents/**/*.js',
|
||||
'../settings/**/*.js',
|
||||
'../app.js',
|
||||
'../app*',
|
||||
'./unit/**/*.js'
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'karma-*',
|
||||
],
|
||||
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
|
@ -50,8 +56,11 @@ module.exports = function(config) {
|
|||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress'],
|
||||
|
||||
reporters: ['progress', 'junit'],
|
||||
junitReporter: {
|
||||
outputFile: 'test-results.xml',
|
||||
suite: ''
|
||||
},
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
@ -72,11 +81,11 @@ module.exports = function(config) {
|
|||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['Chrome'],
|
||||
browsers: ['PhantomJS'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
singleRun: true
|
||||
});
|
||||
};
|
117
syweb/webclient/test/unit/event-handler-service.spec.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
describe('EventHandlerService', function() {
|
||||
var scope;
|
||||
|
||||
var modelService = {};
|
||||
|
||||
// setup the service and mocked dependencies
|
||||
beforeEach(function() {
|
||||
// dependencies
|
||||
module('matrixService');
|
||||
module('notificationService');
|
||||
module('mPresence');
|
||||
|
||||
// cleanup mocked methods
|
||||
modelService = {};
|
||||
|
||||
// mocked dependencies
|
||||
module(function ($provide) {
|
||||
$provide.value('modelService', modelService);
|
||||
});
|
||||
|
||||
// tested service
|
||||
module('eventHandlerService');
|
||||
});
|
||||
|
||||
beforeEach(inject(function($rootScope) {
|
||||
scope = $rootScope;
|
||||
}));
|
||||
|
||||
it('should be able to get the number of joined users in a room', inject(
|
||||
function(eventHandlerService) {
|
||||
var roomId = "!foo:matrix.org";
|
||||
// set mocked data
|
||||
modelService.getRoom = function(roomId) {
|
||||
return {
|
||||
room_id: roomId,
|
||||
current_room_state: {
|
||||
members: {
|
||||
"@adam:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "join" },
|
||||
user_id: "@adam:matrix.org"
|
||||
}
|
||||
},
|
||||
"@beth:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "invite" },
|
||||
user_id: "@beth:matrix.org"
|
||||
}
|
||||
},
|
||||
"@charlie:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "join" },
|
||||
user_id: "@charlie:matrix.org"
|
||||
}
|
||||
},
|
||||
"@danice:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "leave" },
|
||||
user_id: "@danice:matrix.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var num = eventHandlerService.getUsersCountInRoom(roomId);
|
||||
expect(num).toEqual(2);
|
||||
}));
|
||||
|
||||
it('should be able to get a users power level', inject(
|
||||
function(eventHandlerService) {
|
||||
var roomId = "!foo:matrix.org";
|
||||
// set mocked data
|
||||
modelService.getRoom = function(roomId) {
|
||||
return {
|
||||
room_id: roomId,
|
||||
current_room_state: {
|
||||
members: {
|
||||
"@adam:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "join" },
|
||||
user_id: "@adam:matrix.org"
|
||||
}
|
||||
},
|
||||
"@beth:matrix.org": {
|
||||
event: {
|
||||
content: { membership: "join" },
|
||||
user_id: "@beth:matrix.org"
|
||||
}
|
||||
}
|
||||
},
|
||||
s: {
|
||||
"m.room.power_levels": {
|
||||
content: {
|
||||
"@adam:matrix.org": 90,
|
||||
"default": 50
|
||||
}
|
||||
}
|
||||
},
|
||||
state: function(type, key) {
|
||||
return key ? this.s[type+key] : this.s[type]
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var num = eventHandlerService.getUserPowerLevel(roomId, "@beth:matrix.org");
|
||||
expect(num).toEqual(50);
|
||||
|
||||
num = eventHandlerService.getUserPowerLevel(roomId, "@adam:matrix.org");
|
||||
expect(num).toEqual(90);
|
||||
|
||||
num = eventHandlerService.getUserPowerLevel(roomId, "@unknown:matrix.org");
|
||||
expect(num).toEqual(50);
|
||||
}));
|
||||
});
|
488
syweb/webclient/test/unit/filters.spec.js
Normal file
|
@ -0,0 +1,488 @@
|
|||
describe('mRoomName filter', function() {
|
||||
var filter, mRoomName;
|
||||
|
||||
var roomId = "!weufhewifu:matrix.org";
|
||||
|
||||
// test state values (f.e. test)
|
||||
var testUserId, testAlias, testDisplayName, testOtherDisplayName, testRoomState;
|
||||
|
||||
// mocked services which return the test values above.
|
||||
var matrixService = {
|
||||
getRoomIdToAliasMapping: function(room_id) {
|
||||
return testAlias;
|
||||
},
|
||||
|
||||
config: function() {
|
||||
return {
|
||||
user_id: testUserId
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var eventHandlerService = {
|
||||
getUserDisplayName: function(room_id, user_id) {
|
||||
if (user_id === testUserId) {
|
||||
return testDisplayName;
|
||||
}
|
||||
return testOtherDisplayName;
|
||||
}
|
||||
};
|
||||
|
||||
var modelService = {
|
||||
getRoom: function(room_id) {
|
||||
return {
|
||||
current_room_state: testRoomState
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
// inject mocked dependencies
|
||||
module(function ($provide) {
|
||||
$provide.value('matrixService', matrixService);
|
||||
$provide.value('eventHandlerService', eventHandlerService);
|
||||
$provide.value('modelService', modelService);
|
||||
});
|
||||
|
||||
module('matrixFilter');
|
||||
});
|
||||
|
||||
beforeEach(inject(function($filter) {
|
||||
filter = $filter;
|
||||
mRoomName = filter("mRoomName");
|
||||
|
||||
// purge the previous test values
|
||||
testUserId = undefined;
|
||||
testAlias = undefined;
|
||||
testDisplayName = undefined;
|
||||
testOtherDisplayName = undefined;
|
||||
|
||||
// mock up a stub room state
|
||||
testRoomState = {
|
||||
s:{}, // internal; stores the state events
|
||||
state: function(type, key) {
|
||||
// accessor used by filter
|
||||
return key ? this.s[type+key] : this.s[type];
|
||||
},
|
||||
members: {}, // struct used by filter
|
||||
|
||||
// test helper methods
|
||||
setJoinRule: function(rule) {
|
||||
this.s["m.room.join_rules"] = {
|
||||
content: {
|
||||
join_rule: rule
|
||||
}
|
||||
};
|
||||
},
|
||||
setRoomName: function(name) {
|
||||
this.s["m.room.name"] = {
|
||||
content: {
|
||||
name: name
|
||||
}
|
||||
};
|
||||
},
|
||||
setMember: function(user_id, membership, inviter_user_id) {
|
||||
if (!inviter_user_id) {
|
||||
inviter_user_id = user_id;
|
||||
}
|
||||
this.s["m.room.member" + user_id] = {
|
||||
event: {
|
||||
content: {
|
||||
membership: membership
|
||||
},
|
||||
state_key: user_id,
|
||||
user_id: inviter_user_id
|
||||
}
|
||||
};
|
||||
this.members[user_id] = this.s["m.room.member" + user_id];
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
/**** ROOM NAME ****/
|
||||
|
||||
it("should show the room name if one exists for private (invite join_rules) rooms.", function() {
|
||||
var roomName = "The Room Name";
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("invite");
|
||||
testRoomState.setRoomName(roomName);
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(roomName);
|
||||
});
|
||||
|
||||
it("should show the room name if one exists for public (public join_rules) rooms.", function() {
|
||||
var roomName = "The Room Name";
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("public");
|
||||
testRoomState.setRoomName(roomName);
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(roomName);
|
||||
});
|
||||
|
||||
/**** ROOM ALIAS ****/
|
||||
|
||||
it("should show the room alias if one exists for private (invite join_rules) rooms if a room name doesn't exist.", function() {
|
||||
testAlias = "#thealias:matrix.org";
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("invite");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testAlias);
|
||||
});
|
||||
|
||||
it("should show the room alias if one exists for public (public join_rules) rooms if a room name doesn't exist.", function() {
|
||||
testAlias = "#thealias:matrix.org";
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("public");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testAlias);
|
||||
});
|
||||
|
||||
/**** ROOM ID ****/
|
||||
|
||||
it("should show the room ID for public (public join_rules) rooms if a room name and alias don't exist.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("public");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(roomId);
|
||||
});
|
||||
|
||||
it("should show the room ID for private (invite join_rules) rooms if a room name and alias don't exist and there are >2 members.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("public");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
testRoomState.setMember("@alice:matrix.org", "join");
|
||||
testRoomState.setMember("@bob:matrix.org", "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(roomId);
|
||||
});
|
||||
|
||||
/**** SELF-CHAT ****/
|
||||
|
||||
it("should show your display name for private (invite join_rules) rooms if a room name and alias don't exist and it is a self-chat.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testDisplayName = "Me";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testDisplayName);
|
||||
});
|
||||
|
||||
it("should show your user ID for private (invite join_rules) rooms if a room name and alias don't exist and it is a self-chat and they don't have a display name set.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testUserId);
|
||||
});
|
||||
|
||||
/**** ONE-TO-ONE CHAT ****/
|
||||
|
||||
it("should show the other user's display name for private (invite join_rules) rooms if a room name and alias don't exist and it is a 1:1-chat.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
otherUserId = "@alice:matrix.org";
|
||||
testOtherDisplayName = "Alice";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
testRoomState.setMember("@alice:matrix.org", "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testOtherDisplayName);
|
||||
});
|
||||
|
||||
it("should show the other user's ID for private (invite join_rules) rooms if a room name and alias don't exist and it is a 1:1-chat and they don't have a display name set.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
otherUserId = "@alice:matrix.org";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
testRoomState.setMember("@alice:matrix.org", "join");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(otherUserId);
|
||||
});
|
||||
|
||||
/**** INVITED TO ROOM ****/
|
||||
|
||||
it("should show the other user's display name for private (invite join_rules) rooms if you are invited to it.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testDisplayName = "Me";
|
||||
otherUserId = "@alice:matrix.org";
|
||||
testOtherDisplayName = "Alice";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
testRoomState.setMember(otherUserId, "join");
|
||||
testRoomState.setMember(testUserId, "invite");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(testOtherDisplayName);
|
||||
});
|
||||
|
||||
it("should show the other user's ID for private (invite join_rules) rooms if you are invited to it and the inviter doesn't have a display name.", function() {
|
||||
testUserId = "@me:matrix.org";
|
||||
testDisplayName = "Me";
|
||||
otherUserId = "@alice:matrix.org";
|
||||
testRoomState.setJoinRule("private");
|
||||
testRoomState.setMember(testUserId, "join");
|
||||
testRoomState.setMember(otherUserId, "join");
|
||||
testRoomState.setMember(testUserId, "invite");
|
||||
var output = mRoomName(roomId);
|
||||
expect(output).toEqual(otherUserId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('duration filter', function() {
|
||||
var filter, durationFilter;
|
||||
|
||||
beforeEach(module('matrixWebClient'));
|
||||
beforeEach(inject(function($filter) {
|
||||
filter = $filter;
|
||||
durationFilter = filter("duration");
|
||||
}));
|
||||
|
||||
it("should represent 15000 ms as '15s'", function() {
|
||||
var output = durationFilter(15000);
|
||||
expect(output).toEqual("15s");
|
||||
});
|
||||
|
||||
it("should represent 60000 ms as '1m'", function() {
|
||||
var output = durationFilter(60000);
|
||||
expect(output).toEqual("1m");
|
||||
});
|
||||
|
||||
it("should represent 65000 ms as '1m'", function() {
|
||||
var output = durationFilter(65000);
|
||||
expect(output).toEqual("1m");
|
||||
});
|
||||
|
||||
it("should represent 10 ms as '0s'", function() {
|
||||
var output = durationFilter(10);
|
||||
expect(output).toEqual("0s");
|
||||
});
|
||||
|
||||
it("should represent 4m as '4m'", function() {
|
||||
var output = durationFilter(1000*60*4);
|
||||
expect(output).toEqual("4m");
|
||||
});
|
||||
|
||||
it("should represent 4m30s as '4m'", function() {
|
||||
var output = durationFilter(1000*60*4 + 1000*30);
|
||||
expect(output).toEqual("4m");
|
||||
});
|
||||
|
||||
it("should represent 2h as '2h'", function() {
|
||||
var output = durationFilter(1000*60*60*2);
|
||||
expect(output).toEqual("2h");
|
||||
});
|
||||
|
||||
it("should represent 2h35m as '2h'", function() {
|
||||
var output = durationFilter(1000*60*60*2 + 1000*60*35);
|
||||
expect(output).toEqual("2h");
|
||||
});
|
||||
});
|
||||
|
||||
describe('orderMembersList filter', function() {
|
||||
var filter, orderMembersList;
|
||||
|
||||
beforeEach(module('matrixWebClient'));
|
||||
beforeEach(inject(function($filter) {
|
||||
filter = $filter;
|
||||
orderMembersList = filter("orderMembersList");
|
||||
}));
|
||||
|
||||
it("should sort a single entry", function() {
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: 50,
|
||||
last_updated: 1415266943964
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: 50,
|
||||
last_updated: 1415266943964
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should sort by taking last_active_ago into account", function() {
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
"@b:example.com": {
|
||||
last_active_ago: 50,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
"@c:example.com": {
|
||||
last_active_ago: 99999,
|
||||
last_updated: 1415266943964
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([
|
||||
{
|
||||
id: "@b:example.com",
|
||||
last_active_ago: 50,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@c:example.com",
|
||||
last_active_ago: 99999,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should sort by taking last_updated into account", function() {
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
"@b:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266900000
|
||||
},
|
||||
"@c:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943000
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([
|
||||
{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@c:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943000
|
||||
},
|
||||
{
|
||||
id: "@b:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266900000
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should sort by taking last_updated and last_active_ago into account",
|
||||
function() {
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943000
|
||||
},
|
||||
"@b:example.com": {
|
||||
last_active_ago: 100000,
|
||||
last_updated: 1415266943900
|
||||
},
|
||||
"@c:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([
|
||||
{
|
||||
id: "@c:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943000
|
||||
},
|
||||
{
|
||||
id: "@b:example.com",
|
||||
last_active_ago: 100000,
|
||||
last_updated: 1415266943900
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// SYWEB-26 comment
|
||||
it("should sort members who do not have last_active_ago value at the end of the list",
|
||||
function() {
|
||||
// single undefined entry
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
"@b:example.com": {
|
||||
last_active_ago: 100000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
"@c:example.com": {
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([
|
||||
{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: 1000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@b:example.com",
|
||||
last_active_ago: 100000,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
{
|
||||
id: "@c:example.com",
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should sort multiple members who do not have last_active_ago according to presence",
|
||||
function() {
|
||||
// single undefined entry
|
||||
var output = orderMembersList({
|
||||
"@a:example.com": {
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "unavailable"
|
||||
},
|
||||
"@b:example.com": {
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "online"
|
||||
},
|
||||
"@c:example.com": {
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "offline"
|
||||
}
|
||||
});
|
||||
expect(output).toEqual([
|
||||
{
|
||||
id: "@b:example.com",
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "online"
|
||||
},
|
||||
{
|
||||
id: "@a:example.com",
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "unavailable"
|
||||
},
|
||||
{
|
||||
id: "@c:example.com",
|
||||
last_active_ago: undefined,
|
||||
last_updated: 1415266943964,
|
||||
presence: "offline"
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
504
syweb/webclient/test/unit/matrix-service.spec.js
Normal file
|
@ -0,0 +1,504 @@
|
|||
describe('MatrixService', function() {
|
||||
var scope, httpBackend;
|
||||
var BASE = "http://example.com";
|
||||
var PREFIX = "/_matrix/client/api/v1";
|
||||
var URL = BASE + PREFIX;
|
||||
var roomId = "!wejigf387t34:matrix.org";
|
||||
|
||||
var CONFIG = {
|
||||
access_token: "foobar",
|
||||
homeserver: BASE
|
||||
};
|
||||
|
||||
beforeEach(module('matrixService'));
|
||||
|
||||
beforeEach(inject(function($rootScope, $httpBackend) {
|
||||
httpBackend = $httpBackend;
|
||||
scope = $rootScope;
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
httpBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
it('should be able to POST /createRoom with an alias', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var alias = "flibble";
|
||||
matrixService.create(alias).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(URL + "/createRoom?access_token=foobar",
|
||||
{
|
||||
room_alias_name: alias
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /initialSync', inject(function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var limit = 15;
|
||||
matrixService.initialSync(limit).then(function(response) {
|
||||
expect(response.data).toEqual([]);
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/initialSync?access_token=foobar&limit=15")
|
||||
.respond([]);
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /rooms/$roomid/state', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
matrixService.roomState(roomId).then(function(response) {
|
||||
expect(response.data).toEqual([]);
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/state?access_token=foobar")
|
||||
.respond([]);
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST /join', inject(function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
matrixService.joinAlias(roomId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(
|
||||
URL + "/join/" + encodeURIComponent(roomId) +
|
||||
"?access_token=foobar",
|
||||
{})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST /rooms/$roomid/join', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
matrixService.join(roomId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/join?access_token=foobar",
|
||||
{})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST /rooms/$roomid/invite', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var inviteUserId = "@user:example.com";
|
||||
matrixService.invite(roomId, inviteUserId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/invite?access_token=foobar",
|
||||
{
|
||||
user_id: inviteUserId
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST /rooms/$roomid/leave', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
matrixService.leave(roomId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/leave?access_token=foobar",
|
||||
{})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST /rooms/$roomid/ban', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var userId = "@example:example.com";
|
||||
var reason = "Because.";
|
||||
matrixService.ban(roomId, userId, reason).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/ban?access_token=foobar",
|
||||
{
|
||||
user_id: userId,
|
||||
reason: reason
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /directory/room/$alias', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var alias = "#test:example.com";
|
||||
var roomId = "!wefuhewfuiw:example.com";
|
||||
matrixService.resolveRoomAlias(alias).then(function(response) {
|
||||
expect(response.data).toEqual({
|
||||
room_id: roomId
|
||||
});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/directory/room/" + encodeURIComponent(alias) +
|
||||
"?access_token=foobar")
|
||||
.respond({
|
||||
room_id: roomId
|
||||
});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to send m.room.name', inject(function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var name = "Room Name";
|
||||
matrixService.setName(roomId, name).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/state/m.room.name?access_token=foobar",
|
||||
{
|
||||
name: name
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to send m.room.topic', inject(function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var topic = "A room topic can go here.";
|
||||
matrixService.setTopic(roomId, topic).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/state/m.room.topic?access_token=foobar",
|
||||
{
|
||||
topic: topic
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to send generic state events without a state key', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var eventType = "com.example.events.test";
|
||||
var content = {
|
||||
testing: "1 2 3"
|
||||
};
|
||||
matrixService.sendStateEvent(roomId, eventType, content).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) + "/state/" +
|
||||
encodeURIComponent(eventType) + "?access_token=foobar",
|
||||
content)
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
// TODO: Skipped since the webclient is purposefully broken so as not to
|
||||
// 500 matrix.org
|
||||
xit('should be able to send generic state events with a state key', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var eventType = "com.example.events.test:special@characters";
|
||||
var content = {
|
||||
testing: "1 2 3"
|
||||
};
|
||||
var stateKey = "version:1";
|
||||
matrixService.sendStateEvent(roomId, eventType, content, stateKey).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) + "/state/" +
|
||||
encodeURIComponent(eventType) + "/" + encodeURIComponent(stateKey)+
|
||||
"?access_token=foobar",
|
||||
content)
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT generic events ', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var eventType = "com.example.events.test";
|
||||
var txnId = "42";
|
||||
var content = {
|
||||
testing: "1 2 3"
|
||||
};
|
||||
matrixService.sendEvent(roomId, eventType, txnId, content).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) + "/send/" +
|
||||
encodeURIComponent(eventType) + "/" + encodeURIComponent(txnId)+
|
||||
"?access_token=foobar",
|
||||
content)
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT text messages ', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var body = "ABC 123";
|
||||
matrixService.sendTextMessage(roomId, body).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
new RegExp(URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/send/m.room.message/(.*)" +
|
||||
"?access_token=foobar"),
|
||||
{
|
||||
body: body,
|
||||
msgtype: "m.text"
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT emote messages ', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var body = "ABC 123";
|
||||
matrixService.sendEmoteMessage(roomId, body).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPUT(
|
||||
new RegExp(URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/send/m.room.message/(.*)" +
|
||||
"?access_token=foobar"),
|
||||
{
|
||||
body: body,
|
||||
msgtype: "m.emote"
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to POST redactions', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!fh38hfwfwef:example.com";
|
||||
var eventId = "fwefwexample.com";
|
||||
matrixService.redactEvent(roomId, eventId).then(
|
||||
function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectPOST(URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/redact/" + encodeURIComponent(eventId) +
|
||||
"?access_token=foobar")
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /directory/room/$alias', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var alias = "#test:example.com";
|
||||
var roomId = "!wefuhewfuiw:example.com";
|
||||
matrixService.resolveRoomAlias(alias).then(function(response) {
|
||||
expect(response.data).toEqual({
|
||||
room_id: roomId
|
||||
});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/directory/room/" + encodeURIComponent(alias) +
|
||||
"?access_token=foobar")
|
||||
.respond({
|
||||
room_id: roomId
|
||||
});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /rooms/$roomid/members', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!wefuhewfuiw:example.com";
|
||||
matrixService.getMemberList(roomId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/members?access_token=foobar")
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to paginate a room', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var roomId = "!wefuhewfuiw:example.com";
|
||||
var from = "3t_44e_54z";
|
||||
var limit = 20;
|
||||
matrixService.paginateBackMessages(roomId, from, limit).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
URL + "/rooms/" + encodeURIComponent(roomId) +
|
||||
"/messages?access_token=foobar&dir=b&from="+
|
||||
encodeURIComponent(from)+"&limit="+limit)
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /publicRooms', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
matrixService.publicRooms().then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(
|
||||
new RegExp(URL + "/publicRooms(.*)"))
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /profile/$userid/displayname', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var userId = "@foo:example.com";
|
||||
matrixService.getDisplayName(userId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(URL + "/profile/" + encodeURIComponent(userId) +
|
||||
"/displayname?access_token=foobar")
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to GET /profile/$userid/avatar_url', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var userId = "@foo:example.com";
|
||||
matrixService.getProfilePictureUrl(userId).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
httpBackend.expectGET(URL + "/profile/" + encodeURIComponent(userId) +
|
||||
"/avatar_url?access_token=foobar")
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT /profile/$me/avatar_url', inject(
|
||||
function(matrixService) {
|
||||
var testConfig = angular.copy(CONFIG);
|
||||
testConfig.user_id = "@bob:example.com";
|
||||
matrixService.setConfig(testConfig);
|
||||
var url = "http://example.com/mypic.jpg";
|
||||
matrixService.setProfilePictureUrl(url).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
httpBackend.expectPUT(URL + "/profile/" +
|
||||
encodeURIComponent(testConfig.user_id) +
|
||||
"/avatar_url?access_token=foobar",
|
||||
{
|
||||
avatar_url: url
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT /profile/$me/displayname', inject(
|
||||
function(matrixService) {
|
||||
var testConfig = angular.copy(CONFIG);
|
||||
testConfig.user_id = "@bob:example.com";
|
||||
matrixService.setConfig(testConfig);
|
||||
var displayname = "Bob Smith";
|
||||
matrixService.setDisplayName(displayname).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
httpBackend.expectPUT(URL + "/profile/" +
|
||||
encodeURIComponent(testConfig.user_id) +
|
||||
"/displayname?access_token=foobar",
|
||||
{
|
||||
displayname: displayname
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to login with password', inject(
|
||||
function(matrixService) {
|
||||
matrixService.setConfig(CONFIG);
|
||||
var userId = "@bob:example.com";
|
||||
var password = "monkey";
|
||||
matrixService.login(userId, password).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
httpBackend.expectPOST(new RegExp(URL+"/login(.*)"),
|
||||
{
|
||||
user: userId,
|
||||
password: password,
|
||||
type: "m.login.password"
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
|
||||
it('should be able to PUT presence status', inject(
|
||||
function(matrixService) {
|
||||
var testConfig = angular.copy(CONFIG);
|
||||
testConfig.user_id = "@bob:example.com";
|
||||
matrixService.setConfig(testConfig);
|
||||
var status = "unavailable";
|
||||
matrixService.setUserPresence(status).then(function(response) {
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
httpBackend.expectPUT(URL+"/presence/"+
|
||||
encodeURIComponent(testConfig.user_id)+
|
||||
"/status?access_token=foobar",
|
||||
{
|
||||
presence: status
|
||||
})
|
||||
.respond({});
|
||||
httpBackend.flush();
|
||||
}));
|
||||
});
|
30
syweb/webclient/test/unit/model-service.spec.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
describe('ModelService', function() {
|
||||
|
||||
// setup the dependencies
|
||||
beforeEach(function() {
|
||||
// dependencies
|
||||
module('matrixService');
|
||||
|
||||
// tested service
|
||||
module('modelService');
|
||||
});
|
||||
|
||||
it('should be able to get a member in a room', inject(
|
||||
function(modelService) {
|
||||
var roomId = "!wefiohwefuiow:matrix.org";
|
||||
var userId = "@bob:matrix.org";
|
||||
|
||||
modelService.getRoom(roomId).current_room_state.storeStateEvent({
|
||||
type: "m.room.member",
|
||||
id: "fwefw:matrix.org",
|
||||
user_id: userId,
|
||||
state_key: userId,
|
||||
content: {
|
||||
membership: "join"
|
||||
}
|
||||
});
|
||||
|
||||
var user = modelService.getMember(roomId, userId);
|
||||
expect(user.event.state_key).toEqual(userId);
|
||||
}));
|
||||
});
|
84
syweb/webclient/test/unit/register-controller.spec.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
describe("RegisterController ", function() {
|
||||
var rootScope, scope, ctrl, $q, $timeout;
|
||||
var userId = "@foo:bar";
|
||||
var displayName = "Foo";
|
||||
var avatarUrl = "avatar.url";
|
||||
|
||||
window.webClientConfig = {
|
||||
useCapatcha: false
|
||||
};
|
||||
|
||||
// test vars
|
||||
var testRegisterData, testFailRegisterData;
|
||||
|
||||
|
||||
// mock services
|
||||
var matrixService = {
|
||||
config: function() {
|
||||
return {
|
||||
user_id: userId
|
||||
}
|
||||
},
|
||||
setConfig: function(){},
|
||||
register: function(mxid, password, threepidCreds, useCaptcha) {
|
||||
var d = $q.defer();
|
||||
if (testFailRegisterData) {
|
||||
d.reject({
|
||||
data: testFailRegisterData
|
||||
});
|
||||
}
|
||||
else {
|
||||
d.resolve({
|
||||
data: testRegisterData
|
||||
});
|
||||
}
|
||||
return d.promise;
|
||||
}
|
||||
};
|
||||
|
||||
var eventStreamService = {};
|
||||
|
||||
beforeEach(function() {
|
||||
module('matrixWebClient');
|
||||
|
||||
// reset test vars
|
||||
testRegisterData = undefined;
|
||||
testFailRegisterData = undefined;
|
||||
});
|
||||
|
||||
beforeEach(inject(function($rootScope, $injector, $location, $controller, _$q_, _$timeout_) {
|
||||
$q = _$q_;
|
||||
$timeout = _$timeout_;
|
||||
scope = $rootScope.$new();
|
||||
rootScope = $rootScope;
|
||||
routeParams = {
|
||||
user_matrix_id: userId
|
||||
};
|
||||
ctrl = $controller('RegisterController', {
|
||||
'$scope': scope,
|
||||
'$rootScope': $rootScope,
|
||||
'$location': $location,
|
||||
'matrixService': matrixService,
|
||||
'eventStreamService': eventStreamService
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// SYWEB-109
|
||||
it('should display an error if the HS rejects the username on registration', function() {
|
||||
var prevFeedback = angular.copy(scope.feedback);
|
||||
|
||||
testFailRegisterData = {
|
||||
errcode: "M_UNKNOWN",
|
||||
error: "I am rejecting you."
|
||||
};
|
||||
|
||||
scope.account.pwd1 = "password";
|
||||
scope.account.pwd2 = "password";
|
||||
scope.account.desired_user_id = "bob";
|
||||
scope.register(); // this depends on the result of a deferred
|
||||
rootScope.$digest(); // which is delivered after the digest
|
||||
|
||||
expect(scope.feedback).not.toEqual(prevFeedback);
|
||||
});
|
||||
});
|
|
@ -1,146 +0,0 @@
|
|||
/*
|
||||
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';
|
||||
|
||||
angular.module('matrixFilter', [])
|
||||
|
||||
// Compute the room name according to information we have
|
||||
.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', function($rootScope, matrixService, eventHandlerService) {
|
||||
return function(room_id) {
|
||||
var roomName;
|
||||
|
||||
// If there is an alias, use it
|
||||
// TODO: only one alias is managed for now
|
||||
var alias = matrixService.getRoomIdToAliasMapping(room_id);
|
||||
|
||||
var room = $rootScope.events.rooms[room_id];
|
||||
if (room) {
|
||||
// Get name from room state date
|
||||
var room_name_event = room["m.room.name"];
|
||||
|
||||
// Determine if it is a public room
|
||||
var isPublicRoom = false;
|
||||
if (room["m.room.join_rules"] && room["m.room.join_rules"].content) {
|
||||
isPublicRoom = ("public" === room["m.room.join_rules"].content.join_rule);
|
||||
}
|
||||
|
||||
if (room_name_event) {
|
||||
roomName = room_name_event.content.name;
|
||||
}
|
||||
else if (alias) {
|
||||
roomName = alias;
|
||||
}
|
||||
else if (room.members && !isPublicRoom) { // Do not rename public room
|
||||
|
||||
var user_id = matrixService.config().user_id;
|
||||
// Else, build the name from its users
|
||||
// Limit the room renaming to 1:1 room
|
||||
if (2 === Object.keys(room.members).length) {
|
||||
for (var i in room.members) {
|
||||
if (!room.members.hasOwnProperty(i)) continue;
|
||||
|
||||
var member = room.members[i];
|
||||
if (member.state_key !== user_id) {
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, member.state_key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Object.keys(room.members).length <= 1) {
|
||||
|
||||
var otherUserId;
|
||||
|
||||
if (Object.keys(room.members)[0]) {
|
||||
otherUserId = Object.keys(room.members)[0];
|
||||
// this could be an invite event (from event stream)
|
||||
if (otherUserId === user_id &&
|
||||
room.members[user_id].content.membership === "invite") {
|
||||
// this is us being invited to this room, so the
|
||||
// *user_id* is the other user ID and not the state
|
||||
// key.
|
||||
otherUserId = room.members[user_id].user_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// it's got to be an invite, or failing that a self-chat;
|
||||
otherUserId = room.inviter || user_id;
|
||||
/*
|
||||
// XXX: This should all be unnecessary now thanks to using the /rooms/<room>/roomid API
|
||||
|
||||
// The other member may be in the invite list, get all invited users
|
||||
var invitedUserIDs = [];
|
||||
|
||||
// XXX: *SURELY* we shouldn't have to trawl through the whole messages list to
|
||||
// find invite - surely the other user should be in room.members with state invited? :/ --Matthew
|
||||
for (var i in room.messages) {
|
||||
var message = room.messages[i];
|
||||
if ("m.room.member" === message.type && "invite" === message.content.membership) {
|
||||
// Filter out the current user
|
||||
var member_id = message.state_key;
|
||||
if (member_id === user_id) {
|
||||
member_id = message.user_id;
|
||||
}
|
||||
if (member_id !== user_id) {
|
||||
// Make sure there is no duplicate user
|
||||
if (-1 === invitedUserIDs.indexOf(member_id)) {
|
||||
invitedUserIDs.push(member_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For now, only 1:1 room needs to be renamed. It means only 1 invited user
|
||||
if (1 === invitedUserIDs.length) {
|
||||
otherUserId = invitedUserIDs[0];
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Get the user display name
|
||||
roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always show the alias in the room displayed name
|
||||
if (roomName && alias && alias !== roomName) {
|
||||
roomName += " (" + alias + ")";
|
||||
}
|
||||
|
||||
if (undefined === roomName) {
|
||||
// By default, use the room ID
|
||||
roomName = room_id;
|
||||
|
||||
// XXX: this is *INCREDIBLY* heavy logging for a function that calls every single
|
||||
// time any kind of digest runs which refreshes a room name...
|
||||
// commenting it out for now.
|
||||
|
||||
// Log some information that lead to this leak
|
||||
// console.log("Room ID leak for " + room_id);
|
||||
// console.log("room object: " + JSON.stringify(room, undefined, 4));
|
||||
}
|
||||
|
||||
return roomName;
|
||||
};
|
||||
}])
|
||||
|
||||
// Return the user display name
|
||||
.filter('mUserDisplayName', ['eventHandlerService', function(eventHandlerService) {
|
||||
return function(user_id, room_id) {
|
||||
return eventHandlerService.getUserDisplayName(room_id, user_id);
|
||||
};
|
||||
}]);
|