mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-27 20:22:07 +03:00
Merge remote-tracking branch 'origin/develop' into server2server_tls
This commit is contained in:
commit
00b042a3eb
10 changed files with 261 additions and 101 deletions
|
@ -1007,26 +1007,15 @@ for users from other servers entirely.
|
||||||
Presence
|
Presence
|
||||||
========
|
========
|
||||||
|
|
||||||
In the following messages, the presence state is an integer enumeration of the
|
In the following messages, the presence state is a presence string as described in
|
||||||
following states:
|
the main specification document.
|
||||||
0 : OFFLINE
|
|
||||||
1 : BUSY
|
|
||||||
2 : ONLINE
|
|
||||||
3 : FREE_TO_CHAT
|
|
||||||
|
|
||||||
Aside from OFFLINE, the protocol doesn't assign any special meaning to these
|
|
||||||
states; they are provided as an approximate signal for users to give to other
|
|
||||||
users and for clients to present them in some way that may be useful. Clients
|
|
||||||
could have different behaviours for different states of the user's presence, for
|
|
||||||
example to decide how much prominence or sound to use for incoming event
|
|
||||||
notifications.
|
|
||||||
|
|
||||||
Getting/Setting your own presence state
|
Getting/Setting your own presence state
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
REST Path: /presence/$user_id/status
|
REST Path: /presence/$user_id/status
|
||||||
Valid methods: GET/PUT
|
Valid methods: GET/PUT
|
||||||
Required keys:
|
Required keys:
|
||||||
presence : [0|1|2|3] - The user's new presence state
|
presence : <string> - The user's new presence state
|
||||||
Optional keys:
|
Optional keys:
|
||||||
status_msg : text string provided by the user to explain their status
|
status_msg : text string provided by the user to explain their status
|
||||||
|
|
||||||
|
|
|
@ -417,7 +417,7 @@ State events can be sent by ``PUT`` ing to ``/rooms/<room id>/state/<event type>
|
||||||
These events will be overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all match.
|
These events will be overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all match.
|
||||||
If the state event has no ``state_key``, it can be omitted from the path. These requests
|
If the state event has no ``state_key``, it can be omitted from the path. These requests
|
||||||
**cannot use transaction IDs** like other ``PUT`` paths because they cannot be differentiated
|
**cannot use transaction IDs** like other ``PUT`` paths because they cannot be differentiated
|
||||||
from the ``state key``. Furthermore, ``POST`` is unsupported on state paths. Valid requests
|
from the ``state_key``. Furthermore, ``POST`` is unsupported on state paths. Valid requests
|
||||||
look like::
|
look like::
|
||||||
|
|
||||||
PUT /rooms/!roomid:domain/state/m.example.event
|
PUT /rooms/!roomid:domain/state/m.example.event
|
||||||
|
@ -440,7 +440,7 @@ Care should be taken to avoid setting the wrong ``state key``::
|
||||||
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
|
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
|
||||||
|
|
||||||
The ``state_key`` is often used to store state about individual users, by using the user ID as the
|
The ``state_key`` is often used to store state about individual users, by using the user ID as the
|
||||||
value. For example::
|
``state_key`` value. For example::
|
||||||
|
|
||||||
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
|
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
|
||||||
{ "animal" : "cat", "reason": "fluffy" }
|
{ "animal" : "cat", "reason": "fluffy" }
|
||||||
|
@ -471,7 +471,8 @@ Syncing rooms
|
||||||
-------------
|
-------------
|
||||||
When a client logs in, they may have a list of rooms which they have already joined. These rooms
|
When a client logs in, they may have a list of rooms which they have already joined. These rooms
|
||||||
may also have a list of events associated with them. The purpose of 'syncing' is to present the
|
may also have a list of events associated with them. The purpose of 'syncing' is to present the
|
||||||
current room and event information in a convenient, compact manner. There are two APIs provided:
|
current room and event information in a convenient, compact manner. The events returned are not
|
||||||
|
limited to room events; presence events will also be returned. There are two APIs provided:
|
||||||
|
|
||||||
- ``/initialSync`` : A global sync which will present room and event information for all rooms
|
- ``/initialSync`` : A global sync which will present room and event information for all rooms
|
||||||
the user has joined.
|
the user has joined.
|
||||||
|
@ -482,10 +483,40 @@ current room and event information in a convenient, compact manner. There are tw
|
||||||
- TODO: JSON response format for both types
|
- TODO: JSON response format for both types
|
||||||
- TODO: when would you use global? when would you use scoped?
|
- TODO: when would you use global? when would you use scoped?
|
||||||
|
|
||||||
Getting grouped state events for a room
|
Getting events for a room
|
||||||
---------------------------------------
|
-------------------------
|
||||||
- ``/members`` and ``/messages`` and the event types they return. Spec JSON response format.
|
There are several APIs provided to ``GET`` events for a room:
|
||||||
- ``/state`` and it returns ALL THE THINGS.
|
|
||||||
|
``/rooms/<room id>/state/<event type>/<state key>``
|
||||||
|
Description:
|
||||||
|
Get the state event identified.
|
||||||
|
Response format:
|
||||||
|
A JSON object representing the state event **content**.
|
||||||
|
Example:
|
||||||
|
``/rooms/!room:domain.com/state/m.room.name`` returns ``{ "name": "Room name" }``
|
||||||
|
|
||||||
|
``/rooms/<room id>/state``
|
||||||
|
Description:
|
||||||
|
Get all state events for a room.
|
||||||
|
Response format:
|
||||||
|
``[ { state event }, { state event }, ... ]``
|
||||||
|
Example:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
||||||
|
``/rooms/<room id>/members``
|
||||||
|
Description:
|
||||||
|
Get all ``m.room.member`` state events.
|
||||||
|
Response format:
|
||||||
|
``{ "start": "token", "end": "token", "chunk": [ { m.room.member event }, ... ] }``
|
||||||
|
Example:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- ``/rooms/<room id>/messages`` : Get all ``m.room.message`` events.
|
||||||
|
- ``/rooms/<room id>/initialSync`` : Get all relevant events for a room.
|
||||||
|
|
||||||
|
|
||||||
Room Events
|
Room Events
|
||||||
===========
|
===========
|
||||||
|
@ -493,25 +524,110 @@ Room Events
|
||||||
This specification outlines several standard event types, all of which are
|
This specification outlines several standard event types, all of which are
|
||||||
prefixed with ``m.``
|
prefixed with ``m.``
|
||||||
|
|
||||||
State messages
|
``m.room.name``
|
||||||
--------------
|
Summary:
|
||||||
- m.room.name
|
Set the human-readable name for the room.
|
||||||
- m.room.topic
|
Type:
|
||||||
- m.room.member
|
State event
|
||||||
- m.room.config
|
JSON format:
|
||||||
- m.room.invite_join
|
``{ "name" : "string" }``
|
||||||
|
Example:
|
||||||
|
``{ "name" : "My Room" }``
|
||||||
|
Description:
|
||||||
|
A room has an opaque room ID which is not human-friendly to read. A room alias is
|
||||||
|
human-friendly, but not all rooms have room aliases. The room name is a human-friendly
|
||||||
|
string designed to be displayed to the end-user. The room name is not *unique*, as
|
||||||
|
multiple rooms can have the same room name set. The room name can also be set when
|
||||||
|
creating a room using ``/createRoom`` with the ``name`` key.
|
||||||
|
|
||||||
What are they, when are they used, what do they contain, how should they be used.
|
``m.room.topic``
|
||||||
Link back to explanatory sections (e.g. invite/join/leave sections for m.room.member)
|
Summary:
|
||||||
|
Set a topic for the room.
|
||||||
|
Type:
|
||||||
|
State event
|
||||||
|
JSON format:
|
||||||
|
``{ "topic" : "string" }``
|
||||||
|
Example:
|
||||||
|
``{ "topic" : "Welcome to the real world." }``
|
||||||
|
Description:
|
||||||
|
A topic is a short message detailing what is currently being discussed in the room.
|
||||||
|
It can also be used as a way to display extra information about the room, which may
|
||||||
|
not be suitable for the room name.
|
||||||
|
|
||||||
|
``m.room.member``
|
||||||
|
Summary:
|
||||||
|
The current membership state of a user in the room.
|
||||||
|
Type:
|
||||||
|
State event
|
||||||
|
JSON format:
|
||||||
|
``{ "membership" : "enum[ invite|join|leave|ban ]" }``
|
||||||
|
Example:
|
||||||
|
``{ "membership" : "join" }``
|
||||||
|
Description:
|
||||||
|
Adjusts the membership state for a user in a room. It is preferable to use the
|
||||||
|
membership APIs (``/rooms/<room id>/invite`` etc) when performing membership actions
|
||||||
|
rather than adjusting the state directly as there are a restricted set of valid
|
||||||
|
transformations. For example, user A cannot force user B to join a room, and trying
|
||||||
|
to force this state change directly will fail. See the "Rooms" section for how to
|
||||||
|
use the membership APIs.
|
||||||
|
|
||||||
|
``m.room.config``
|
||||||
|
Summary:
|
||||||
|
The room config.
|
||||||
|
Type:
|
||||||
|
State event
|
||||||
|
JSON format:
|
||||||
|
TODO
|
||||||
|
Example:
|
||||||
|
TODO
|
||||||
|
Description:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
``m.room.invite_join``
|
||||||
|
Summary:
|
||||||
|
TODO.
|
||||||
|
Type:
|
||||||
|
State event
|
||||||
|
JSON format:
|
||||||
|
TODO
|
||||||
|
Example:
|
||||||
|
TODO
|
||||||
|
Description:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
``m.room.message``
|
||||||
|
Summary:
|
||||||
|
A message.
|
||||||
|
Type:
|
||||||
|
Non-state event
|
||||||
|
JSON format:
|
||||||
|
``{ "msgtype": "string" }``
|
||||||
|
Example:
|
||||||
|
``{ "msgtype": "m.text", "body": "Testing" }``
|
||||||
|
Description:
|
||||||
|
This event is used when sending messages in a room. Messages are not limited to be text.
|
||||||
|
The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc.
|
||||||
|
Whilst not required, the ``body`` key SHOULD be used with every kind of ``msgtype`` as
|
||||||
|
a fallback mechanism when a client cannot render the message. For more information on
|
||||||
|
the types of messages which can be sent, see "m.room.message msgtypes".
|
||||||
|
|
||||||
|
``m.room.message.feedback``
|
||||||
|
Summary:
|
||||||
|
A receipt for a message.
|
||||||
|
Type:
|
||||||
|
Non-state event
|
||||||
|
JSON format:
|
||||||
|
``{ "type": "enum [ delivered|read ]", "target_event_id": "string" }``
|
||||||
|
Example:
|
||||||
|
``{ "type": "delivered", "target_event_id": "e3b2icys" }``
|
||||||
|
Description:
|
||||||
|
Feedback events are events sent to acknowledge a message in some way. There are two
|
||||||
|
supported acknowledgements: ``delivered`` (sent when the event has been received) and
|
||||||
|
``read`` (sent when the event has been observed by the end-user). The ``target_event_id``
|
||||||
|
should reference the ``m.room.message`` event being acknowledged.
|
||||||
|
|
||||||
Non-state messages
|
|
||||||
------------------
|
|
||||||
- m.room.message
|
|
||||||
- m.room.message.feedback (and compressed format)
|
|
||||||
- voip?
|
- voip?
|
||||||
|
|
||||||
What are they, when are they used, what do they contain, how should they be used
|
|
||||||
|
|
||||||
m.room.message msgtypes
|
m.room.message msgtypes
|
||||||
-----------------------
|
-----------------------
|
||||||
Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type of
|
Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type of
|
||||||
|
@ -636,6 +752,14 @@ client devices they have connected. The home server should synchronise this
|
||||||
status choice among multiple devices to ensure the user gets a consistent
|
status choice among multiple devices to ensure the user gets a consistent
|
||||||
experience.
|
experience.
|
||||||
|
|
||||||
|
In addition, the server maintains a timestamp of the last time it saw an active
|
||||||
|
action from the user; either sending a message to a room, or changing presence
|
||||||
|
state from a lower to a higher level of availability (thus: changing state from
|
||||||
|
``unavailable`` to ``online`` will count as an action for being active, whereas
|
||||||
|
in the other direction will not). This timestamp is presented via a key called
|
||||||
|
``last_active_ago``, which gives the relative number of miliseconds since the
|
||||||
|
message is generated/emitted, that the user was last seen active.
|
||||||
|
|
||||||
Idle Time
|
Idle Time
|
||||||
---------
|
---------
|
||||||
As well as the basic ``presence`` field, the presence information can also show
|
As well as the basic ``presence`` field, the presence information can also show
|
||||||
|
|
|
@ -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', 'matrixService', 'mPresence', 'eventStreamService',
|
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService', 'matrixPhoneService',
|
||||||
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService) {
|
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService, matrixPhoneService) {
|
||||||
|
|
||||||
// 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();
|
||||||
|
@ -37,7 +37,11 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
mPresence.start();
|
mPresence.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.user_id;
|
||||||
|
var config = matrixService.config();
|
||||||
|
if (config) {
|
||||||
$scope.user_id = matrixService.config().user_id;
|
$scope.user_id = matrixService.config().user_id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a given page.
|
* Open a given page.
|
||||||
|
@ -85,6 +89,26 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
$scope.user_id = matrixService.config().user_id;
|
$scope.user_id = matrixService.config().user_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) {
|
||||||
|
console.trace("incoming call");
|
||||||
|
call.onError = $scope.onCallError;
|
||||||
|
call.onHangup = $scope.onCallHangup;
|
||||||
|
$rootScope.currentCall = call;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.answerCall = function() {
|
||||||
|
$scope.currentCall.answer();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hangupCall = function() {
|
||||||
|
$scope.currentCall.hangup();
|
||||||
|
$scope.currentCall = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
$rootScope.onCallError = function(errStr) {
|
||||||
|
$scope.feedback = errStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootScope.onCallHangup = function() {
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ angular.module('matrixWebClient')
|
||||||
});
|
});
|
||||||
|
|
||||||
filtered.sort(function (a, b) {
|
filtered.sort(function (a, b) {
|
||||||
return ((a["mtime_age"] || 10e10) > (b["mtime_age"] || 10e10) ? 1 : -1);
|
return ((a["last_active_ago"] || 10e10) > (b["last_active_ago"] || 10e10) ? 1 : -1);
|
||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
};
|
};
|
||||||
|
@ -79,4 +79,43 @@ angular.module('matrixWebClient')
|
||||||
return function(text) {
|
return function(text) {
|
||||||
return $sce.trustAsHtml(text);
|
return $sce.trustAsHtml(text);
|
||||||
};
|
};
|
||||||
|
}])
|
||||||
|
|
||||||
|
// Compute the room name according to information we have
|
||||||
|
.filter('roomName', ['$rootScope', 'matrixService', function($rootScope, matrixService) {
|
||||||
|
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);
|
||||||
|
if (alias) {
|
||||||
|
roomName = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (undefined === roomName) {
|
||||||
|
// Else, build the name from its users
|
||||||
|
var room = $rootScope.events.rooms[room_id];
|
||||||
|
if (room) {
|
||||||
|
if (room.members) {
|
||||||
|
// Limit the room renaming to 1:1 room
|
||||||
|
if (2 === Object.keys(room.members).length) {
|
||||||
|
for (var i in room.members) {
|
||||||
|
var member = room.members[i];
|
||||||
|
if (member.user_id !== matrixService.config().user_id) {
|
||||||
|
roomName = member.content.displayname ? member.content.displayname : member.user_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (undefined === roomName) {
|
||||||
|
// By default, use the room ID
|
||||||
|
roomName = room_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomName;
|
||||||
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -43,6 +43,10 @@ a:active { color: #000; }
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#callBar {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
#headerContent {
|
#headerContent {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
|
|
|
@ -44,6 +44,19 @@
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<!-- Do not show buttons on the login page -->
|
<!-- Do not show buttons on the login page -->
|
||||||
<div id="headerContent" ng-hide="'/login' == location || '/register' == location">
|
<div id="headerContent" ng-hide="'/login' == location || '/register' == location">
|
||||||
|
<div id="callBar">
|
||||||
|
<div ng-show="currentCall.state == 'ringing'">
|
||||||
|
Incoming call from {{ currentCall.user_id }}
|
||||||
|
<button ng-click="answerCall()">Answer</button>
|
||||||
|
<button ng-click="hangupCall()">Reject</button>
|
||||||
|
</div>
|
||||||
|
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
|
||||||
|
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
|
||||||
|
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
|
||||||
|
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
|
||||||
|
<span ng-show="currentCall.state == 'ended'">Call Ended</span>
|
||||||
|
<span style="display: none; ">{{ currentCall.state }}</span>
|
||||||
|
</div>
|
||||||
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
|
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
|
||||||
|
|
||||||
<button ng-click='goToPage("/")'>Home</button>
|
<button ng-click='goToPage("/")'>Home</button>
|
||||||
|
|
|
@ -33,8 +33,7 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
||||||
console.log("Invited to room " + event.room_id);
|
console.log("Invited to room " + event.room_id);
|
||||||
// FIXME push membership to top level key to match /im/sync
|
// FIXME push membership to top level key to match /im/sync
|
||||||
event.membership = event.content.membership;
|
event.membership = event.content.membership;
|
||||||
// FIXME bodge a nicer name than the room ID for this invite.
|
|
||||||
event.room_display_name = event.user_id + "'s room";
|
|
||||||
$scope.rooms[event.room_id] = event;
|
$scope.rooms[event.room_id] = event;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -88,7 +87,9 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.onInit = function() {
|
$scope.onInit = function() {
|
||||||
|
eventHandlerService.waitForInitialSyncCompletion().then(function() {
|
||||||
refresh();
|
refresh();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="recentsRoomName">
|
<td class="recentsRoomName">
|
||||||
{{ room.room_display_name }}
|
{{ room.room_id | roomName }}
|
||||||
</td>
|
</td>
|
||||||
<td class="recentsRoomSummaryTS">
|
<td class="recentsRoomSummaryTS">
|
||||||
{{ (room.lastMsg.ts) | date:'MMM d HH:mm' }}
|
{{ (room.lastMsg.ts) | date:'MMM d HH:mm' }}
|
||||||
|
|
|
@ -82,13 +82,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
updatePresence(event);
|
updatePresence(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
$rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) {
|
|
||||||
console.trace("incoming call");
|
|
||||||
call.onError = $scope.onCallError;
|
|
||||||
call.onHangup = $scope.onCallHangup;
|
|
||||||
$scope.currentCall = call;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.memberCount = function() {
|
$scope.memberCount = function() {
|
||||||
return Object.keys($scope.members).length;
|
return Object.keys($scope.members).length;
|
||||||
};
|
};
|
||||||
|
@ -100,15 +93,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.answerCall = function() {
|
|
||||||
$scope.currentCall.answer();
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.hangupCall = function() {
|
|
||||||
$scope.currentCall.hangup();
|
|
||||||
$scope.currentCall = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
var paginate = function(numItems) {
|
var paginate = function(numItems) {
|
||||||
// console.log("paginate " + numItems);
|
// console.log("paginate " + numItems);
|
||||||
if ($scope.state.paginating || !$scope.room_id) {
|
if ($scope.state.paginating || !$scope.room_id) {
|
||||||
|
@ -181,11 +165,11 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
var isNewMember = !(target_user_id in $scope.members);
|
var isNewMember = !(target_user_id in $scope.members);
|
||||||
if (isNewMember) {
|
if (isNewMember) {
|
||||||
// FIXME: why are we copying these fields around inside chunk?
|
// FIXME: why are we copying these fields around inside chunk?
|
||||||
if ("state" in chunk.content) {
|
if ("presence" in chunk.content) {
|
||||||
chunk.presenceState = chunk.content.state; // why is this renamed?
|
chunk.presence = chunk.content.presence;
|
||||||
}
|
}
|
||||||
if ("mtime_age" in chunk.content) {
|
if ("last_active_ago" in chunk.content) {
|
||||||
chunk.mtime_age = chunk.content.mtime_age;
|
chunk.last_active_ago = chunk.content.last_active_ago;
|
||||||
}
|
}
|
||||||
if ("displayname" in chunk.content) {
|
if ("displayname" in chunk.content) {
|
||||||
chunk.displayname = chunk.content.displayname;
|
chunk.displayname = chunk.content.displayname;
|
||||||
|
@ -204,11 +188,11 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
// selectively update membership and presence else it will nuke the picture and displayname too :/
|
// selectively update membership and presence else it will nuke the picture and displayname too :/
|
||||||
var member = $scope.members[target_user_id];
|
var member = $scope.members[target_user_id];
|
||||||
member.membership = chunk.content.membership;
|
member.membership = chunk.content.membership;
|
||||||
if ("state" in chunk.content) {
|
if ("presence" in chunk.content) {
|
||||||
member.presenceState = chunk.content.state;
|
member.presence = chunk.content.presence;
|
||||||
}
|
}
|
||||||
if ("mtime_age" in chunk.content) {
|
if ("last_active_ago" in chunk.content) {
|
||||||
member.mtime_age = chunk.content.mtime_age;
|
member.last_active_ago = chunk.content.last_active_ago;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -227,13 +211,12 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
var member = $scope.members[chunk.content.user_id];
|
var member = $scope.members[chunk.content.user_id];
|
||||||
|
|
||||||
// XXX: why not just pass the chunk straight through?
|
// XXX: why not just pass the chunk straight through?
|
||||||
if ("state" in chunk.content) {
|
if ("presence" in chunk.content) {
|
||||||
member.presenceState = chunk.content.state;
|
member.presence = chunk.content.presence;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("mtime_age" in chunk.content) {
|
if ("last_active_ago" in chunk.content) {
|
||||||
// FIXME: should probably keep updating mtime_age in realtime like FB does
|
member.last_active_ago = chunk.content.last_active_ago;
|
||||||
member.mtime_age = chunk.content.mtime_age;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this may also contain a new display name or avatar url, so check.
|
// this may also contain a new display name or avatar url, so check.
|
||||||
|
@ -478,16 +461,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
|
|
||||||
$scope.startVoiceCall = function() {
|
$scope.startVoiceCall = function() {
|
||||||
var call = new MatrixCall($scope.room_id);
|
var call = new MatrixCall($scope.room_id);
|
||||||
call.onError = $scope.onCallError;
|
call.onError = $rootScope.onCallError;
|
||||||
call.onHangup = $scope.onCallHangup;
|
call.onHangup = $rootScope.onCallHangup;
|
||||||
call.placeCall();
|
call.placeCall();
|
||||||
$scope.currentCall = call;
|
$rootScope.currentCall = call;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.onCallError = function(errStr) {
|
|
||||||
$scope.feedback = errStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.onCallHangup = function() {
|
|
||||||
}
|
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div id="roomHeader">
|
<div id="roomHeader">
|
||||||
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||||
<div id="roomName">
|
<div id="roomName">
|
||||||
{{ room_alias || room_id }}
|
{{ room_id | roomName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@
|
||||||
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
||||||
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="userPresence" ng-class="(member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
<td class="userPresence" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
||||||
<span ng-show="member.mtime_age">{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>ago</span>
|
<span ng-show="member.last_active_ago">{{ member.last_active_ago + (now - member.last_updated) | duration }}<br/>ago</span>
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,18 +100,7 @@
|
||||||
<button ng-click="inviteUser(userIDToInvite)">Invite</button>
|
<button ng-click="inviteUser(userIDToInvite)">Invite</button>
|
||||||
</span>
|
</span>
|
||||||
<button ng-click="leaveRoom()">Leave</button>
|
<button ng-click="leaveRoom()">Leave</button>
|
||||||
<button ng-click="startVoiceCall()" ng-show="currentCall == undefined && memberCount() == 2">Voice Call</button>
|
<button ng-click="startVoiceCall()" ng-show="(currentCall == undefined || currentCall.state == 'ended') && memberCount() == 2">Voice Call</button>
|
||||||
<div ng-show="currentCall.state == 'ringing'">
|
|
||||||
Incoming call from {{ currentCall.user_id }}
|
|
||||||
<button ng-click="answerCall()">Answer</button>
|
|
||||||
<button ng-click="hangupCall()">Reject</button>
|
|
||||||
</div>
|
|
||||||
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button>
|
|
||||||
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
|
|
||||||
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
|
|
||||||
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
|
|
||||||
<span ng-show="currentCall.state == 'ended'">Call Ended</span>
|
|
||||||
<span style="display: none; ">{{ currentCall.state }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ feedback }}
|
{{ feedback }}
|
||||||
|
|
Loading…
Reference in a new issue