Merge branch 'develop' of github.com:matrix-org/synapse into federation_authorization
2
.gitignore
vendored
|
@ -24,7 +24,7 @@ graph/*.svg
|
||||||
graph/*.png
|
graph/*.png
|
||||||
graph/*.dot
|
graph/*.dot
|
||||||
|
|
||||||
webclient/config.js
|
**/webclient/config.js
|
||||||
webclient/test/environment-protractor.js
|
webclient/test/environment-protractor.js
|
||||||
|
|
||||||
uploads
|
uploads
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
recursive-include tests *.py
|
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
|
[2] End-to-end encryption is currently in development
|
||||||
|
|
||||||
|
|
||||||
Homeserver Installation
|
Homeserver Installation
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
First, the dependencies need to be installed. Start by installing
|
Synapse is written in python but some of the libraries is uses are written in
|
||||||
'python2.7-dev' and the various tools of the compiler toolchain.
|
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::
|
Installing prerequisites on Ubuntu::
|
||||||
|
|
||||||
|
@ -137,30 +137,35 @@ Installing prerequisites on Mac OS X::
|
||||||
|
|
||||||
$ xcode-select --install
|
$ 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
|
The homeserver has a number of external dependencies, that are easiest
|
||||||
to install by making setup.py do so, in --user mode::
|
to install by making setup.py do so, in --user mode::
|
||||||
|
|
||||||
$ python setup.py develop --user
|
$ 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
|
This will run a process of downloading and installing into your
|
||||||
user's .local/lib directory all of the required dependencies that are
|
user's .local/lib directory all of the required dependencies that are
|
||||||
missing.
|
missing.
|
||||||
|
@ -204,11 +209,11 @@ IDs:
|
||||||
For the first form, simply pass the required hostname (of the machine) as the
|
For the first form, simply pass the required hostname (of the machine) as the
|
||||||
--host parameter::
|
--host parameter::
|
||||||
|
|
||||||
$ python synapse/app/homeserver.py \
|
$ python -m synapse.app.homeserver \
|
||||||
--server-name machine.my.domain.name \
|
--server-name machine.my.domain.name \
|
||||||
--config-path homeserver.config \
|
--config-path homeserver.config \
|
||||||
--generate-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
|
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
|
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
|
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::
|
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 \
|
--server-name YOURDOMAIN \
|
||||||
--bind-port 8448 \
|
--bind-port 8448 \
|
||||||
--config-path homeserver.config \
|
--config-path homeserver.config \
|
||||||
--generate-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
|
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
|
done
|
||||||
|
|
||||||
echo "Starting webclient on port 8000..."
|
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"
|
cd "$CWD"
|
||||||
|
|
3
setup.py
|
@ -28,7 +28,7 @@ def read(fname):
|
||||||
setup(
|
setup(
|
||||||
name="SynapseHomeServer",
|
name="SynapseHomeServer",
|
||||||
version="0.0.1",
|
version="0.0.1",
|
||||||
packages=find_packages(exclude=["tests"]),
|
packages=find_packages(exclude=["tests", "tests.*"]),
|
||||||
description="Reference Synapse Home Server",
|
description="Reference Synapse Home Server",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"syutil==0.0.2",
|
"syutil==0.0.2",
|
||||||
|
@ -43,6 +43,7 @@ setup(
|
||||||
],
|
],
|
||||||
dependency_links=[
|
dependency_links=[
|
||||||
"https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2",
|
"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=[
|
setup_requires=[
|
||||||
"setuptools_trial",
|
"setuptools_trial",
|
||||||
|
|
|
@ -42,6 +42,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import syweb
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -58,7 +59,9 @@ class SynapseHomeServer(HomeServer):
|
||||||
return JsonResource()
|
return JsonResource()
|
||||||
|
|
||||||
def build_resource_for_web_client(self):
|
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):
|
def build_resource_for_content_repo(self):
|
||||||
return ContentRepoResource(
|
return ContentRepoResource(
|
||||||
|
|
|
@ -148,7 +148,7 @@ class RoomStateEventRestServlet(RestServlet):
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
event = self.event_factory.create_event(
|
event = self.event_factory.create_event(
|
||||||
etype=event_type,
|
etype=urllib.unquote(event_type),
|
||||||
content=content,
|
content=content,
|
||||||
room_id=urllib.unquote(room_id),
|
room_id=urllib.unquote(room_id),
|
||||||
user_id=user.to_string(),
|
user_id=user.to_string(),
|
||||||
|
@ -182,7 +182,7 @@ class RoomSendEventRestServlet(RestServlet):
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
event = self.event_factory.create_event(
|
event = self.event_factory.create_event(
|
||||||
etype=event_type,
|
etype=urllib.unquote(event_type),
|
||||||
room_id=urllib.unquote(room_id),
|
room_id=urllib.unquote(room_id),
|
||||||
user_id=user.to_string(),
|
user_id=user.to_string(),
|
||||||
content=content
|
content=content
|
||||||
|
@ -458,7 +458,7 @@ class RoomRedactEventRestServlet(RestServlet):
|
||||||
room_id=urllib.unquote(room_id),
|
room_id=urllib.unquote(room_id),
|
||||||
user_id=user.to_string(),
|
user_id=user.to_string(),
|
||||||
content=content,
|
content=content,
|
||||||
redacts=event_id,
|
redacts=urllib.unquote(event_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
|
|
0
syweb/__init__.py
Normal file
|
@ -21,8 +21,8 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
||||||
.controller('MatrixWebClientController', ['$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) {
|
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
|
// Check current URL to avoid to display the logout button on the login page
|
||||||
$scope.location = $location.path();
|
$scope.location = $location.path();
|
||||||
|
@ -117,7 +117,7 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
return;
|
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];
|
delete roomMembers[matrixService.config().user_id];
|
||||||
|
|
||||||
$rootScope.currentCall.user_id = Object.keys(roomMembers)[0];
|
$rootScope.currentCall.user_id = Object.keys(roomMembers)[0];
|
|
@ -29,10 +29,10 @@ angular.module('matrixWebClient')
|
||||||
return s + "s";
|
return s + "s";
|
||||||
}
|
}
|
||||||
if (t < 60 * 60) {
|
if (t < 60 * 60) {
|
||||||
return m + "m "; // + s + "s";
|
return m + "m"; // + s + "s";
|
||||||
}
|
}
|
||||||
if (t < 24 * 60 * 60) {
|
if (t < 24 * 60 * 60) {
|
||||||
return h + "h "; // + m + "m";
|
return h + "h"; // + m + "m";
|
||||||
}
|
}
|
||||||
return d + "d "; // + h + "h";
|
return d + "d "; // + h + "h";
|
||||||
};
|
};
|
||||||
|
@ -76,17 +76,6 @@ angular.module('matrixWebClient')
|
||||||
return filtered;
|
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) {
|
.filter('unsafe', ['$sce', function($sce) {
|
||||||
return function(text) {
|
return function(text) {
|
||||||
return $sce.trustAsHtml(text);
|
return $sce.trustAsHtml(text);
|
|
@ -31,6 +31,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
||||||
'eventStreamService',
|
'eventStreamService',
|
||||||
'eventHandlerService',
|
'eventHandlerService',
|
||||||
'notificationService',
|
'notificationService',
|
||||||
|
'modelService',
|
||||||
'infinite-scroll',
|
'infinite-scroll',
|
||||||
'ui.bootstrap',
|
'ui.bootstrap',
|
||||||
'monospaced.elastic'
|
'monospaced.elastic'
|
|
@ -64,7 +64,8 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
||||||
var imageMessage = {
|
var imageMessage = {
|
||||||
msgtype: "m.image",
|
msgtype: "m.image",
|
||||||
url: undefined,
|
url: undefined,
|
||||||
body: {
|
body: "Image",
|
||||||
|
info: {
|
||||||
size: undefined,
|
size: undefined,
|
||||||
w: undefined,
|
w: undefined,
|
||||||
h: undefined,
|
h: undefined,
|
||||||
|
@ -90,7 +91,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
||||||
function(url) {
|
function(url) {
|
||||||
// Update message metadata
|
// Update message metadata
|
||||||
imageMessage.url = url;
|
imageMessage.url = url;
|
||||||
imageMessage.body = {
|
imageMessage.info = {
|
||||||
size: imageFile.size,
|
size: imageFile.size,
|
||||||
w: size.width,
|
w: size.width,
|
||||||
h: size.height,
|
h: size.height,
|
||||||
|
@ -101,7 +102,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
||||||
// reuse the original image info for thumbnail data
|
// reuse the original image info for thumbnail data
|
||||||
if (!imageMessage.thumbnail_url) {
|
if (!imageMessage.thumbnail_url) {
|
||||||
imageMessage.thumbnail_url = imageMessage.url;
|
imageMessage.thumbnail_url = imageMessage.url;
|
||||||
imageMessage.thumbnail_info = imageMessage.body;
|
imageMessage.thumbnail_info = imageMessage.info;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are done
|
// 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
|
process them. Events may be coming from the event stream, the REST API (via
|
||||||
direct GETs or via a pagination stream API), etc.
|
direct GETs or via a pagination stream API), etc.
|
||||||
|
|
||||||
Typically, this service will store events or broadcast them to any listeners
|
Typically, this service will store events and broadcast them to any listeners
|
||||||
(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
|
(e.g. controllers) via $broadcast.
|
||||||
if typically all the $on method would do is update its own $scope.
|
|
||||||
*/
|
*/
|
||||||
angular.module('eventHandlerService', [])
|
angular.module('eventHandlerService', [])
|
||||||
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService',
|
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', '$filter', 'mPresence', 'notificationService', 'modelService',
|
||||||
function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) {
|
function(matrixService, $rootScope, $q, $timeout, $filter, mPresence, notificationService, modelService) {
|
||||||
var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
|
var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
|
||||||
var MSG_EVENT = "MSG_EVENT";
|
var MSG_EVENT = "MSG_EVENT";
|
||||||
var MEMBER_EVENT = "MEMBER_EVENT";
|
var MEMBER_EVENT = "MEMBER_EVENT";
|
||||||
|
@ -44,6 +43,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
// of the app, given we never try to reap memory yet)
|
// of the app, given we never try to reap memory yet)
|
||||||
var eventMap = {};
|
var eventMap = {};
|
||||||
|
|
||||||
|
// TODO: Remove this and replace with modelService.User objects.
|
||||||
$rootScope.presence = {};
|
$rootScope.presence = {};
|
||||||
|
|
||||||
var initialSyncDeferred;
|
var initialSyncDeferred;
|
||||||
|
@ -51,81 +51,43 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
var reset = function() {
|
var reset = function() {
|
||||||
initialSyncDeferred = $q.defer();
|
initialSyncDeferred = $q.defer();
|
||||||
|
|
||||||
$rootScope.events = {
|
|
||||||
rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.presence = {};
|
$rootScope.presence = {};
|
||||||
|
|
||||||
eventMap = {};
|
eventMap = {};
|
||||||
};
|
};
|
||||||
reset();
|
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) {
|
var resetRoomMessages = function(room_id) {
|
||||||
if ($rootScope.events.rooms[room_id]) {
|
var room = modelService.getRoom(room_id);
|
||||||
$rootScope.events.rooms[room_id].messages = [];
|
room.events = [];
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic method to handle events data
|
// Generic method to handle events data
|
||||||
var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) {
|
var handleRoomStateEvent = function(event, isLiveEvent, addToRoomMessages) {
|
||||||
// Add topic changes as if they were a room message
|
var room = modelService.getRoom(event.room_id);
|
||||||
if (addToRoomMessages) {
|
if (addToRoomMessages) {
|
||||||
if (isLiveEvent) {
|
// some state events are displayed as messages, so add them.
|
||||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
room.addMessageEvent(event, !isLiveEvent);
|
||||||
}
|
|
||||||
else {
|
|
||||||
$rootScope.events.rooms[event.room_id].messages.unshift(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// live events always update, but non-live events only update if the
|
if (isLiveEvent) {
|
||||||
// ts is later.
|
// update the current room state with the latest state
|
||||||
var latestData = true;
|
room.current_room_state.storeStateEvent(event);
|
||||||
if (!isLiveEvent) {
|
}
|
||||||
|
else {
|
||||||
var eventTs = event.origin_server_ts;
|
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) {
|
||||||
if (storedEvent.origin_server_ts > eventTs) {
|
if (storedEvent.origin_server_ts < eventTs) {
|
||||||
// ignore it, we have a newer one already.
|
// the incoming event is newer, use it.
|
||||||
latestData = false;
|
room.current_room_state.storeStateEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (latestData) {
|
// TODO: handle old_room_state
|
||||||
$rootScope.events.rooms[event.room_id][event.type] = event;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleRoomCreate = function(event, isLiveEvent) {
|
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);
|
$rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,6 +95,62 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
|
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) {
|
var handleMessage = function(event, isLiveEvent) {
|
||||||
// Check for empty event content
|
// Check for empty event content
|
||||||
var hasContent = false;
|
var hasContent = false;
|
||||||
|
@ -145,134 +163,78 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
return;
|
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 room = modelService.getRoom(event.room_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).
|
if (event.user_id !== matrixService.config().user_id) {
|
||||||
//
|
room.addMessageEvent(event, !isLiveEvent);
|
||||||
// However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is
|
displayNotification(event);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
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
|
// 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);
|
$rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
|
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
|
// did something change?
|
||||||
// Exception: Do not do this if the event is a room state event because such events already come
|
var memberChanges = undefined;
|
||||||
// as room messages events. Moreover, when they come as room messages events, they are relatively ordered
|
|
||||||
// with other other room messages
|
|
||||||
if (!isStateEvent) {
|
if (!isStateEvent) {
|
||||||
// could be a membership change, display name change, etc.
|
// could be a membership change, display name change, etc.
|
||||||
// Find out which one.
|
// 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))) {
|
if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) {
|
||||||
memberChanges = "membership";
|
memberChanges = "membership";
|
||||||
}
|
}
|
||||||
else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) {
|
else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) {
|
||||||
memberChanges = "displayname";
|
memberChanges = "displayname";
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the key which changed
|
// mark the key which changed
|
||||||
event.changedKey = memberChanges;
|
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) {
|
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);
|
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent);
|
||||||
};
|
};
|
||||||
|
@ -283,30 +245,28 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
};
|
};
|
||||||
|
|
||||||
var handlePowerLevels = function(event, isLiveEvent) {
|
var handlePowerLevels = function(event, isLiveEvent) {
|
||||||
// Keep the latest data. Do not care of events that come when paginating back
|
handleRoomStateEvent(event, isLiveEvent);
|
||||||
if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
|
$rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
|
||||||
$rootScope.events.rooms[event.room_id][event.type] = event;
|
|
||||||
$rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleRoomName = function(event, isLiveEvent, isStateEvent) {
|
var handleRoomName = function(event, isLiveEvent, isStateEvent) {
|
||||||
console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name);
|
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);
|
$rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var handleRoomTopic = function(event, isLiveEvent, isStateEvent) {
|
var handleRoomTopic = function(event, isLiveEvent, isStateEvent) {
|
||||||
console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic);
|
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);
|
$rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleCallEvent = function(event, isLiveEvent) {
|
var handleCallEvent = function(event, isLiveEvent) {
|
||||||
$rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
|
$rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
|
||||||
if (event.type === 'm.call.invite') {
|
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
|
// we need to remove something possibly: do we know the redacted
|
||||||
// event ID?
|
// event ID?
|
||||||
if (eventMap[event.redacts]) {
|
if (eventMap[event.redacts]) {
|
||||||
|
var room = modelService.getRoom(event.room_id);
|
||||||
// remove event from list of messages in this room.
|
// 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++) {
|
for (var i=0; i<eventList.length; i++) {
|
||||||
if (eventList[i].event_id === event.redacts) {
|
if (eventList[i].event_id === event.redacts) {
|
||||||
console.log("Removing event " + event.redacts);
|
console.log("Removing event " + event.redacts);
|
||||||
|
@ -330,51 +291,10 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcast the redaction so controllers can nuke this
|
|
||||||
console.log("Redacted an event.");
|
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
|
* Return the display name of an user acccording to data already downloaded
|
||||||
* @param {String} room_id the room id
|
* @param {String} room_id the room id
|
||||||
|
@ -385,17 +305,20 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
var displayName;
|
var displayName;
|
||||||
|
|
||||||
// Get the user display name from the member list of the room
|
// 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
|
if (member && member.content.displayname) { // Do not consider null displayname
|
||||||
displayName = member.content.displayname;
|
displayName = member.content.displayname;
|
||||||
|
|
||||||
// Disambiguate users who have the same displayname in the room
|
// Disambiguate users who have the same displayname in the room
|
||||||
if (user_id !== matrixService.config().user_id) {
|
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) {
|
for (var member_id in room.current_room_state.members) {
|
||||||
if (room.members.hasOwnProperty(member_id) && member_id !== user_id) {
|
if (room.current_room_state.members.hasOwnProperty(member_id) && member_id !== user_id) {
|
||||||
var member2 = room.members[member_id];
|
var member2 = room.current_room_state.members[member_id].event;
|
||||||
if (member2.content.displayname && member2.content.displayname === displayName) {
|
if (member2.content.displayname && member2.content.displayname === displayName) {
|
||||||
displayName = displayName + " (" + user_id + ")";
|
displayName = displayName + " (" + user_id + ")";
|
||||||
break;
|
break;
|
||||||
|
@ -434,18 +357,8 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
$rootScope.$broadcast(RESET_EVENT);
|
$rootScope.$broadcast(RESET_EVENT);
|
||||||
},
|
},
|
||||||
|
|
||||||
initRoom: function(room) {
|
|
||||||
initRoom(room.room_id, room);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(event, isLiveEvent, isStateEvent) {
|
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
|
// Avoid duplicated events
|
||||||
// Needed for rooms where initialSync has not been done.
|
// 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
|
// 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.
|
// displays on the Room Info screen.
|
||||||
if (typeof(event.state_key) === "string") { // incls. 0-len strings
|
if (typeof(event.state_key) === "string") { // incls. 0-len strings
|
||||||
if (event.room_id) {
|
if (event.room_id) {
|
||||||
handleRoomDateEvent(event, isLiveEvent, false);
|
handleRoomStateEvent(event, isLiveEvent, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Unable to handle event type " + event.type);
|
console.log("Unable to handle event type " + event.type);
|
||||||
console.log(JSON.stringify(event, undefined, 4));
|
// console.log(JSON.stringify(event, undefined, 4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -524,8 +437,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
|
|
||||||
// Handle messages from /initialSync or /messages
|
// Handle messages from /initialSync or /messages
|
||||||
handleRoomMessages: function(room_id, messages, isLiveEvents, dir) {
|
handleRoomMessages: function(room_id, messages, isLiveEvents, dir) {
|
||||||
initRoom(room_id);
|
|
||||||
|
|
||||||
var events = messages.chunk;
|
var events = messages.chunk;
|
||||||
|
|
||||||
// Handles messages according to their time order
|
// 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
|
// 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 {
|
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--) {
|
for (var i=events.length - 1; i>=0; i--) {
|
||||||
this.handleEvent(events[i], isLiveEvents, isLiveEvents);
|
this.handleEvent(events[i], isLiveEvents, isLiveEvents);
|
||||||
}
|
}
|
||||||
// Store where to start pagination
|
// 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");
|
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
|
// 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) {
|
getLastMessage: function(room_id, filterEcho) {
|
||||||
var lastMessage;
|
var lastMessage;
|
||||||
|
|
||||||
var room = $rootScope.events.rooms[room_id];
|
var events = modelService.getRoom(room_id).events;
|
||||||
if (room) {
|
for (var i = events.length - 1; i >= 0; i--) {
|
||||||
for (var i = room.messages.length - 1; i >= 0; i--) {
|
var message = events[i];
|
||||||
var message = room.messages[i];
|
|
||||||
|
|
||||||
if (!filterEcho || undefined === message.echo_msg_state) {
|
if (!filterEcho || undefined === message.echo_msg_state) {
|
||||||
lastMessage = message;
|
lastMessage = message;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,18 +549,15 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService
|
||||||
getUsersCountInRoom: function(room_id) {
|
getUsersCountInRoom: function(room_id) {
|
||||||
var memberCount;
|
var memberCount;
|
||||||
|
|
||||||
var room = $rootScope.events.rooms[room_id];
|
var room = modelService.getRoom(room_id);
|
||||||
if (room) {
|
memberCount = 0;
|
||||||
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) {
|
var member = room.current_room_state.members[i].event;
|
||||||
if (!room.members.hasOwnProperty(i)) continue;
|
|
||||||
|
|
||||||
var member = room.members[i];
|
if ("join" === member.content.membership) {
|
||||||
|
memberCount = memberCount + 1;
|
||||||
if ("join" === member.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} room_id the room id
|
||||||
* @param {String} user_id the id of the user
|
* @param {String} user_id the user id
|
||||||
* @returns {undefined | Object} the member object of this user in this room if he is part of the room
|
* @returns {Number} a value between 0 and 10
|
||||||
*/
|
*/
|
||||||
getMember: function(room_id, user_id) {
|
getUserPowerLevel: function(room_id, user_id) {
|
||||||
return getMember(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) {
|
getUserDisplayName: function(room_id, user_id) {
|
||||||
return getUserDisplayName(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
|
// without requiring to make an additional request
|
||||||
matrixService.initialSync(30, false).then(
|
matrixService.initialSync(30, false).then(
|
||||||
function(response) {
|
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);
|
eventHandlerService.handleInitialSyncDone(response);
|
||||||
|
|
||||||
// Start event streaming from that point
|
// 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.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
|
||||||
window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
|
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', [])
|
angular.module('MatrixCall', [])
|
||||||
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) {
|
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $rootScope, $timeout) {
|
||||||
$rootScope.isWebRTCSupported = isWebRTCSupported();
|
$rootScope.isWebRTCSupported = function () {
|
||||||
|
return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate);
|
||||||
|
};
|
||||||
|
|
||||||
var MatrixCall = function(room_id) {
|
var MatrixCall = function(room_id) {
|
||||||
this.room_id = room_id;
|
this.room_id = room_id;
|
||||||
|
@ -213,8 +210,8 @@ angular.module('MatrixCall', [])
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var roomMembers = $rootScope.events.rooms[this.room_id].members;
|
var roomMembers = modelService.getRoom(this.room_id).current_room_state.members;
|
||||||
if (roomMembers[matrixService.config().user_id].membership != 'join') {
|
if (roomMembers[matrixService.config().user_id].event.content.membership != 'join') {
|
||||||
console.log("We need to join the room before we can accept this call");
|
console.log("We need to join the room before we can accept this call");
|
||||||
matrixService.join(this.room_id).then(function() {
|
matrixService.join(this.room_id).then(function() {
|
||||||
self.answer();
|
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 MatrixCall = $injector.get('MatrixCall');
|
||||||
var call = new MatrixCall(event.room_id);
|
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");
|
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
|
// 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.
|
// the call on their behalf would be really annoying.
|
|
@ -267,7 +267,7 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
// get room state for a specific room
|
// get room state for a specific room
|
||||||
roomState: function(room_id) {
|
roomState: function(room_id) {
|
||||||
var path = "/rooms/" + room_id + "/state";
|
var path = "/rooms/" + encodeURIComponent(room_id) + "/state";
|
||||||
return doRequest("GET", path);
|
return doRequest("GET", path);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -375,9 +375,11 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
|
|
||||||
sendStateEvent: function(room_id, eventType, content, state_key) {
|
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) {
|
if (state_key !== undefined) {
|
||||||
path += "/" + state_key;
|
path += "/" + encodeURIComponent(state_key);
|
||||||
}
|
}
|
||||||
room_id = encodeURIComponent(room_id);
|
room_id = encodeURIComponent(room_id);
|
||||||
path = path.replace("$room_id", room_id);
|
path = path.replace("$room_id", room_id);
|
||||||
|
@ -422,7 +424,8 @@ angular.module('matrixService', [])
|
||||||
var content = {
|
var content = {
|
||||||
msgtype: "m.image",
|
msgtype: "m.image",
|
||||||
url: image_url,
|
url: image_url,
|
||||||
body: image_body
|
info: image_body,
|
||||||
|
body: "Image"
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.sendMessage(room_id, msg_id, content);
|
return this.sendMessage(room_id, msg_id, content);
|
||||||
|
@ -440,7 +443,8 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
redactEvent: function(room_id, event_id) {
|
redactEvent: function(room_id, event_id) {
|
||||||
var path = "/rooms/$room_id/redact/$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);
|
path = path.replace("$event_id", event_id);
|
||||||
var content = {};
|
var content = {};
|
||||||
return doRequest("POST", path, undefined, content);
|
return doRequest("POST", path, undefined, content);
|
||||||
|
@ -458,7 +462,7 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
paginateBackMessages: function(room_id, from_token, limit) {
|
paginateBackMessages: function(room_id, from_token, limit) {
|
||||||
var path = "/rooms/$room_id/messages";
|
var path = "/rooms/$room_id/messages";
|
||||||
path = path.replace("$room_id", room_id);
|
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||||
var params = {
|
var params = {
|
||||||
from: from_token,
|
from: from_token,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
|
@ -506,12 +510,12 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
setProfileInfo: function(data, info_segment) {
|
setProfileInfo: function(data, info_segment) {
|
||||||
var path = "/profile/$user/" + 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);
|
return doRequest("PUT", path, undefined, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
getProfileInfo: function(userId, info_segment) {
|
getProfileInfo: function(userId, info_segment) {
|
||||||
var path = "/profile/"+userId
|
var path = "/profile/"+encodeURIComponent(userId);
|
||||||
if (info_segment) path += '/' + info_segment;
|
if (info_segment) path += '/' + info_segment;
|
||||||
return doRequest("GET", path);
|
return doRequest("GET", path);
|
||||||
},
|
},
|
||||||
|
@ -630,7 +634,7 @@ angular.module('matrixService', [])
|
||||||
// Set the logged in user presence state
|
// Set the logged in user presence state
|
||||||
setUserPresence: function(presence) {
|
setUserPresence: function(presence) {
|
||||||
var path = "/presence/$user_id/status";
|
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, {
|
return doRequest("PUT", path, undefined, {
|
||||||
presence: presence
|
presence: presence
|
||||||
});
|
});
|
||||||
|
@ -725,56 +729,29 @@ angular.module('matrixService', [])
|
||||||
return 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
|
* Change or reset the power level of a user
|
||||||
* @param {String} room_id the room id
|
* @param {String} room_id the room id
|
||||||
* @param {String} user_id the user 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
|
* 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
|
* @returns {promise} an $http promise
|
||||||
*/
|
*/
|
||||||
setUserPowerLevel: function(room_id, user_id, powerLevel) {
|
setUserPowerLevel: function(room_id, user_id, powerLevel, event) {
|
||||||
|
var content = {};
|
||||||
// Hack: currently, there is no home server API so do it by hand by updating
|
if (event) {
|
||||||
// the current m.room.power_levels of the room and send it to the server
|
// if there is an existing event, copy the content as it contains
|
||||||
var room = $rootScope.events.rooms[room_id];
|
// the power level values for other members which we do not want
|
||||||
if (room && room["m.room.power_levels"]) {
|
// to modify.
|
||||||
var content = angular.copy(room["m.room.power_levels"].content);
|
content = angular.copy(event.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);
|
|
||||||
}
|
}
|
||||||
|
content[user_id] = powerLevel;
|
||||||
|
|
||||||
// The room does not exist or does not contain power_levels data
|
var path = "/rooms/$room_id/state/m.room.power_levels";
|
||||||
var deferred = $q.defer();
|
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||||
deferred.reject({data:{error: "Invalid room: " + room_id}});
|
|
||||||
return deferred.promise;
|
return doRequest("PUT", path, undefined, content);
|
||||||
},
|
},
|
||||||
|
|
||||||
getTurnServer: function() {
|
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
|
// Add room_alias & room_display_name members
|
||||||
angular.extend(room, matrixService.getRoomAliasAndDisplayName(room));
|
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='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 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-route.min.js"></script>
|
||||||
<script src="js/angular-sanitize.min.js"></script>
|
<script src="js/angular-sanitize.min.js"></script>
|
||||||
<script src="js/angular-animate.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-stream-service.js"></script>
|
||||||
<script src="components/matrix/event-handler-service.js"></script>
|
<script src="components/matrix/event-handler-service.js"></script>
|
||||||
<script src="components/matrix/notification-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/matrix/presence-service.js"></script>
|
||||||
<script src="components/fileInput/file-input-directive.js"></script>
|
<script src="components/fileInput/file-input-directive.js"></script>
|
||||||
<script src="components/fileUpload/file-upload-service.js"></script>
|
<script src="components/fileUpload/file-upload-service.js"></script>
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span ng-show="currentCall.state == 'ringing'">
|
<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>
|
<button ng-click="hangupCall()">Reject</button>
|
||||||
</span>
|
</span>
|
||||||
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
|
<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';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,6 +56,8 @@ angular.mock.$Browser = function() {
|
||||||
return listener;
|
return listener;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.$$checkUrlChange = angular.noop;
|
||||||
|
|
||||||
self.cookieHash = {};
|
self.cookieHash = {};
|
||||||
self.lastCookieHash = {};
|
self.lastCookieHash = {};
|
||||||
self.deferredFns = [];
|
self.deferredFns = [];
|
||||||
|
@ -125,7 +120,7 @@ angular.mock.$Browser = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.$$baseHref = '';
|
self.$$baseHref = '/';
|
||||||
self.baseHref = function() {
|
self.baseHref = function() {
|
||||||
return this.$$baseHref;
|
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 = {
|
var animate = {
|
||||||
queue : [],
|
queue : [],
|
||||||
|
cancel : $delegate.cancel,
|
||||||
enabled : $delegate.enabled,
|
enabled : $delegate.enabled,
|
||||||
triggerCallbacks : function() {
|
triggerCallbackEvents : function() {
|
||||||
$$asyncCallback.flush();
|
$$asyncCallback.flush();
|
||||||
},
|
},
|
||||||
|
triggerCallbackPromise : function() {
|
||||||
|
$timeout.flush(0);
|
||||||
|
},
|
||||||
|
triggerCallbacks : function() {
|
||||||
|
this.triggerCallbackEvents();
|
||||||
|
this.triggerCallbackPromise();
|
||||||
|
},
|
||||||
triggerReflow : function() {
|
triggerReflow : function() {
|
||||||
angular.forEach(reflowQueue, function(fn) {
|
angular.forEach(reflowQueue, function(fn) {
|
||||||
fn();
|
fn();
|
||||||
|
@ -797,12 +801,12 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
||||||
element : arguments[0],
|
element : arguments[0],
|
||||||
args : arguments
|
args : arguments
|
||||||
});
|
});
|
||||||
$delegate[method].apply($delegate, arguments);
|
return $delegate[method].apply($delegate, arguments);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return animate;
|
return animate;
|
||||||
});
|
}]);
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -888,7 +892,7 @@ angular.mock.dump = function(object) {
|
||||||
* development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
|
* 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
|
* 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
|
* [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
|
* 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
|
* 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
|
```js
|
||||||
// testing controller
|
// testing controller
|
||||||
describe('MyController', function() {
|
describe('MyController', function() {
|
||||||
var $httpBackend, $rootScope, createController;
|
var $httpBackend, $rootScope, createController, authRequestHandler;
|
||||||
|
|
||||||
beforeEach(inject(function($injector) {
|
beforeEach(inject(function($injector) {
|
||||||
// Set up the mock http service responses
|
// Set up the mock http service responses
|
||||||
$httpBackend = $injector.get('$httpBackend');
|
$httpBackend = $injector.get('$httpBackend');
|
||||||
// backend definition common for all tests
|
// 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)
|
// Get hold of a scope (i.e. the root scope)
|
||||||
$rootScope = $injector.get('$rootScope');
|
$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() {
|
it('should send msg to server', function() {
|
||||||
var controller = createController();
|
var controller = createController();
|
||||||
$httpBackend.flush();
|
$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
|
// the controller will still send the request and
|
||||||
// $httpBackend will respond without you having to
|
// $httpBackend will respond without you having to
|
||||||
// specify the expectation and response for this request
|
// specify the expectation and response for this request
|
||||||
|
@ -1186,32 +1203,39 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
* Creates a new backend definition.
|
* Creates a new backend definition.
|
||||||
*
|
*
|
||||||
* @param {string} method HTTP method.
|
* @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
|
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||||
* data string and returns true if the data is as expected.
|
* data string and returns true if the data is as expected.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||||
* object and returns true if the headers match the current definition.
|
* object and returns true if the headers match the current definition.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
* @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([status,] data[, headers, statusText])
|
||||||
* | function(function(method, url, data, headers)}`
|
* | 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
|
* 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) {
|
$httpBackend.when = function(method, url, data, headers) {
|
||||||
var definition = new MockHttpExpectation(method, url, data, headers),
|
var definition = new MockHttpExpectation(method, url, data, headers),
|
||||||
chain = {
|
chain = {
|
||||||
respond: function(status, data, headers, statusText) {
|
respond: function(status, data, headers, statusText) {
|
||||||
|
definition.passThrough = undefined;
|
||||||
definition.response = createResponse(status, data, headers, statusText);
|
definition.response = createResponse(status, data, headers, statusText);
|
||||||
|
return chain;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($browser) {
|
if ($browser) {
|
||||||
chain.passThrough = function() {
|
chain.passThrough = function() {
|
||||||
|
definition.response = undefined;
|
||||||
definition.passThrough = true;
|
definition.passThrough = true;
|
||||||
|
return chain;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1225,10 +1249,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
* @description
|
* @description
|
||||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
* 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
|
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||||
* data string and returns true if the data is as expected.
|
* data string and returns true if the data is as expected.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
* 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
|
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||||
* data string and returns true if the data is as expected.
|
* data string and returns true if the data is as expected.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
* 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
|
* @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');
|
createShortMethods('when');
|
||||||
|
|
||||||
|
@ -1303,30 +1339,36 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
* Creates a new request expectation.
|
* Creates a new request expectation.
|
||||||
*
|
*
|
||||||
* @param {string} method HTTP method.
|
* @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
|
* @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
|
* receives data string and returns true if the data is as expected, or Object if request body
|
||||||
* is in JSON format.
|
* is in JSON format.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||||
* object and returns true if the headers match the current expectation.
|
* object and returns true if the headers match the current expectation.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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([status,] data[, headers, statusText])
|
||||||
* | function(function(method, url, data, headers)}`
|
* | 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
|
* 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) {
|
$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);
|
expectations.push(expectation);
|
||||||
return {
|
return chain;
|
||||||
respond: function (status, data, headers, statusText) {
|
|
||||||
expectation.response = createResponse(status, data, headers, statusText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1336,10 +1378,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
* @description
|
* @description
|
||||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
* 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.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
* 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.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
* 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.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
* 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
|
* @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
|
* receives data string and returns true if the data is as expected, or Object if request body
|
||||||
* is in JSON format.
|
* is in JSON format.
|
||||||
* @param {Object=} headers HTTP headers.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
* 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
|
* @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
|
* receives data string and returns true if the data is as expected, or Object if request body
|
||||||
* is in JSON format.
|
* is in JSON format.
|
||||||
* @param {Object=} headers HTTP headers.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
* 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
|
* @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
|
* receives data string and returns true if the data is as expected, or Object if request body
|
||||||
* is in JSON format.
|
* is in JSON format.
|
||||||
* @param {Object=} headers HTTP headers.
|
* @param {Object=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
* @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
|
* @description
|
||||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
* 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
|
* @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');
|
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
|
* 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).
|
* is called an exception is thrown (as this typically a sign of programming error).
|
||||||
*/
|
*/
|
||||||
$httpBackend.flush = function(count) {
|
$httpBackend.flush = function(count, digest) {
|
||||||
$rootScope.$digest();
|
if (digest !== false) $rootScope.$digest();
|
||||||
if (!responses.length) throw new Error('No pending request to flush !');
|
if (!responses.length) throw new Error('No pending request to flush !');
|
||||||
|
|
||||||
if (angular.isDefined(count)) {
|
if (angular.isDefined(count) && count !== null) {
|
||||||
while (count--) {
|
while (count--) {
|
||||||
if (!responses.length) throw new Error('No more pending request to flush !');
|
if (!responses.length) throw new Error('No more pending request to flush !');
|
||||||
responses.shift()();
|
responses.shift()();
|
||||||
|
@ -1448,7 +1504,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
responses.shift()();
|
responses.shift()();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$httpBackend.verifyNoOutstandingExpectation();
|
$httpBackend.verifyNoOutstandingExpectation(digest);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1466,8 +1522,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
* afterEach($httpBackend.verifyNoOutstandingExpectation);
|
* afterEach($httpBackend.verifyNoOutstandingExpectation);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
$httpBackend.verifyNoOutstandingExpectation = function() {
|
$httpBackend.verifyNoOutstandingExpectation = function(digest) {
|
||||||
$rootScope.$digest();
|
if (digest !== false) $rootScope.$digest();
|
||||||
if (expectations.length) {
|
if (expectations.length) {
|
||||||
throw new Error('Unsatisfied requests: ' + expectations.join(', '));
|
throw new Error('Unsatisfied requests: ' + expectations.join(', '));
|
||||||
}
|
}
|
||||||
|
@ -1511,7 +1567,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||||
|
|
||||||
|
|
||||||
function createShortMethods(prefix) {
|
function createShortMethods(prefix) {
|
||||||
angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
|
angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
|
||||||
$httpBackend[prefix + method] = function(url, headers) {
|
$httpBackend[prefix + method] = function(url, headers) {
|
||||||
return $httpBackend[prefix](method, url, undefined, headers);
|
return $httpBackend[prefix](method, url, undefined, headers);
|
||||||
};
|
};
|
||||||
|
@ -1541,6 +1597,7 @@ function MockHttpExpectation(method, url, data, headers) {
|
||||||
this.matchUrl = function(u) {
|
this.matchUrl = function(u) {
|
||||||
if (!url) return true;
|
if (!url) return true;
|
||||||
if (angular.isFunction(url.test)) return url.test(u);
|
if (angular.isFunction(url.test)) return url.test(u);
|
||||||
|
if (angular.isFunction(url)) return url(u);
|
||||||
return url == u;
|
return url == u;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1627,7 +1684,7 @@ function MockXhr() {
|
||||||
* that adds a "flush" and "verifyNoPendingTasks" methods.
|
* that adds a "flush" and "verifyNoPendingTasks" methods.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc method
|
* @ngdoc method
|
||||||
|
@ -1666,9 +1723,9 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $delegate;
|
return $delegate;
|
||||||
};
|
}];
|
||||||
|
|
||||||
angular.mock.$RAFDecorator = function($delegate) {
|
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||||
var queue = [];
|
var queue = [];
|
||||||
var rafFn = function(fn) {
|
var rafFn = function(fn) {
|
||||||
var index = queue.length;
|
var index = queue.length;
|
||||||
|
@ -1694,9 +1751,9 @@ angular.mock.$RAFDecorator = function($delegate) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return rafFn;
|
return rafFn;
|
||||||
};
|
}];
|
||||||
|
|
||||||
angular.mock.$AsyncCallbackDecorator = function($delegate) {
|
angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) {
|
||||||
var callbacks = [];
|
var callbacks = [];
|
||||||
var addFn = function(fn) {
|
var addFn = function(fn) {
|
||||||
callbacks.push(fn);
|
callbacks.push(fn);
|
||||||
|
@ -1708,7 +1765,7 @@ angular.mock.$AsyncCallbackDecorator = function($delegate) {
|
||||||
callbacks = [];
|
callbacks = [];
|
||||||
};
|
};
|
||||||
return addFn;
|
return addFn;
|
||||||
};
|
}];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -1822,22 +1879,25 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||||
* Creates a new backend definition.
|
* Creates a new backend definition.
|
||||||
*
|
*
|
||||||
* @param {string} method HTTP method.
|
* @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 {(string|RegExp)=} data HTTP request body.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||||
* object and returns true if the headers match the current definition.
|
* object and returns true if the headers match the current definition.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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([status,] data[, headers, statusText])
|
||||||
* | function(function(method, url, data, headers)}`
|
* | 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
|
* an array containing response status (number), response data (string), response headers
|
||||||
* (Object), and the text for the status (string).
|
* (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
|
* `passThrough` handler will be passed through to the real backend (an XHR request will be made
|
||||||
* to the server.)
|
* 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
|
* @description
|
||||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
* 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.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
* 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 {(string|RegExp)=} data HTTP request body.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
* 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 {(string|RegExp)=} data HTTP request body.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
* 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 {(string|RegExp)=} data HTTP request body.
|
||||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
* @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
|
* @description
|
||||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
* 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
|
* @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 = {};
|
||||||
angular.mock.e2e.$httpBackendDecorator =
|
angular.mock.e2e.$httpBackendDecorator =
|
||||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
['$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) {
|
if(window.jasmine || window.mocha) {
|
||||||
|
|
||||||
var currentSpec = null,
|
var currentSpec = null,
|
||||||
|
@ -1982,8 +2041,6 @@ if(window.jasmine || window.mocha) {
|
||||||
injector.get('$browser').pollFns.length = 0;
|
injector.get('$browser').pollFns.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.mock.clearDataCache();
|
|
||||||
|
|
||||||
// clean up jquery's fragment cache
|
// clean up jquery's fragment cache
|
||||||
angular.forEach(angular.element.fragments, function(val, key) {
|
angular.forEach(angular.element.fragments, function(val, key) {
|
||||||
delete angular.element.fragments[key];
|
delete angular.element.fragments[key];
|
||||||
|
@ -2003,6 +2060,7 @@ if(window.jasmine || window.mocha) {
|
||||||
* @description
|
* @description
|
||||||
*
|
*
|
||||||
* *NOTE*: This function is also published on window for easy access.<br>
|
* *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
|
* 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}.
|
* 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
|
* @description
|
||||||
*
|
*
|
||||||
* *NOTE*: This function is also published on window for easy access.<br>
|
* *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
|
* 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
|
* instance of {@link auto.$injector $injector} per test, which is then used for
|
||||||
|
@ -2144,14 +2203,28 @@ if(window.jasmine || window.mocha) {
|
||||||
/////////////////////
|
/////////////////////
|
||||||
function workFn() {
|
function workFn() {
|
||||||
var modules = currentSpec.$modules || [];
|
var modules = currentSpec.$modules || [];
|
||||||
|
var strictDi = !!currentSpec.$injectorStrict;
|
||||||
modules.unshift('ngMock');
|
modules.unshift('ngMock');
|
||||||
modules.unshift('ng');
|
modules.unshift('ng');
|
||||||
var injector = currentSpec.$injector;
|
var injector = currentSpec.$injector;
|
||||||
if (!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++) {
|
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 {
|
try {
|
||||||
/* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
|
/* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
|
||||||
injector.invoke(blockFns[i] || angular.noop, this);
|
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");
|
$location.url("home");
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
console.trace("Registration error: "+error);
|
console.error("Registration error: "+JSON.stringify(error));
|
||||||
if (useCaptcha) {
|
if (useCaptcha) {
|
||||||
Recaptcha.reload();
|
Recaptcha.reload();
|
||||||
}
|
}
|
|
@ -17,12 +17,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('RecentsController', ['matrixService', 'matrixFilter'])
|
angular.module('RecentsController', ['matrixService', 'matrixFilter'])
|
||||||
.controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService',
|
.controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService', 'modelService',
|
||||||
function($rootScope, $scope, eventHandlerService) {
|
function($rootScope, $scope, eventHandlerService, modelService) {
|
||||||
|
|
||||||
// Expose the service to the view
|
// Expose the service to the view
|
||||||
$scope.eventHandlerService = eventHandlerService;
|
$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
|
// $rootScope of the parent where the recents component is included can override this value
|
||||||
// in order to highlight a specific room in the list
|
// in order to highlight a specific room in the list
|
||||||
$rootScope.recentsSelectedRoomID;
|
$rootScope.recentsSelectedRoomID;
|
|
@ -17,7 +17,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('RecentsController')
|
angular.module('RecentsController')
|
||||||
.filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) {
|
.filter('orderRecents', ["matrixService", "eventHandlerService", "modelService", function(matrixService, eventHandlerService, modelService) {
|
||||||
return function(rooms) {
|
return function(rooms) {
|
||||||
var user_id = matrixService.config().user_id;
|
var user_id = matrixService.config().user_id;
|
||||||
|
|
||||||
|
@ -25,26 +25,33 @@ angular.module('RecentsController')
|
||||||
// The key, room_id, is already in value objects
|
// The key, room_id, is already in value objects
|
||||||
var filtered = [];
|
var filtered = [];
|
||||||
angular.forEach(rooms, function(room, room_id) {
|
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
|
// Show the room only if the user has joined it or has been invited
|
||||||
// (ie, do not show it if he has been banned)
|
// (ie, do not show it if he has been banned)
|
||||||
var member = eventHandlerService.getMember(room_id, user_id);
|
var member = modelService.getMember(room_id, user_id);
|
||||||
if (member && ("invite" === member.membership || "join" === member.membership)) {
|
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
|
// Count users here
|
||||||
// TODO: Compute it directly in eventHandlerService
|
// TODO: Compute it directly in eventHandlerService
|
||||||
room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
|
room.recent.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
|
||||||
|
|
||||||
filtered.push(room);
|
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
|
// The only information we have about the room is that the user has been invited
|
||||||
filtered.push(room);
|
filtered.push(room);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// And time sort them
|
// 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) {
|
filtered.sort(function (roomA, roomB) {
|
||||||
|
|
||||||
var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true);
|
var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true);
|
|
@ -1,16 +1,16 @@
|
||||||
<div ng-controller="RecentsController">
|
<div ng-controller="RecentsController">
|
||||||
<table class="recentsTable">
|
<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) )"
|
ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )"
|
||||||
class="recentsRoom"
|
class="recentsRoom"
|
||||||
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
||||||
<tr>
|
<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 }}
|
{{ room.room_id | mRoomName }}
|
||||||
</td>
|
</td>
|
||||||
<td class="recentsRoomSummaryUsersCount">
|
<td class="recentsRoomSummaryUsersCount">
|
||||||
<span ng-show="undefined !== room.numUsersInRoom">
|
<span ng-show="undefined !== room.recent.numUsersInRoom">
|
||||||
{{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }}
|
{{ room.recent.numUsersInRoom || '1' }} {{ room.recent.numUsersInRoom == 1 ? 'user' : 'users' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="recentsRoomSummaryTS">
|
<td class="recentsRoomSummaryTS">
|
||||||
|
@ -27,11 +27,11 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="recentsRoomSummary">
|
<td colspan="3" class="recentsRoomSummary">
|
||||||
|
|
||||||
<div ng-show="room.membership === 'invite'">
|
<div ng-show="room.recent.me.content.membership === 'invite'">
|
||||||
{{ room.inviter | mUserDisplayName: room.room_id }} invited you
|
{{ room.recent.inviter | mUserDisplayName: room.room_id }} invited you
|
||||||
</div>
|
</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">
|
<div ng-switch-when="m.room.member">
|
||||||
<span ng-switch="lastMsg.changedKey">
|
<span ng-switch="lastMsg.changedKey">
|
||||||
<span ng-switch-when="membership">
|
<span ng-switch-when="membership">
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
.controller('RoomController', ['$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) {
|
function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var MESSAGES_PER_PAGINATION = 30;
|
var MESSAGES_PER_PAGINATION = 30;
|
||||||
var THUMBNAIL_SIZE = 320;
|
var THUMBNAIL_SIZE = 320;
|
||||||
|
@ -64,7 +64,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
return;
|
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) {
|
if (nameEvent) {
|
||||||
$scope.name.newNameText = nameEvent.content.name;
|
$scope.name.newNameText = nameEvent.content.name;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
console.log("Warning: Already editing topic.");
|
console.log("Warning: Already editing topic.");
|
||||||
return;
|
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) {
|
if (topicEvent) {
|
||||||
$scope.topic.newTopicText = topicEvent.content.topic;
|
$scope.topic.newTopicText = topicEvent.content.topic;
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
}
|
}
|
||||||
notificationService.showNotification(
|
notificationService.showNotification(
|
||||||
userName +
|
userName +
|
||||||
" (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")",
|
" (" + $filter("mRoomName")(event.room_id) + ")",
|
||||||
userName + " joined",
|
userName + " joined",
|
||||||
event.content.avatar_url ? event.content.avatar_url : undefined,
|
event.content.avatar_url ? event.content.avatar_url : undefined,
|
||||||
function() {
|
function() {
|
||||||
|
@ -254,11 +254,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
$scope.state.paginating = true;
|
$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];
|
var originalTopRow = $("#messageTable>tbody>tr:first")[0];
|
||||||
|
|
||||||
// Paginate events from the point in cache
|
// 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) {
|
function(response) {
|
||||||
|
|
||||||
eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b');
|
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 updateUserPowerLevel = function(user_id) {
|
||||||
var member = $scope.members[user_id];
|
var member = $scope.members[user_id];
|
||||||
if (member) {
|
if (member) {
|
||||||
member.powerLevel = matrixService.getUserPowerLevel($scope.room_id, user_id);
|
member.powerLevel = eventHandlerService.getUserPowerLevel($scope.room_id, user_id);
|
||||||
|
|
||||||
normaliseMembersPowerLevels();
|
normaliseMembersPowerLevels();
|
||||||
}
|
}
|
||||||
|
@ -492,7 +492,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
|
|
||||||
var room_id = matrixService.getAliasToRoomIdMapping(room_alias);
|
var room_id = matrixService.getAliasToRoomIdMapping(room_alias);
|
||||||
console.log("joining " + room_alias + " id=" + room_id);
|
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.
|
// don't send a join event for a room you're already in.
|
||||||
$location.url("room/" + room_alias);
|
$location.url("room/" + room_alias);
|
||||||
}
|
}
|
||||||
|
@ -576,7 +576,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
powerLevel = parseInt(matches[3]);
|
powerLevel = parseInt(matches[3]);
|
||||||
}
|
}
|
||||||
if (powerLevel !== NaN) {
|
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) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
var matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
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('');
|
$('#mainInput').val('');
|
||||||
$rootScope.events.rooms[$scope.room_id].messages.push(echoMessage);
|
$scope.room.addMessageEvent(echoMessage);
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,6 +719,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
|
|
||||||
var onInit2 = function() {
|
var onInit2 = function() {
|
||||||
console.log("onInit2");
|
console.log("onInit2");
|
||||||
|
// =============================
|
||||||
|
$scope.room = modelService.getRoom($scope.room_id);
|
||||||
|
// =============================
|
||||||
|
|
||||||
// Scroll down as soon as possible so that we point to the last message
|
// Scroll down as soon as possible so that we point to the last message
|
||||||
// if it already exists in memory
|
// if it already exists in memory
|
||||||
|
@ -729,9 +734,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
var needsToJoin = true;
|
var needsToJoin = true;
|
||||||
|
|
||||||
// The room members is available in the data fetched by initialSync
|
// 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
|
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)) {
|
|| (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;
|
$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
|
// Update the member list
|
||||||
for (var i in members) {
|
for (var i in members) {
|
||||||
if (!members.hasOwnProperty(i)) continue;
|
if (!members.hasOwnProperty(i)) continue;
|
||||||
|
|
||||||
var member = members[i];
|
var member = members[i].event;
|
||||||
updateMemberList(member);
|
updateMemberList(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has already join the room
|
// Check if the user has already join the room
|
||||||
if ($scope.state.user_id in members) {
|
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;
|
needsToJoin = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -999,10 +1004,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.openJson = function(content) {
|
$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
|
// scope this so the template can check power levels and enable/disable
|
||||||
// buttons
|
// buttons
|
||||||
$scope.pow = matrixService.getUserPowerLevel;
|
$scope.pow = eventHandlerService.getUserPowerLevel;
|
||||||
|
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
templateUrl: 'eventInfoTemplate.html',
|
templateUrl: 'eventInfoTemplate.html',
|
||||||
|
@ -1039,8 +1049,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
state_key: ""
|
state_key: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
var stateFilter = $filter("stateEventsFilter");
|
var stateEvents = $scope.room.current_room_state.state_events;
|
||||||
var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]);
|
|
||||||
// The modal dialog will 2-way bind this field, so we MUST make a deep
|
// 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
|
// copy of the state events else we will be *actually adjusing our view
|
||||||
// of the world* when fiddling with the JSON!! Apparently parse/stringify
|
// 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));
|
console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected));
|
||||||
$scope.redact = function() {
|
$scope.redact = function() {
|
||||||
console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+
|
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));
|
console.log("Redact event >> " + JSON.stringify($scope.event_selected));
|
||||||
$modalInstance.close("redact");
|
$modalInstance.close("redact");
|
||||||
};
|
};
|
|
@ -6,7 +6,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button ng-click="redact()" type="button" class="btn btn-danger"
|
<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.">
|
title="Delete this event on all home servers. This cannot be undone.">
|
||||||
Redact
|
Redact
|
||||||
</button>
|
</button>
|
||||||
|
@ -18,7 +18,8 @@
|
||||||
<table class="room-info">
|
<table class="room-info">
|
||||||
<tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event">
|
<tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event">
|
||||||
<td class="room-info-event-meta" width="30%">
|
<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/>
|
<br/>
|
||||||
{{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
|
{{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -68,13 +69,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="roomTopicSection">
|
<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">
|
ng-click="topic.editTopic()" class="roomTopicSetNew">
|
||||||
Set Topic
|
Set Topic
|
||||||
</button>
|
</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">
|
<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>
|
</div>
|
||||||
<form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm">
|
<form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm">
|
||||||
<input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/>
|
<input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/>
|
||||||
|
@ -123,32 +124,34 @@
|
||||||
ng-style="{ 'visibility': state.messages_visibility }"
|
ng-style="{ 'visibility': state.messages_visibility }"
|
||||||
keep-scroll>
|
keep-scroll>
|
||||||
<table id="messageTable" infinite-scroll="paginateMore()">
|
<table id="messageTable" infinite-scroll="paginateMore()">
|
||||||
<tr ng-repeat="msg in events.rooms[room_id].messages"
|
<tr ng-repeat="msg in room.events"
|
||||||
ng-class="(events.rooms[room_id].messages[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item>
|
ng-class="(room.events[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item>
|
||||||
<td class="leftBlock">
|
<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"
|
<div class="timestamp"
|
||||||
ng-class="msg.echo_msg_state">
|
ng-class="msg.echo_msg_state">
|
||||||
{{ (msg.origin_server_ts) | date:'MMM d HH:mm' }}
|
{{ (msg.origin_server_ts) | date:'MMM d HH:mm' }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="avatar">
|
<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}}"
|
<!-- msg.__room_member.avatar_url is just backwards compat, and can be removed in the future. -->
|
||||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
|
<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>
|
||||||
<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'">
|
<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'">
|
<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>
|
||||||
<span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'">
|
<span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'">
|
||||||
<span ng-if="msg.user_id === msg.state_key">
|
<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>
|
||||||
<span ng-if="msg.user_id !== msg.state_key && msg.prev_content">
|
<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] }}
|
{{ {"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">
|
<span ng-if="'join' === msg.prev_content.membership && msg.content.reason">
|
||||||
: {{ msg.content.reason }}
|
: {{ msg.content.reason }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -156,9 +159,9 @@
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' ||
|
<span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' ||
|
||||||
'ban' === 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] }}
|
{{ {"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">
|
<span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason">
|
||||||
: {{ msg.content.reason }}
|
: {{ msg.content.reason }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -179,8 +182,8 @@
|
||||||
(msg.content.formatted_body | unsanitizedLinky) :
|
(msg.content.formatted_body | unsanitizedLinky) :
|
||||||
(msg.content.msgtype === 'm.text' && msg.type === 'm.room.message') ? (msg.content.body | linky:'_blank') : '' "/>
|
(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'>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'>Incoming Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span>
|
||||||
|
|
||||||
<div ng-show='msg.content.msgtype === "m.image"'>
|
<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}">
|
<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>
|
||||||
<td class="rightBlock">
|
<td class="rightBlock">
|
||||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||||
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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -245,15 +248,15 @@
|
||||||
<button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave</button>
|
<button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave</button>
|
||||||
<button ng-click="startVoiceCall()"
|
<button ng-click="startVoiceCall()"
|
||||||
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
||||||
ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2"
|
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') }}"
|
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
|
Voice Call
|
||||||
</button>
|
</button>
|
||||||
<button ng-click="startVideoCall()"
|
<button ng-click="startVideoCall()"
|
||||||
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
ng-show="(currentCall == undefined || currentCall.state == 'ended')"
|
||||||
ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2"
|
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') }}"
|
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
|
Video Call
|
||||||
</button>
|
</button>
|
|
@ -1,13 +1,31 @@
|
||||||
Requires:
|
Testing is done using Karma.
|
||||||
- nodejs/npm
|
|
||||||
- npm install karma
|
|
||||||
- npm install jasmine
|
|
||||||
- npm install protractor (e2e testing)
|
|
||||||
|
|
||||||
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
|
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
|
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):
|
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-animate.js',
|
||||||
'../js/angular-sanitize.js',
|
'../js/angular-sanitize.js',
|
||||||
'../js/ng-infinite-scroll-matrix.js',
|
'../js/ng-infinite-scroll-matrix.js',
|
||||||
'../login/**/*.*',
|
'../js/ui-bootstrap*',
|
||||||
'../room/**/*.*',
|
'../js/elastic.js',
|
||||||
'../components/**/*.*',
|
'../login/**/*.js',
|
||||||
'../user/**/*.*',
|
'../room/**/*.js',
|
||||||
'../home/**/*.*',
|
'../components/**/*.js',
|
||||||
'../recents/**/*.*',
|
'../user/**/*.js',
|
||||||
'../settings/**/*.*',
|
'../home/**/*.js',
|
||||||
|
'../recents/**/*.js',
|
||||||
|
'../settings/**/*.js',
|
||||||
'../app.js',
|
'../app.js',
|
||||||
'../app*',
|
'../app*',
|
||||||
'./unit/**/*.js'
|
'./unit/**/*.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
'karma-*',
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
// list of files to exclude
|
// list of files to exclude
|
||||||
exclude: [
|
exclude: [
|
||||||
|
@ -50,8 +56,11 @@ module.exports = function(config) {
|
||||||
// test results reporter to use
|
// test results reporter to use
|
||||||
// possible values: 'dots', 'progress'
|
// possible values: 'dots', 'progress'
|
||||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||||
reporters: ['progress'],
|
reporters: ['progress', 'junit'],
|
||||||
|
junitReporter: {
|
||||||
|
outputFile: 'test-results.xml',
|
||||||
|
suite: ''
|
||||||
|
},
|
||||||
|
|
||||||
// web server port
|
// web server port
|
||||||
port: 9876,
|
port: 9876,
|
||||||
|
@ -72,11 +81,11 @@ module.exports = function(config) {
|
||||||
|
|
||||||
// start these browsers
|
// start these browsers
|
||||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||||
browsers: ['Chrome'],
|
browsers: ['PhantomJS'],
|
||||||
|
|
||||||
|
|
||||||
// Continuous Integration mode
|
// Continuous Integration mode
|
||||||
// if true, Karma captures browsers, runs the tests and exits
|
// 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);
|
|
||||||
};
|
|
||||||
}]);
|
|