Merge branch 'develop' of github.com:matrix-org/synapse into stream_refactor
commit
ff3709e577
|
@ -15,6 +15,7 @@
|
|||
|
||||
""" This module contains base REST classes for constructing REST servlets. """
|
||||
from synapse.api.urls import CLIENT_PREFIX
|
||||
from synapse.rest.transactions import HttpTransactionStore
|
||||
import re
|
||||
|
||||
|
||||
|
@ -59,6 +60,7 @@ class RestServlet(object):
|
|||
self.handlers = hs.get_handlers()
|
||||
self.event_factory = hs.get_event_factory()
|
||||
self.auth = hs.get_auth()
|
||||
self.txns = HttpTransactionStore()
|
||||
|
||||
def register(self, http_server):
|
||||
""" Register this servlet with the given HTTP server. """
|
||||
|
|
|
@ -366,6 +366,51 @@ class RoomTriggerBackfill(RestServlet):
|
|||
defer.returnValue((200, res))
|
||||
|
||||
|
||||
class RoomMembershipRestServlet(RestServlet):
|
||||
|
||||
def register(self, http_server):
|
||||
# /rooms/$roomid/[invite|join|leave]
|
||||
PATTERN = ("/rooms/(?P<room_id>[^/]*)/" +
|
||||
"(?P<membership_action>join|invite|leave)")
|
||||
register_txn_path(self, PATTERN, http_server)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_POST(self, request, room_id, membership_action):
|
||||
user = yield self.auth.get_user_by_req(request)
|
||||
|
||||
content = _parse_json(request)
|
||||
|
||||
# target user is you unless it is an invite
|
||||
state_key = user.to_string()
|
||||
if membership_action == "invite":
|
||||
if "user_id" not in content:
|
||||
raise SynapseError(400, "Missing user_id key.")
|
||||
state_key = content["user_id"]
|
||||
|
||||
event = self.event_factory.create_event(
|
||||
etype=RoomMemberEvent.TYPE,
|
||||
content={"membership": unicode(membership_action)},
|
||||
room_id=urllib.unquote(room_id),
|
||||
user_id=user.to_string(),
|
||||
state_key=state_key
|
||||
)
|
||||
handler = self.handlers.room_member_handler
|
||||
yield handler.change_membership(event)
|
||||
defer.returnValue((200, ""))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_PUT(self, request, room_id, membership_action, txn_id):
|
||||
try:
|
||||
defer.returnValue(self.txns.get_client_transaction(request, txn_id))
|
||||
except:
|
||||
pass
|
||||
|
||||
response = yield self.on_POST(request, room_id, membership_action)
|
||||
|
||||
self.txns.store_client_transaction(request, txn_id, response)
|
||||
defer.returnValue(response)
|
||||
|
||||
|
||||
def _parse_json(request):
|
||||
try:
|
||||
content = json.loads(request.content.read())
|
||||
|
@ -377,6 +422,30 @@ def _parse_json(request):
|
|||
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||
|
||||
|
||||
def register_txn_path(servlet, regex_string, http_server):
|
||||
"""Registers a transaction-based path.
|
||||
|
||||
This registers two paths:
|
||||
PUT regex_string/$txnid
|
||||
POST regex_string
|
||||
|
||||
Args:
|
||||
regex_string (str): The regex string to register. Must NOT have a
|
||||
trailing $ as this string will be appended to.
|
||||
http_server : The http_server to register paths with.
|
||||
"""
|
||||
http_server.register_path(
|
||||
"POST",
|
||||
client_path_pattern(regex_string + "$"),
|
||||
servlet.on_POST
|
||||
)
|
||||
http_server.register_path(
|
||||
"PUT",
|
||||
client_path_pattern(regex_string + "/(?P<txn_id>[^/]*)$"),
|
||||
servlet.on_PUT
|
||||
)
|
||||
|
||||
|
||||
def register_servlets(hs, http_server):
|
||||
RoomStateEventRestServlet(hs).register(http_server)
|
||||
MessageRestServlet(hs).register(http_server)
|
||||
|
@ -386,3 +455,4 @@ def register_servlets(hs, http_server):
|
|||
RoomMessageListRestServlet(hs).register(http_server)
|
||||
JoinRoomAliasServlet(hs).register(http_server)
|
||||
RoomTriggerBackfill(hs).register(http_server)
|
||||
RoomMembershipRestServlet(hs).register(http_server)
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""This module contains logic for storing HTTP PUT transactions. This is used
|
||||
to ensure idempotency when performing PUTs using the REST API."""
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HttpTransactionStore(object):
|
||||
|
||||
def __init__(self):
|
||||
# { key : (txn_id, response) }
|
||||
self.transactions = {}
|
||||
|
||||
def get_response(self, key, txn_id):
|
||||
"""Retrieve a response for this request.
|
||||
|
||||
Args:
|
||||
key (str): A transaction-independent key for this request. Typically
|
||||
this is a combination of the path (without the transaction id) and
|
||||
the user's access token.
|
||||
txn_id (str): The transaction ID for this request
|
||||
Returns:
|
||||
A tuple of (HTTP response code, response content) or None.
|
||||
"""
|
||||
try:
|
||||
logger.debug("get_response Key: %s TxnId: %s", key, txn_id)
|
||||
(last_txn_id, response) = self.transactions[key]
|
||||
if txn_id == last_txn_id:
|
||||
logger.info("get_response: Returning a response for %s", key)
|
||||
return response
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def store_response(self, key, txn_id, response):
|
||||
"""Stores an HTTP response tuple.
|
||||
|
||||
Args:
|
||||
key (str): A transaction-independent key for this request. Typically
|
||||
this is a combination of the path (without the transaction id) and
|
||||
the user's access token.
|
||||
txn_id (str): The transaction ID for this request.
|
||||
response (tuple): A tuple of (HTTP response code, response content)
|
||||
"""
|
||||
logger.debug("store_response Key: %s TxnId: %s", key, txn_id)
|
||||
self.transactions[key] = (txn_id, response)
|
||||
|
||||
def store_client_transaction(self, request, txn_id, response):
|
||||
"""Stores the request/response pair of an HTTP transaction.
|
||||
|
||||
Args:
|
||||
request (twisted.web.http.Request): The twisted HTTP request. This
|
||||
request must have the transaction ID as the last path segment.
|
||||
response (tuple): A tuple of (response code, response dict)
|
||||
txn_id (str): The transaction ID for this request.
|
||||
"""
|
||||
self.store_response(self._get_key(request), txn_id, response)
|
||||
|
||||
def get_client_transaction(self, request, txn_id):
|
||||
"""Retrieves a stored response if there was one.
|
||||
|
||||
Args:
|
||||
request (twisted.web.http.Request): The twisted HTTP request. This
|
||||
request must have the transaction ID as the last path segment.
|
||||
txn_id (str): The transaction ID for this request.
|
||||
Returns:
|
||||
The response tuple.
|
||||
Raises:
|
||||
KeyError if the transaction was not found.
|
||||
"""
|
||||
response = self.get_response(self._get_key(request), txn_id)
|
||||
if response is None:
|
||||
raise KeyError("Transaction not found.")
|
||||
return response
|
||||
|
||||
def _get_key(self, request):
|
||||
token = request.args["access_token"][0]
|
||||
path_without_txn_id = request.path.rsplit("/", 1)[0]
|
||||
return path_without_txn_id + "/" + token
|
||||
|
||||
|
|
@ -20,9 +20,9 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
angular.module('MatrixWebClientController', ['matrixService'])
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'eventStreamService',
|
||||
function($scope, $location, $rootScope, matrixService, eventStreamService) {
|
||||
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService',
|
||||
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService) {
|
||||
|
||||
// Check current URL to avoid to display the logout button on the login page
|
||||
$scope.location = $location.path();
|
||||
|
@ -34,6 +34,7 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||
|
||||
if (matrixService.isUserLoggedIn()) {
|
||||
// eventStreamService.resume();
|
||||
mPresence.start();
|
||||
}
|
||||
|
||||
$scope.go = function(url) {
|
||||
|
@ -42,9 +43,13 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||
|
||||
// Logs the user out
|
||||
$scope.logout = function() {
|
||||
|
||||
// kill the event stream
|
||||
eventStreamService.stop();
|
||||
|
||||
|
||||
// Do not update presence anymore
|
||||
mPresence.stop();
|
||||
|
||||
// Clean permanent data
|
||||
matrixService.setConfig({});
|
||||
matrixService.saveConfig();
|
||||
|
@ -67,7 +72,6 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
}]);
|
||||
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ angular.module('matrixService', [])
|
|||
|
||||
// Joins a room
|
||||
join: function(room_id) {
|
||||
return this.membershipChange(room_id, config.user_id, "join");
|
||||
return this.membershipChange(room_id, undefined, "join");
|
||||
},
|
||||
|
||||
joinAlias: function(room_alias) {
|
||||
|
@ -134,18 +134,22 @@ angular.module('matrixService', [])
|
|||
|
||||
// Leaves a room
|
||||
leave: function(room_id) {
|
||||
return this.membershipChange(room_id, config.user_id, "leave");
|
||||
return this.membershipChange(room_id, undefined, "leave");
|
||||
},
|
||||
|
||||
membershipChange: function(room_id, user_id, membershipValue) {
|
||||
// The REST path spec
|
||||
var path = "/rooms/$room_id/state/m.room.member/$user_id";
|
||||
var path = "/rooms/$room_id/$membership";
|
||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||
path = path.replace("$user_id", encodeURIComponent(user_id));
|
||||
path = path.replace("$membership", encodeURIComponent(membershipValue));
|
||||
|
||||
return doRequest("PUT", path, undefined, {
|
||||
membership: membershipValue
|
||||
});
|
||||
var data = {};
|
||||
if (user_id !== undefined) {
|
||||
data = { user_id: user_id };
|
||||
}
|
||||
|
||||
// TODO: Use PUT with transaction IDs
|
||||
return doRequest("POST", path, undefined, data);
|
||||
},
|
||||
|
||||
// Retrieves the room ID corresponding to a room alias
|
||||
|
@ -354,6 +358,23 @@ angular.module('matrixService', [])
|
|||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Enum of presence state
|
||||
presence: {
|
||||
offline: "offline",
|
||||
unavailable: "unavailable",
|
||||
online: "online",
|
||||
free_for_chat: "free_for_chat"
|
||||
},
|
||||
|
||||
// Set the logged in user presence state
|
||||
setUserPresence: function(presence) {
|
||||
var path = "/presence/$user_id/status";
|
||||
path = path.replace("$user_id", config.user_id);
|
||||
return doRequest("PUT", path, undefined, {
|
||||
state: presence
|
||||
});
|
||||
},
|
||||
|
||||
/****** Permanent storage of user information ******/
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2014 matrix.org
|
||||
|
||||
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 tracks user activity on the page to determine his presence state.
|
||||
* Any state change will be sent to the Home Server.
|
||||
*/
|
||||
angular.module('mPresence', [])
|
||||
.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
|
||||
|
||||
// Time in ms after that a user is considered as offline/away
|
||||
var OFFLINE_TIME = 5 * 60000; // 5 mins
|
||||
|
||||
// The current presence state
|
||||
var state = undefined;
|
||||
|
||||
var self =this;
|
||||
var timer;
|
||||
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
* Any state change will be sent to the Home Server.
|
||||
*/
|
||||
this.start = function() {
|
||||
if (undefined === state) {
|
||||
// The user is online if he moves the mouser or press a key
|
||||
document.onmousemove = resetTimer;
|
||||
document.onkeypress = resetTimer;
|
||||
|
||||
resetTimer();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop tracking user activity
|
||||
*/
|
||||
this.stop = function() {
|
||||
if (timer) {
|
||||
$timeout.cancel(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
state = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current presence state.
|
||||
* @returns {matrixService.presence} the presence state
|
||||
*/
|
||||
this.getState = function() {
|
||||
return state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the presence state.
|
||||
* If the state has changed, the Home Server will be notified.
|
||||
* @param {matrixService.presence} newState the new presence state
|
||||
*/
|
||||
this.setState = function(newState) {
|
||||
if (newState !== state) {
|
||||
console.log("mPresence - New state: " + newState);
|
||||
|
||||
state = newState;
|
||||
|
||||
// Inform the HS on the new user state
|
||||
matrixService.setUserPresence(state).then(
|
||||
function() {
|
||||
|
||||
},
|
||||
function(error) {
|
||||
console.log("mPresence - Failed to send new presence state: " + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback called when the user made no action on the page for OFFLINE_TIME ms.
|
||||
* @private
|
||||
*/
|
||||
function onOfflineTimerFire() {
|
||||
self.setState(matrixService.presence.offline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called when the user made an action on the page
|
||||
* @private
|
||||
*/
|
||||
function resetTimer() {
|
||||
// User is still here
|
||||
self.setState(matrixService.presence.online);
|
||||
|
||||
// Re-arm the timer
|
||||
$timeout.cancel(timer);
|
||||
timer = $timeout(onOfflineTimerFire, OFFLINE_TIME);
|
||||
}
|
||||
|
||||
}]);
|
||||
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
<script src="components/matrix/matrix-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/presence-service.js"></script>
|
||||
<script src="components/fileInput/file-input-directive.js"></script>
|
||||
<script src="components/fileUpload/file-upload-service.js"></script>
|
||||
<script src="components/utilities/utilities-service.js"></script>
|
||||
|
|
Loading…
Reference in New Issue