mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2024-10-01 11:49:51 -04:00
Merge branch 'develop' of github.com:matrix-org/synapse into stream_refactor
This commit is contained in:
commit
ff3709e577
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
""" This module contains base REST classes for constructing REST servlets. """
|
""" This module contains base REST classes for constructing REST servlets. """
|
||||||
from synapse.api.urls import CLIENT_PREFIX
|
from synapse.api.urls import CLIENT_PREFIX
|
||||||
|
from synapse.rest.transactions import HttpTransactionStore
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ class RestServlet(object):
|
|||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
self.event_factory = hs.get_event_factory()
|
self.event_factory = hs.get_event_factory()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
self.txns = HttpTransactionStore()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
""" Register this servlet with the given HTTP server. """
|
""" Register this servlet with the given HTTP server. """
|
||||||
|
@ -366,6 +366,51 @@ class RoomTriggerBackfill(RestServlet):
|
|||||||
defer.returnValue((200, res))
|
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):
|
def _parse_json(request):
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
@ -377,6 +422,30 @@ def _parse_json(request):
|
|||||||
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
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):
|
def register_servlets(hs, http_server):
|
||||||
RoomStateEventRestServlet(hs).register(http_server)
|
RoomStateEventRestServlet(hs).register(http_server)
|
||||||
MessageRestServlet(hs).register(http_server)
|
MessageRestServlet(hs).register(http_server)
|
||||||
@ -386,3 +455,4 @@ def register_servlets(hs, http_server):
|
|||||||
RoomMessageListRestServlet(hs).register(http_server)
|
RoomMessageListRestServlet(hs).register(http_server)
|
||||||
JoinRoomAliasServlet(hs).register(http_server)
|
JoinRoomAliasServlet(hs).register(http_server)
|
||||||
RoomTriggerBackfill(hs).register(http_server)
|
RoomTriggerBackfill(hs).register(http_server)
|
||||||
|
RoomMembershipRestServlet(hs).register(http_server)
|
||||||
|
96
synapse/rest/transactions.py
Normal file
96
synapse/rest/transactions.py
Normal file
@ -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';
|
'use strict';
|
||||||
|
|
||||||
angular.module('MatrixWebClientController', ['matrixService'])
|
angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService'])
|
||||||
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'eventStreamService',
|
.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService',
|
||||||
function($scope, $location, $rootScope, matrixService, eventStreamService) {
|
function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService) {
|
||||||
|
|
||||||
// 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();
|
||||||
@ -34,6 +34,7 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||||||
|
|
||||||
if (matrixService.isUserLoggedIn()) {
|
if (matrixService.isUserLoggedIn()) {
|
||||||
// eventStreamService.resume();
|
// eventStreamService.resume();
|
||||||
|
mPresence.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.go = function(url) {
|
$scope.go = function(url) {
|
||||||
@ -42,9 +43,13 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||||||
|
|
||||||
// Logs the user out
|
// Logs the user out
|
||||||
$scope.logout = function() {
|
$scope.logout = function() {
|
||||||
|
|
||||||
// kill the event stream
|
// kill the event stream
|
||||||
eventStreamService.stop();
|
eventStreamService.stop();
|
||||||
|
|
||||||
|
// Do not update presence anymore
|
||||||
|
mPresence.stop();
|
||||||
|
|
||||||
// Clean permanent data
|
// Clean permanent data
|
||||||
matrixService.setConfig({});
|
matrixService.setConfig({});
|
||||||
matrixService.saveConfig();
|
matrixService.saveConfig();
|
||||||
@ -67,7 +72,6 @@ angular.module('MatrixWebClientController', ['matrixService'])
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ angular.module('matrixService', [])
|
|||||||
|
|
||||||
// Joins a room
|
// Joins a room
|
||||||
join: function(room_id) {
|
join: function(room_id) {
|
||||||
return this.membershipChange(room_id, config.user_id, "join");
|
return this.membershipChange(room_id, undefined, "join");
|
||||||
},
|
},
|
||||||
|
|
||||||
joinAlias: function(room_alias) {
|
joinAlias: function(room_alias) {
|
||||||
@ -134,18 +134,22 @@ angular.module('matrixService', [])
|
|||||||
|
|
||||||
// Leaves a room
|
// Leaves a room
|
||||||
leave: function(room_id) {
|
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) {
|
membershipChange: function(room_id, user_id, membershipValue) {
|
||||||
// The REST path spec
|
// 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("$room_id", encodeURIComponent(room_id));
|
||||||
path = path.replace("$user_id", encodeURIComponent(user_id));
|
path = path.replace("$membership", encodeURIComponent(membershipValue));
|
||||||
|
|
||||||
return doRequest("PUT", path, undefined, {
|
var data = {};
|
||||||
membership: membershipValue
|
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
|
// Retrieves the room ID corresponding to a room alias
|
||||||
@ -354,6 +358,23 @@ angular.module('matrixService', [])
|
|||||||
return false;
|
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 ******/
|
/****** Permanent storage of user information ******/
|
||||||
|
|
||||||
|
113
webclient/components/matrix/presence-service.js
Normal file
113
webclient/components/matrix/presence-service.js
Normal file
@ -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/matrix-service.js"></script>
|
||||||
<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/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>
|
||||||
<script src="components/utilities/utilities-service.js"></script>
|
<script src="components/utilities/utilities-service.js"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user