forked from MirrorHub/synapse
Merge branch 'develop' into storage_transactions
This commit is contained in:
commit
1d95e78759
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,7 +10,7 @@ docs/build/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
cmdclient_config.json
|
cmdclient_config.json
|
||||||
homeserver.db
|
homeserver*.db
|
||||||
|
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov
|
htmlcov
|
||||||
|
|
|
@ -471,7 +471,7 @@ class SynapseCmd(cmd.Cmd):
|
||||||
room_name = args["vis"]
|
room_name = args["vis"]
|
||||||
body["room_alias_name"] = room_name
|
body["room_alias_name"] = room_name
|
||||||
|
|
||||||
reactor.callFromThread(self._run_and_pprint, "POST", "/rooms", body)
|
reactor.callFromThread(self._run_and_pprint, "POST", "/createRoom", body)
|
||||||
|
|
||||||
def do_raw(self, line):
|
def do_raw(self, line):
|
||||||
"""Directly send a JSON object: "raw <method> <path> <data> <notoken>"
|
"""Directly send a JSON object: "raw <method> <path> <data> <notoken>"
|
||||||
|
|
|
@ -21,6 +21,14 @@
|
||||||
{
|
{
|
||||||
"path": "/presence",
|
"path": "/presence",
|
||||||
"description": "Presence operations"
|
"description": "Presence operations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/events",
|
||||||
|
"description": "Event operations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/directory",
|
||||||
|
"description": "Directory operations"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"authorizations": {
|
"authorizations": {
|
||||||
|
|
83
docs/client-server/swagger_matrix/directory
Normal file
83
docs/client-server/swagger_matrix/directory
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"apiVersion": "1.0.0",
|
||||||
|
"swaggerVersion": "1.2",
|
||||||
|
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
||||||
|
"resourcePath": "/directory",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"apis": [
|
||||||
|
{
|
||||||
|
"path": "/directory/room/{roomAlias}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get the room ID corresponding to this room alias.",
|
||||||
|
"type": "DirectoryResponse",
|
||||||
|
"nickname": "get_room_id_for_alias",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomAlias",
|
||||||
|
"description": "The room alias.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Create a new mapping from room alias to room ID.",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "add_room_alias",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomAlias",
|
||||||
|
"description": "The room alias to set.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"description": "The room ID to set.",
|
||||||
|
"required": true,
|
||||||
|
"type": "RoomAliasRequest",
|
||||||
|
"paramType": "body"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"models": {
|
||||||
|
"DirectoryResponse": {
|
||||||
|
"id": "DirectoryResponse",
|
||||||
|
"properties": {
|
||||||
|
"room_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The fully-qualified room ID.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"servers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "string"
|
||||||
|
},
|
||||||
|
"description": "A list of servers that know about this room.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RoomAliasRequest": {
|
||||||
|
"id": "RoomAliasRequest",
|
||||||
|
"properties": {
|
||||||
|
"room_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The room ID to map the alias to.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,299 +1,246 @@
|
||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://petstore.swagger.wordnik.com/api",
|
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
||||||
"resourcePath": "/user",
|
"resourcePath": "/events",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
{
|
||||||
"path": "/user",
|
"path": "/events",
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"summary": "Create user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "Created user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "User",
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/logout",
|
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"summary": "Logs out current logged in user session",
|
"summary": "Listen on the event stream",
|
||||||
"notes": "",
|
"notes": "This can only be done by the logged in user. This will block until an event is received, or until the timeout is reached.",
|
||||||
"type": "void",
|
"type": "PaginationChunk",
|
||||||
"nickname": "logoutUser",
|
"nickname": "get_event_stream"
|
||||||
"authorizations": {},
|
|
||||||
"parameters": []
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
},
|
"parameters": [
|
||||||
{
|
|
||||||
"path": "/user/createWithArray",
|
|
||||||
"operations": [
|
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"name": "from",
|
||||||
"summary": "Creates list of users with given input array",
|
"description": "The token to stream from.",
|
||||||
"notes": "",
|
"required": false,
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUsersWithArrayInput",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "List of user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "User"
|
|
||||||
},
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/createWithList",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"summary": "Creates list of users with given list input",
|
|
||||||
"notes": "",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUsersWithListInput",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "List of user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "User"
|
|
||||||
},
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/{username}",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "PUT",
|
|
||||||
"summary": "Updated user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "updateUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "name that need to be deleted",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "Updated user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "User",
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"summary": "Delete user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "deleteUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "The name that needs to be deleted",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get user by user name",
|
|
||||||
"notes": "",
|
|
||||||
"type": "User",
|
|
||||||
"nickname": "getUserByName",
|
|
||||||
"authorizations": {},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "The name that needs to be fetched. Use user1 for testing.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/login",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Logs user into the system",
|
|
||||||
"notes": "",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nickname": "loginUser",
|
"paramType": "query"
|
||||||
"authorizations": {},
|
},
|
||||||
|
{
|
||||||
|
"name": "timeout",
|
||||||
|
"description": "The maximum time in milliseconds to wait for an event.",
|
||||||
|
"required": false,
|
||||||
|
"type": "integer",
|
||||||
|
"paramType": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responseMessages": [
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "Bad pagination token."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/events/{eventId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get information about a single event.",
|
||||||
|
"notes": "Get information about a single event.",
|
||||||
|
"type": "Event",
|
||||||
|
"nickname": "get_event",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"name": "eventId",
|
||||||
"description": "The user name for login",
|
"description": "The event ID to get.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "query"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"description": "The password for login in clear text",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "query"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responseMessages": [
|
"responseMessages": [
|
||||||
{
|
{
|
||||||
"code": 400,
|
"code": 404,
|
||||||
"message": "Invalid username and password combination"
|
"message": "Event not found."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/initialSync",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get this user's current state.",
|
||||||
|
"notes": "Get this user's current state.",
|
||||||
|
"type": "InitialSyncResponse",
|
||||||
|
"nickname": "initial_sync",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"description": "The maximum number of messages to return for each room.",
|
||||||
|
"type": "integer",
|
||||||
|
"paramType": "query",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/publicRooms",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get a list of publicly visible rooms.",
|
||||||
|
"type": "PublicRoomsPaginationChunk",
|
||||||
|
"nickname": "get_public_room_list"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"models": {
|
"models": {
|
||||||
"User": {
|
"PaginationChunk": {
|
||||||
"id": "User",
|
"id": "PaginationChunk",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"start": {
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"format": "int64"
|
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"firstName": {
|
"end": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"username": {
|
"chunk": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"description": "An array of events.",
|
||||||
|
"required": true,
|
||||||
|
"items": {
|
||||||
|
"$ref": "Event"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Event": {
|
||||||
|
"id": "Event",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "An ID which uniquely identifies this event.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"lastName": {
|
"room_id": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "The room in which this event occurred.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PublicRoomInfo": {
|
||||||
|
"id": "PublicRoomInfo",
|
||||||
|
"properties": {
|
||||||
|
"aliases": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of room aliases for this room.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"email": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "The name of the room, as given by the m.room.name state event."
|
||||||
},
|
},
|
||||||
"password": {
|
"room_id": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "The room ID for this public room.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"phone": {
|
"topic": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "The topic of this room, as given by the m.room.topic state event."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PublicRoomsPaginationChunk": {
|
||||||
|
"id": "PublicRoomsPaginationChunk",
|
||||||
|
"properties": {
|
||||||
|
"start": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"userStatus": {
|
"end": {
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"format": "int32",
|
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
|
||||||
"description": "User Status",
|
"required": true
|
||||||
"enum": [
|
},
|
||||||
"1-registered",
|
"chunk": {
|
||||||
"2-active",
|
"type": "array",
|
||||||
"3-closed"
|
"description": "A list of public room data.",
|
||||||
]
|
"required": true,
|
||||||
|
"items": {
|
||||||
|
"$ref": "PublicRoomInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"InitialSyncResponse": {
|
||||||
|
"id": "InitialSyncResponse",
|
||||||
|
"properties": {
|
||||||
|
"end": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A streaming token which can be used with /events to continue from this snapshot of data.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"presence": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of presence events.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "Event"
|
||||||
|
},
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"rooms": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of initial sync room data.",
|
||||||
|
"required": false,
|
||||||
|
"items": {
|
||||||
|
"$ref": "InitialSyncRoomData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"InitialSyncRoomData": {
|
||||||
|
"id": "InitialSyncRoomData",
|
||||||
|
"properties": {
|
||||||
|
"membership": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "This user's membership state in this room.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"room_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The ID of this room.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"type": "PaginationChunk",
|
||||||
|
"description": "The most recent messages for this room, governed by the limit parameter.",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of state events representing the current state of the room.",
|
||||||
|
"required": false,
|
||||||
|
"items": {
|
||||||
|
"$ref": "Event"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/presence_list/{userId}",
|
"path": "/presence/list/{userId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
|
|
@ -14,13 +14,103 @@
|
||||||
},
|
},
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/{userId}/{messageId}",
|
"path": "/rooms/{roomId}/send/{eventType}/{txnId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Send a generic non-state event to this room.",
|
||||||
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/{eventType}",
|
||||||
|
"type": "EventId",
|
||||||
|
"nickname": "send_non_state_event",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"description": "The event contents",
|
||||||
|
"required": true,
|
||||||
|
"type": "EventContent",
|
||||||
|
"paramType": "body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to send the message in.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventType",
|
||||||
|
"description": "The type of event to send.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "txnId",
|
||||||
|
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/state/{eventType}/{stateKey}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Send a generic state event to this room.",
|
||||||
|
"notes": "The state key can be omitted, such that you can PUT to /rooms/{roomId}/state/{eventType}. The state key defaults to a 0 length string in this case.",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "send_state_event",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"description": "The event contents",
|
||||||
|
"required": true,
|
||||||
|
"type": "EventContent",
|
||||||
|
"paramType": "body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to send the message in.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventType",
|
||||||
|
"description": "The type of event to send.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stateKey",
|
||||||
|
"description": "An identifier used to specify clobbering semantics. State events with the same (roomId, eventType, stateKey) will be replaced.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/send/m.room.message/{txnId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"summary": "Send a message in this room.",
|
"summary": "Send a message in this room.",
|
||||||
"notes": "Send a message in this room.",
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message",
|
||||||
"type": "void",
|
"type": "EventId",
|
||||||
"nickname": "send_message",
|
"nickname": "send_message",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -41,67 +131,18 @@
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "userId",
|
"name": "txnId",
|
||||||
"description": "The fully qualified message sender's user ID.",
|
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send messages as yourself."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get a message from this room.",
|
|
||||||
"notes": "Get a message from this room.",
|
|
||||||
"type": "Message",
|
|
||||||
"nickname": "get_message",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "roomId",
|
|
||||||
"description": "The room to send the message in.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "userId",
|
|
||||||
"description": "The fully qualified message sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "Message not found."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/topic",
|
"path": "/rooms/{roomId}/state/m.room.topic",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
|
@ -127,12 +168,6 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send messages as yourself."
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160,13 +195,13 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/{msgSenderId}/{messageId}/feedback/{senderId}/{feedbackType}",
|
"path": "/rooms/{roomId}/send/m.room.message.feedback/{txnId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"summary": "Send feedback to a message.",
|
"summary": "Send feedback to a message.",
|
||||||
"notes": "Send feedback to a message.",
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message.feedback",
|
||||||
"type": "void",
|
"type": "EventId",
|
||||||
"nickname": "send_feedback",
|
"nickname": "send_feedback",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -187,107 +222,124 @@
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "msgSenderId",
|
"name": "txnId",
|
||||||
"description": "The fully qualified message sender's user ID.",
|
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "senderId",
|
|
||||||
"description": "The fully qualified feedback sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "feedbackType",
|
|
||||||
"description": "The type of feedback being sent.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path",
|
|
||||||
"enum": [
|
|
||||||
"d",
|
|
||||||
"r"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responseMessages": [
|
"responseMessages": [
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send feedback as yourself."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "Bad feedback type."
|
"message": "Bad feedback type."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get feedback for a message.",
|
|
||||||
"notes": "Get feedback for a message.",
|
|
||||||
"type": "Feedback",
|
|
||||||
"nickname": "get_feedback",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "roomId",
|
|
||||||
"description": "The room to send the message in.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "msgSenderId",
|
|
||||||
"description": "The fully qualified message sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "senderId",
|
|
||||||
"description": "The fully qualified feedback sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "feedbackType",
|
|
||||||
"description": "Enum: The type of feedback being sent.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path",
|
|
||||||
"enum": [
|
|
||||||
"d",
|
|
||||||
"r"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "Feedback not found."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/members/{userId}/state",
|
"path": "/rooms/{roomId}/invite/{txnId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Invite a user to this room.",
|
||||||
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/invite",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "invite",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room which has this user.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "txnId",
|
||||||
|
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
|
||||||
|
"required": false,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"description": "The user to invite.",
|
||||||
|
"required": true,
|
||||||
|
"type": "InviteRequest",
|
||||||
|
"paramType": "body"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/join/{txnId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Join this room.",
|
||||||
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/join",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "join_room",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to join.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "txnId",
|
||||||
|
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
|
||||||
|
"required": false,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/leave/{txnId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "PUT",
|
||||||
|
"summary": "Leave this room.",
|
||||||
|
"notes": "This operation can also be done as a POST to /rooms/{roomId}/leave",
|
||||||
|
"type": "void",
|
||||||
|
"nickname": "leave",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "roomId",
|
||||||
|
"description": "The room to leave.",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "txnId",
|
||||||
|
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
|
||||||
|
"required": false,
|
||||||
|
"type": "string",
|
||||||
|
"paramType": "path"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rooms/{roomId}/state/m.room.member/{userId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
|
@ -376,58 +428,25 @@
|
||||||
"message": "Member not found."
|
"message": "Member not found."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"summary": "Leave a room.",
|
|
||||||
"notes": "Leave a room.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "remove_membership",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "userId",
|
|
||||||
"description": "The user who is leaving.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "roomId",
|
|
||||||
"description": "The room which has this user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "You are not in the room."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Cannot force another user to leave."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/join/{roomAlias}",
|
"path": "/join/{roomAliasOrId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"summary": "Join a room via a room alias.",
|
"summary": "Join a room via a room alias or room ID.",
|
||||||
"notes": "Join a room via a room alias.",
|
"notes": "Join a room via a room alias or room ID.",
|
||||||
"type": "RoomInfo",
|
"type": "RoomInfo",
|
||||||
"nickname": "join_room_via_alias",
|
"nickname": "join",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "roomAlias",
|
"name": "roomAliasOrId",
|
||||||
"description": "The room alias to join.",
|
"description": "The room alias or room ID to join.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
|
@ -443,7 +462,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms",
|
"path": "/createRoom",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
@ -477,7 +496,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/list",
|
"path": "/rooms/{roomId}/messages",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
@ -519,7 +538,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/members/list",
|
"path": "/rooms/{roomId}/members",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
@ -704,12 +723,17 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"event_id": {
|
"event_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "An ID which uniquely identifies this event.",
|
"description": "An ID which uniquely identifies this event. This is automatically set by the server.",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"room_id": {
|
"room_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The room in which this event occurred.",
|
"description": "The room in which this event occurred. This is automatically set by the server.",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The event type.",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -717,6 +741,26 @@
|
||||||
"MessageEvent"
|
"MessageEvent"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"EventId": {
|
||||||
|
"id": "EventId",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The allocated event ID for this event.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EventContent": {
|
||||||
|
"id": "EventContent",
|
||||||
|
"properties": {
|
||||||
|
"__event_content_keys__": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Event-specific content keys and values.",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MessageEvent": {
|
"MessageEvent": {
|
||||||
"id": "MessageEvent",
|
"id": "MessageEvent",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -733,73 +777,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Tag": {
|
"InviteRequest": {
|
||||||
"id": "Tag",
|
"id": "InviteRequest",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"user_id": {
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Pet": {
|
|
||||||
"id": "Pet",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64",
|
|
||||||
"description": "unique identifier for the pet",
|
|
||||||
"minimum": "0.0",
|
|
||||||
"maximum": "100.0"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"$ref": "Category"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"photoUrls": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "Tag"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "pet status in the store",
|
"description": "The fully-qualified user ID."
|
||||||
"enum": [
|
|
||||||
"available",
|
|
||||||
"pending",
|
|
||||||
"sold"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Category": {
|
|
||||||
"id": "Category",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pet": {
|
|
||||||
"$ref": "Pet"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
=========================
|
|
||||||
Client-Server URL Summary
|
|
||||||
=========================
|
|
||||||
|
|
||||||
A brief overview of the URL scheme involved in the Synapse Client-Server API.
|
|
||||||
|
|
||||||
|
|
||||||
URLs
|
|
||||||
====
|
|
||||||
|
|
||||||
Fetch events:
|
|
||||||
GET /events
|
|
||||||
|
|
||||||
Registering an account
|
|
||||||
POST /register
|
|
||||||
|
|
||||||
Unregistering an account
|
|
||||||
POST /unregister
|
|
||||||
|
|
||||||
Rooms
|
|
||||||
-----
|
|
||||||
|
|
||||||
Creating a room by ID
|
|
||||||
PUT /rooms/$roomid
|
|
||||||
|
|
||||||
Creating an anonymous room
|
|
||||||
POST /rooms
|
|
||||||
|
|
||||||
Room topic
|
|
||||||
GET /rooms/$roomid/topic
|
|
||||||
PUT /rooms/$roomid/topic
|
|
||||||
|
|
||||||
List rooms
|
|
||||||
GET /rooms/list
|
|
||||||
|
|
||||||
Invite/Join/Leave
|
|
||||||
GET /rooms/$roomid/members/$userid/state
|
|
||||||
PUT /rooms/$roomid/members/$userid/state
|
|
||||||
DELETE /rooms/$roomid/members/$userid/state
|
|
||||||
|
|
||||||
List members
|
|
||||||
GET /rooms/$roomid/members/list
|
|
||||||
|
|
||||||
Sending/reading messages
|
|
||||||
PUT /rooms/$roomid/messages/$sender/$msgid
|
|
||||||
|
|
||||||
Feedback
|
|
||||||
GET /rooms/$roomid/messages/$sender/$msgid/feedback/$feedbackuser/$feedback
|
|
||||||
PUT /rooms/$roomid/messages/$sender/$msgid/feedback/$feedbackuser/$feedback
|
|
||||||
|
|
||||||
Paginating messages
|
|
||||||
GET /rooms/$roomid/messages/list
|
|
||||||
|
|
||||||
Profiles
|
|
||||||
--------
|
|
||||||
|
|
||||||
Display name
|
|
||||||
GET /profile/$userid/displayname
|
|
||||||
PUT /profile/$userid/displayname
|
|
||||||
|
|
||||||
Avatar URL
|
|
||||||
GET /profile/$userid/avatar_url
|
|
||||||
PUT /profile/$userid/avatar_url
|
|
||||||
|
|
||||||
Metadata
|
|
||||||
GET /profile/$userid/metadata
|
|
||||||
POST /profile/$userid/metadata
|
|
||||||
|
|
||||||
Presence
|
|
||||||
--------
|
|
||||||
|
|
||||||
My state or status message
|
|
||||||
GET /presence/$userid/status
|
|
||||||
PUT /presence/$userid/status
|
|
||||||
also 'GET' for fetching others
|
|
||||||
|
|
||||||
TODO(paul): per-device idle time, device type; similar to above
|
|
||||||
|
|
||||||
My presence list
|
|
||||||
GET /presence_list/$myuserid
|
|
||||||
POST /presence_list/$myuserid
|
|
||||||
body is JSON-encoded dict of keys:
|
|
||||||
invite: list of UserID strings to invite
|
|
||||||
drop: list of UserID strings to remove
|
|
||||||
TODO(paul): define other ops: accept, group management, ordering?
|
|
||||||
|
|
||||||
Presence polling start/stop
|
|
||||||
POST /presence_list/$myuserid?op=start
|
|
||||||
POST /presence_list/$myuserid?op=stop
|
|
||||||
|
|
||||||
Presence invite
|
|
||||||
POST /presence_list/$myuserid/invite/$targetuserid
|
|
|
@ -43,6 +43,22 @@ import re
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMAS = [
|
||||||
|
"transactions",
|
||||||
|
"pdu",
|
||||||
|
"users",
|
||||||
|
"profiles",
|
||||||
|
"presence",
|
||||||
|
"im",
|
||||||
|
"room_aliases",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Remember to update this number every time an incompatible change is made to
|
||||||
|
# database schema files, so the users will be informed on server restarts.
|
||||||
|
SCHEMA_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
class SynapseHomeServer(HomeServer):
|
class SynapseHomeServer(HomeServer):
|
||||||
|
|
||||||
def build_http_client(self):
|
def build_http_client(self):
|
||||||
|
@ -65,31 +81,39 @@ class SynapseHomeServer(HomeServer):
|
||||||
don't have to worry about overwriting existing content.
|
don't have to worry about overwriting existing content.
|
||||||
"""
|
"""
|
||||||
logging.info("Preparing database: %s...", self.db_name)
|
logging.info("Preparing database: %s...", self.db_name)
|
||||||
|
|
||||||
|
with sqlite3.connect(self.db_name) as db_conn:
|
||||||
|
c = db_conn.cursor()
|
||||||
|
c.execute("PRAGMA user_version")
|
||||||
|
row = c.fetchone()
|
||||||
|
|
||||||
|
if row and row[0]:
|
||||||
|
user_version = row[0]
|
||||||
|
|
||||||
|
if user_version < SCHEMA_VERSION:
|
||||||
|
# TODO(paul): add some kind of intelligent fixup here
|
||||||
|
raise ValueError("Cannot use this database as the " +
|
||||||
|
"schema version (%d) does not match (%d)" %
|
||||||
|
(user_version, SCHEMA_VERSION)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for sql_loc in SCHEMAS:
|
||||||
|
sql_script = read_schema(sql_loc)
|
||||||
|
|
||||||
|
c.executescript(sql_script)
|
||||||
|
db_conn.commit()
|
||||||
|
|
||||||
|
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
logging.info("Database prepared in %s.", self.db_name)
|
||||||
|
|
||||||
pool = adbapi.ConnectionPool(
|
pool = adbapi.ConnectionPool(
|
||||||
'sqlite3', self.db_name, check_same_thread=False,
|
'sqlite3', self.db_name, check_same_thread=False,
|
||||||
cp_min=1, cp_max=1)
|
cp_min=1, cp_max=1)
|
||||||
|
|
||||||
schemas = [
|
|
||||||
"transactions",
|
|
||||||
"pdu",
|
|
||||||
"users",
|
|
||||||
"profiles",
|
|
||||||
"presence",
|
|
||||||
"im",
|
|
||||||
"room_aliases",
|
|
||||||
]
|
|
||||||
|
|
||||||
for sql_loc in schemas:
|
|
||||||
sql_script = read_schema(sql_loc)
|
|
||||||
|
|
||||||
with sqlite3.connect(self.db_name) as db_conn:
|
|
||||||
c = db_conn.cursor()
|
|
||||||
c.executescript(sql_script)
|
|
||||||
c.close()
|
|
||||||
db_conn.commit()
|
|
||||||
|
|
||||||
logging.info("Database prepared in %s.", self.db_name)
|
|
||||||
|
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def create_resource_tree(self, web_client, redirect_root_to_web_client):
|
def create_resource_tree(self, web_client, redirect_root_to_web_client):
|
||||||
|
@ -184,6 +208,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
|
|
||||||
def start_listening(self, port):
|
def start_listening(self, port):
|
||||||
reactor.listenTCP(port, Site(self.root_resource))
|
reactor.listenTCP(port, Site(self.root_resource))
|
||||||
|
logger.info("Synapse now listening on port %d", port)
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbosity=0, filename=None, config_path=None):
|
def setup_logging(verbosity=0, filename=None, config_path=None):
|
||||||
|
@ -278,7 +303,7 @@ def setup():
|
||||||
redirect_root_to_web_client=True)
|
redirect_root_to_web_client=True)
|
||||||
hs.start_listening(args.port)
|
hs.start_listening(args.port)
|
||||||
|
|
||||||
hs.build_db_pool()
|
hs.get_db_pool()
|
||||||
|
|
||||||
if args.manhole:
|
if args.manhole:
|
||||||
f = twisted.manhole.telnet.ShellFactory()
|
f = twisted.manhole.telnet.ShellFactory()
|
||||||
|
|
|
@ -23,6 +23,7 @@ from .login import LoginHandler
|
||||||
from .profile import ProfileHandler
|
from .profile import ProfileHandler
|
||||||
from .presence import PresenceHandler
|
from .presence import PresenceHandler
|
||||||
from .directory import DirectoryHandler
|
from .directory import DirectoryHandler
|
||||||
|
from .typing import TypingNotificationHandler
|
||||||
|
|
||||||
|
|
||||||
class Handlers(object):
|
class Handlers(object):
|
||||||
|
@ -46,3 +47,4 @@ class Handlers(object):
|
||||||
self.room_list_handler = RoomListHandler(hs)
|
self.room_list_handler = RoomListHandler(hs)
|
||||||
self.login_handler = LoginHandler(hs)
|
self.login_handler = LoginHandler(hs)
|
||||||
self.directory_handler = DirectoryHandler(hs)
|
self.directory_handler = DirectoryHandler(hs)
|
||||||
|
self.typing_notification_handler = TypingNotificationHandler(hs)
|
||||||
|
|
146
synapse/handlers/typing.py
Normal file
146
synapse/handlers/typing.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# A tiny object useful for storing a user's membership in a room, as a mapping
|
||||||
|
# key
|
||||||
|
RoomMember = namedtuple("RoomMember", ("room_id", "user"))
|
||||||
|
|
||||||
|
|
||||||
|
class TypingNotificationHandler(BaseHandler):
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(TypingNotificationHandler, self).__init__(hs)
|
||||||
|
|
||||||
|
self.homeserver = hs
|
||||||
|
|
||||||
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
|
self.federation = hs.get_replication_layer()
|
||||||
|
|
||||||
|
self.federation.register_edu_handler("m.typing", self._recv_edu)
|
||||||
|
|
||||||
|
self._member_typing_until = {}
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def started_typing(self, target_user, auth_user, room_id, timeout):
|
||||||
|
if not target_user.is_mine:
|
||||||
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
|
if target_user != auth_user:
|
||||||
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
until = self.clock.time_msec() + timeout
|
||||||
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
was_present = member in self._member_typing_until
|
||||||
|
|
||||||
|
self._member_typing_until[member] = until
|
||||||
|
|
||||||
|
if was_present:
|
||||||
|
# No point sending another notification
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
yield self._push_update(
|
||||||
|
room_id=room_id,
|
||||||
|
user=target_user,
|
||||||
|
typing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def stopped_typing(self, target_user, auth_user, room_id):
|
||||||
|
if not target_user.is_mine:
|
||||||
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
|
if target_user != auth_user:
|
||||||
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
if member not in self._member_typing_until:
|
||||||
|
# No point
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
yield self._push_update(
|
||||||
|
room_id=room_id,
|
||||||
|
user=target_user,
|
||||||
|
typing=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _push_update(self, room_id, user, typing):
|
||||||
|
localusers = set()
|
||||||
|
remotedomains = set()
|
||||||
|
|
||||||
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
|
yield rm_handler.fetch_room_distributions_into(room_id,
|
||||||
|
localusers=localusers, remotedomains=remotedomains,
|
||||||
|
ignore_user=user)
|
||||||
|
|
||||||
|
for u in localusers:
|
||||||
|
self.push_update_to_clients(
|
||||||
|
room_id=room_id,
|
||||||
|
observer_user=u,
|
||||||
|
observed_user=user,
|
||||||
|
typing=typing,
|
||||||
|
)
|
||||||
|
|
||||||
|
deferreds = []
|
||||||
|
for domain in remotedomains:
|
||||||
|
deferreds.append(self.federation.send_edu(
|
||||||
|
destination=domain,
|
||||||
|
edu_type="m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": room_id,
|
||||||
|
"user_id": user.to_string(),
|
||||||
|
"typing": typing,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
yield defer.DeferredList(deferreds, consumeErrors=False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _recv_edu(self, origin, content):
|
||||||
|
room_id = content["room_id"]
|
||||||
|
user = self.homeserver.parse_userid(content["user_id"])
|
||||||
|
|
||||||
|
localusers = set()
|
||||||
|
|
||||||
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
|
yield rm_handler.fetch_room_distributions_into(room_id,
|
||||||
|
localusers=localusers)
|
||||||
|
|
||||||
|
for u in localusers:
|
||||||
|
self.push_update_to_clients(
|
||||||
|
room_id=room_id,
|
||||||
|
observer_user=u,
|
||||||
|
observed_user=user,
|
||||||
|
typing=content["typing"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def push_update_to_clients(self, room_id, observer_user, observed_user,
|
||||||
|
typing):
|
||||||
|
# TODO(paul) steal this from presence.py
|
||||||
|
pass
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
room, events, register, login, profile, public, presence, initial_sync,
|
room, events, register, login, profile, presence, initial_sync, directory
|
||||||
directory
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +39,6 @@ class RestServletFactory(object):
|
||||||
register.register_servlets(hs, client_resource)
|
register.register_servlets(hs, client_resource)
|
||||||
login.register_servlets(hs, client_resource)
|
login.register_servlets(hs, client_resource)
|
||||||
profile.register_servlets(hs, client_resource)
|
profile.register_servlets(hs, client_resource)
|
||||||
public.register_servlets(hs, client_resource)
|
|
||||||
presence.register_servlets(hs, client_resource)
|
presence.register_servlets(hs, client_resource)
|
||||||
initial_sync.register_servlets(hs, client_resource)
|
initial_sync.register_servlets(hs, client_resource)
|
||||||
directory.register_servlets(hs, client_resource)
|
directory.register_servlets(hs, client_resource)
|
||||||
|
|
|
@ -31,7 +31,7 @@ def register_servlets(hs, http_server):
|
||||||
|
|
||||||
|
|
||||||
class ClientDirectoryServer(RestServlet):
|
class ClientDirectoryServer(RestServlet):
|
||||||
PATTERN = client_path_pattern("/ds/room/(?P<room_alias>[^/]*)$")
|
PATTERN = client_path_pattern("/directory/room/(?P<room_alias>[^/]*)$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_alias):
|
def on_GET(self, request, room_alias):
|
||||||
|
|
|
@ -68,7 +68,7 @@ class PresenceStatusRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
class PresenceListRestServlet(RestServlet):
|
class PresenceListRestServlet(RestServlet):
|
||||||
PATTERN = client_path_pattern("/presence_list/(?P<user_id>[^/]*)")
|
PATTERN = client_path_pattern("/presence/list/(?P<user_id>[^/]*)")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- 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 REST servlets to do with public paths: /public"""
|
|
||||||
from twisted.internet import defer
|
|
||||||
|
|
||||||
from base import RestServlet, client_path_pattern
|
|
||||||
|
|
||||||
|
|
||||||
class PublicRoomListRestServlet(RestServlet):
|
|
||||||
PATTERN = client_path_pattern("/public/rooms$")
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def on_GET(self, request):
|
|
||||||
handler = self.handlers.room_list_handler
|
|
||||||
data = yield handler.get_public_room_list()
|
|
||||||
defer.returnValue((200, data))
|
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs, http_server):
|
|
||||||
PublicRoomListRestServlet(hs).register(http_server)
|
|
|
@ -34,31 +34,28 @@ class RoomCreateRestServlet(RestServlet):
|
||||||
# No PATTERN; we have custom dispatch rules here
|
# No PATTERN; we have custom dispatch rules here
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms OR /rooms/<roomid>
|
PATTERN = "/createRoom"
|
||||||
http_server.register_path("POST",
|
register_txn_path(self, PATTERN, http_server)
|
||||||
client_path_pattern("/rooms$"),
|
|
||||||
self.on_POST)
|
|
||||||
http_server.register_path("PUT",
|
|
||||||
client_path_pattern(
|
|
||||||
"/rooms/(?P<room_id>[^/]*)$"),
|
|
||||||
self.on_PUT)
|
|
||||||
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
||||||
http_server.register_path("OPTIONS",
|
http_server.register_path("OPTIONS",
|
||||||
client_path_pattern("/rooms(?:/.*)?$"),
|
client_path_pattern("/rooms(?:/.*)?$"),
|
||||||
self.on_OPTIONS)
|
self.on_OPTIONS)
|
||||||
|
# define CORS for /createRoom[/txnid]
|
||||||
|
http_server.register_path("OPTIONS",
|
||||||
|
client_path_pattern("/createRoom(?:/.*)?$"),
|
||||||
|
self.on_OPTIONS)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_id):
|
def on_PUT(self, request, txn_id):
|
||||||
room_id = urllib.unquote(room_id)
|
try:
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
defer.returnValue(self.txns.get_client_transaction(request, txn_id))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not room_id:
|
response = yield self.on_POST(request)
|
||||||
raise SynapseError(400, "PUT must specify a room ID")
|
|
||||||
|
|
||||||
room_config = self.get_room_config(request)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
info = yield self.make_room(room_config, auth_user, room_id)
|
defer.returnValue(response)
|
||||||
room_config.update(info)
|
|
||||||
defer.returnValue((200, info))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request):
|
def on_POST(self, request):
|
||||||
|
@ -267,6 +264,17 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Needs unit testing
|
||||||
|
class PublicRoomListRestServlet(RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/publicRooms$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_GET(self, request):
|
||||||
|
handler = self.handlers.room_list_handler
|
||||||
|
data = yield handler.get_public_room_list()
|
||||||
|
defer.returnValue((200, data))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMemberListRestServlet(RestServlet):
|
class RoomMemberListRestServlet(RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
|
||||||
|
@ -314,6 +322,50 @@ class RoomMessageListRestServlet(RestServlet):
|
||||||
defer.returnValue((200, msgs))
|
defer.returnValue((200, msgs))
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Needs unit testing
|
||||||
|
class RoomStateRestServlet(RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/state$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_GET(self, request, room_id):
|
||||||
|
user = yield self.auth.get_user_by_req(request)
|
||||||
|
# TODO: Get all the current state for this room and return in the same
|
||||||
|
# format as initial sync, that is:
|
||||||
|
# [
|
||||||
|
# { state event }, { state event }
|
||||||
|
# ]
|
||||||
|
defer.returnValue((200, []))
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Needs unit testing
|
||||||
|
class RoomInitialSyncRestServlet(RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/initialSync$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_GET(self, request, room_id):
|
||||||
|
user = yield self.auth.get_user_by_req(request)
|
||||||
|
# TODO: Get all the initial sync data for this room and return in the
|
||||||
|
# same format as initial sync, that is:
|
||||||
|
# {
|
||||||
|
# membership: join,
|
||||||
|
# messages: [
|
||||||
|
# chunk: [ msg events ],
|
||||||
|
# start: s_tok,
|
||||||
|
# end: e_tok
|
||||||
|
# ],
|
||||||
|
# room_id: foo,
|
||||||
|
# state: [
|
||||||
|
# { state event } , { state event }
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
# Probably worth keeping the keys room_id and membership for parity with
|
||||||
|
# /initialSync even though they must be joined to sync this and know the
|
||||||
|
# room ID, so clients can reuse the same code (room_id and membership
|
||||||
|
# are MANDATORY for /initialSync, so the code will expect it to be
|
||||||
|
# there)
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
class RoomTriggerBackfill(RestServlet):
|
class RoomTriggerBackfill(RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
|
||||||
|
|
||||||
|
@ -427,3 +479,6 @@ def register_servlets(hs, http_server):
|
||||||
RoomTriggerBackfill(hs).register(http_server)
|
RoomTriggerBackfill(hs).register(http_server)
|
||||||
RoomMembershipRestServlet(hs).register(http_server)
|
RoomMembershipRestServlet(hs).register(http_server)
|
||||||
RoomSendEventRestServlet(hs).register(http_server)
|
RoomSendEventRestServlet(hs).register(http_server)
|
||||||
|
PublicRoomListRestServlet(hs).register(http_server)
|
||||||
|
RoomStateRestServlet(hs).register(http_server)
|
||||||
|
RoomInitialSyncRestServlet(hs).register(http_server)
|
||||||
|
|
|
@ -124,6 +124,11 @@ class BaseHomeServer(object):
|
||||||
object."""
|
object."""
|
||||||
return UserID.from_string(s, hs=self)
|
return UserID.from_string(s, hs=self)
|
||||||
|
|
||||||
|
def parse_roomid(self, s):
|
||||||
|
"""Parse the string given by 's' as a Room ID and return a RoomID
|
||||||
|
object."""
|
||||||
|
return RoomID.from_string(s, hs=self)
|
||||||
|
|
||||||
def parse_roomalias(self, s):
|
def parse_roomalias(self, s):
|
||||||
"""Parse the string given by 's' as a Room Alias and return a RoomAlias
|
"""Parse the string given by 's' as a Room Alias and return a RoomAlias
|
||||||
object."""
|
object."""
|
||||||
|
|
250
tests/handlers/test_typing.py
Normal file
250
tests/handlers/test_typing.py
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
|
||||||
|
from twisted.trial import unittest
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from mock import Mock, call, ANY
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ..utils import MockHttpResource, MockClock, DeferredMockCallable
|
||||||
|
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
from synapse.handlers.typing import TypingNotificationHandler
|
||||||
|
|
||||||
|
|
||||||
|
logging.getLogger().addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
|
||||||
|
def _expect_edu(destination, edu_type, content, origin="test"):
|
||||||
|
return {
|
||||||
|
"origin": origin,
|
||||||
|
"ts": 1000000,
|
||||||
|
"pdus": [],
|
||||||
|
"edus": [
|
||||||
|
{
|
||||||
|
"origin": origin,
|
||||||
|
"destination": destination,
|
||||||
|
"edu_type": edu_type,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _make_edu_json(origin, edu_type, content):
|
||||||
|
return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
|
||||||
|
|
||||||
|
|
||||||
|
class JustTypingNotificationHandlers(object):
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.typing_notification_handler = TypingNotificationHandler(hs)
|
||||||
|
|
||||||
|
|
||||||
|
class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
|
"""Tests typing notifications to rooms."""
|
||||||
|
def setUp(self):
|
||||||
|
self.clock = MockClock()
|
||||||
|
|
||||||
|
self.mock_http_client = Mock(spec=[])
|
||||||
|
self.mock_http_client.put_json = DeferredMockCallable()
|
||||||
|
|
||||||
|
self.mock_federation_resource = MockHttpResource()
|
||||||
|
|
||||||
|
hs = HomeServer("test",
|
||||||
|
clock=self.clock,
|
||||||
|
db_pool=None,
|
||||||
|
datastore=Mock(spec=[
|
||||||
|
# Bits that Federation needs
|
||||||
|
"prep_send_transaction",
|
||||||
|
"delivered_txn",
|
||||||
|
"get_received_txn_response",
|
||||||
|
"set_received_txn_response",
|
||||||
|
]),
|
||||||
|
handlers=None,
|
||||||
|
resource_for_client=Mock(),
|
||||||
|
resource_for_federation=self.mock_federation_resource,
|
||||||
|
http_client=self.mock_http_client,
|
||||||
|
)
|
||||||
|
hs.handlers = JustTypingNotificationHandlers(hs)
|
||||||
|
|
||||||
|
self.mock_update_client = Mock()
|
||||||
|
self.mock_update_client.return_value = defer.succeed(None)
|
||||||
|
|
||||||
|
self.handler = hs.get_handlers().typing_notification_handler
|
||||||
|
self.handler.push_update_to_clients = self.mock_update_client
|
||||||
|
|
||||||
|
self.datastore = hs.get_datastore()
|
||||||
|
|
||||||
|
def get_received_txn_response(*args):
|
||||||
|
return defer.succeed(None)
|
||||||
|
self.datastore.get_received_txn_response = get_received_txn_response
|
||||||
|
|
||||||
|
self.room_id = "a-room"
|
||||||
|
|
||||||
|
# Mock the RoomMemberHandler
|
||||||
|
hs.handlers.room_member_handler = Mock(spec=[])
|
||||||
|
self.room_member_handler = hs.handlers.room_member_handler
|
||||||
|
|
||||||
|
self.room_members = []
|
||||||
|
|
||||||
|
def get_rooms_for_user(user):
|
||||||
|
if user in self.room_members:
|
||||||
|
return defer.succeed([self.room_id])
|
||||||
|
else:
|
||||||
|
return defer.succeed([])
|
||||||
|
self.room_member_handler.get_rooms_for_user = get_rooms_for_user
|
||||||
|
|
||||||
|
def get_room_members(room_id):
|
||||||
|
if room_id == self.room_id:
|
||||||
|
return defer.succeed(self.room_members)
|
||||||
|
else:
|
||||||
|
return defer.succeed([])
|
||||||
|
self.room_member_handler.get_room_members = get_room_members
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def fetch_room_distributions_into(room_id, localusers=None,
|
||||||
|
remotedomains=None, ignore_user=None):
|
||||||
|
|
||||||
|
members = yield get_room_members(room_id)
|
||||||
|
for member in members:
|
||||||
|
if ignore_user is not None and member == ignore_user:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if member.is_mine:
|
||||||
|
if localusers is not None:
|
||||||
|
localusers.add(member)
|
||||||
|
else:
|
||||||
|
if remotedomains is not None:
|
||||||
|
remotedomains.add(member.domain)
|
||||||
|
self.room_member_handler.fetch_room_distributions_into = (
|
||||||
|
fetch_room_distributions_into)
|
||||||
|
|
||||||
|
# Some local users to test with
|
||||||
|
self.u_apple = hs.parse_userid("@apple:test")
|
||||||
|
self.u_banana = hs.parse_userid("@banana:test")
|
||||||
|
|
||||||
|
# Remote user
|
||||||
|
self.u_onion = hs.parse_userid("@onion:farm")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_local(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=20000,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_banana,
|
||||||
|
observed_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_remote_send(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
|
put_json = self.mock_http_client.put_json
|
||||||
|
put_json.expect_call_and_return(
|
||||||
|
call("farm",
|
||||||
|
path="/matrix/federation/v1/send/1000000/",
|
||||||
|
data=_expect_edu("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_apple.to_string(),
|
||||||
|
"typing": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
defer.succeed((200, "OK"))
|
||||||
|
)
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=20000,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield put_json.await_calls()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_remote_recv(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
|
"/matrix/federation/v1/send/1000000/",
|
||||||
|
_make_edu_json("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_onion.to_string(),
|
||||||
|
"typing": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_apple,
|
||||||
|
observed_user=self.u_onion,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_stopped_typing(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_banana, self.u_onion]
|
||||||
|
|
||||||
|
put_json = self.mock_http_client.put_json
|
||||||
|
put_json.expect_call_and_return(
|
||||||
|
call("farm",
|
||||||
|
path="/matrix/federation/v1/send/1000000/",
|
||||||
|
data=_expect_edu("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_apple.to_string(),
|
||||||
|
"typing": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
defer.succeed((200, "OK"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gut-wrenching
|
||||||
|
from synapse.handlers.typing import RoomMember
|
||||||
|
self.handler._member_typing_until[
|
||||||
|
RoomMember(self.room_id, self.u_apple)
|
||||||
|
] = 1002000
|
||||||
|
|
||||||
|
yield self.handler.stopped_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_banana,
|
||||||
|
observed_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=False),
|
||||||
|
])
|
||||||
|
|
||||||
|
yield put_json.await_calls()
|
|
@ -179,9 +179,8 @@ class EventStreamPermissionsTestCase(RestTestCase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_stream_room_permissions(self):
|
def test_stream_room_permissions(self):
|
||||||
room_id = "!rid1:test"
|
room_id = yield self.create_room_as(self.other_user,
|
||||||
yield self.create_room_as(room_id, self.other_user,
|
tok=self.other_token)
|
||||||
tok=self.other_token)
|
|
||||||
yield self.send(room_id, tok=self.other_token)
|
yield self.send(room_id, tok=self.other_token)
|
||||||
|
|
||||||
# invited to room (expect no content for room)
|
# invited to room (expect no content for room)
|
||||||
|
|
|
@ -171,7 +171,7 @@ class PresenceListTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/presence_list/%s" % (myid), None)
|
"/presence/list/%s" % (myid), None)
|
||||||
|
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
|
@ -192,7 +192,7 @@ class PresenceListTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"/presence_list/%s" % (myid),
|
"/presence/list/%s" % (myid),
|
||||||
"""{"invite": ["@banana:test"]}"""
|
"""{"invite": ["@banana:test"]}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ class PresenceListTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"/presence_list/%s" % (myid),
|
"/presence/list/%s" % (myid),
|
||||||
"""{"drop": ["@banana:test"]}"""
|
"""{"drop": ["@banana:test"]}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -74,13 +74,11 @@ class RoomPermissionsTestCase(RestTestCase):
|
||||||
# create some rooms under the name rmcreator_id
|
# create some rooms under the name rmcreator_id
|
||||||
self.uncreated_rmid = "!aa:test"
|
self.uncreated_rmid = "!aa:test"
|
||||||
|
|
||||||
self.created_rmid = "!abc:test"
|
self.created_rmid = yield self.create_room_as(self.rmcreator_id,
|
||||||
yield self.create_room_as(self.created_rmid, self.rmcreator_id,
|
is_public=False)
|
||||||
is_public=False)
|
|
||||||
|
|
||||||
self.created_public_rmid = "!def1234ghi:test"
|
self.created_public_rmid = yield self.create_room_as(self.rmcreator_id,
|
||||||
yield self.create_room_as(self.created_public_rmid, self.rmcreator_id,
|
is_public=True)
|
||||||
is_public=True)
|
|
||||||
|
|
||||||
# send a message in one of the rooms
|
# send a message in one of the rooms
|
||||||
self.created_rmid_msg_path = ("/rooms/%s/send/m.room.message/a1" %
|
self.created_rmid_msg_path = ("/rooms/%s/send/m.room.message/a1" %
|
||||||
|
@ -423,8 +421,7 @@ class RoomsMemberListTestCase(RestTestCase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list(self):
|
def test_get_member_list(self):
|
||||||
room_id = "!aa:test"
|
room_id = yield self.create_room_as(self.user_id)
|
||||||
yield self.create_room_as(room_id, self.user_id)
|
|
||||||
(code, response) = yield self.mock_resource.trigger_get(
|
(code, response) = yield self.mock_resource.trigger_get(
|
||||||
"/rooms/%s/members" % room_id)
|
"/rooms/%s/members" % room_id)
|
||||||
self.assertEquals(200, code, msg=str(response))
|
self.assertEquals(200, code, msg=str(response))
|
||||||
|
@ -437,18 +434,16 @@ class RoomsMemberListTestCase(RestTestCase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list_no_permission(self):
|
def test_get_member_list_no_permission(self):
|
||||||
room_id = "!bb:test"
|
room_id = yield self.create_room_as("@some_other_guy:red")
|
||||||
yield self.create_room_as(room_id, "@some_other_guy:red")
|
|
||||||
(code, response) = yield self.mock_resource.trigger_get(
|
(code, response) = yield self.mock_resource.trigger_get(
|
||||||
"/rooms/%s/members" % room_id)
|
"/rooms/%s/members" % room_id)
|
||||||
self.assertEquals(403, code, msg=str(response))
|
self.assertEquals(403, code, msg=str(response))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list_mixed_memberships(self):
|
def test_get_member_list_mixed_memberships(self):
|
||||||
room_id = "!bb:test"
|
|
||||||
room_creator = "@some_other_guy:blue"
|
room_creator = "@some_other_guy:blue"
|
||||||
|
room_id = yield self.create_room_as(room_creator)
|
||||||
room_path = "/rooms/%s/members" % room_id
|
room_path = "/rooms/%s/members" % room_id
|
||||||
yield self.create_room_as(room_id, room_creator)
|
|
||||||
yield self.invite(room=room_id, src=room_creator,
|
yield self.invite(room=room_id, src=room_creator,
|
||||||
targ=self.user_id)
|
targ=self.user_id)
|
||||||
# can't see list if you're just invited.
|
# can't see list if you're just invited.
|
||||||
|
@ -503,107 +498,57 @@ class RoomsCreateTestCase(RestTestCase):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_no_keys(self):
|
def test_post_room_no_keys(self):
|
||||||
# POST with no config keys, expect new room id
|
# POST with no config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"{}")
|
"/createRoom",
|
||||||
|
"{}")
|
||||||
self.assertEquals(200, code, response)
|
self.assertEquals(200, code, response)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_visibility_key(self):
|
def test_post_room_visibility_key(self):
|
||||||
# POST with visibility config key, expect new room id
|
# POST with visibility config key, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"visibility":"private"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"visibility":"private"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_custom_key(self):
|
def test_post_room_custom_key(self):
|
||||||
# POST with custom config keys, expect new room id
|
# POST with custom config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"custom":"stuff"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"custom":"stuff"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_known_and_unknown_keys(self):
|
def test_post_room_known_and_unknown_keys(self):
|
||||||
# POST with custom + known config keys, expect new room id
|
# POST with custom + known config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"visibility":"private","custom":"things"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"visibility":"private","custom":"things"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_invalid_content(self):
|
def test_post_room_invalid_content(self):
|
||||||
# POST with invalid content / paths, expect 400
|
# POST with invalid content / paths, expect 400
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
|
||||||
'{"visibili')
|
|
||||||
self.assertEquals(400, code)
|
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
|
||||||
'["hello"]')
|
|
||||||
self.assertEquals(400, code)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_no_keys(self):
|
|
||||||
# PUT with no config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
"PUT", "/rooms/%21aa%3Atest", "{}"
|
"POST",
|
||||||
)
|
"/createRoom",
|
||||||
self.assertEquals(200, code)
|
'{"visibili')
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_visibility_key(self):
|
|
||||||
# PUT with known config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21bb%3Atest", '{"visibility":"private"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_custom_key(self):
|
|
||||||
# PUT with custom config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21cc%3Atest", '{"custom":"stuff"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_known_and_unknown_keys(self):
|
|
||||||
# PUT with custom + known config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21dd%3Atest",
|
|
||||||
'{"visibility":"private","custom":"things"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_invalid_content(self):
|
|
||||||
# PUT with invalid content / room names, expect 400
|
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/ee", '{"sdf"'
|
|
||||||
)
|
|
||||||
self.assertEquals(400, code)
|
self.assertEquals(400, code)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
"PUT", "/rooms/ee", '["hello"]'
|
"POST",
|
||||||
)
|
"/createRoom",
|
||||||
|
'["hello"]')
|
||||||
self.assertEquals(400, code)
|
self.assertEquals(400, code)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_conflict(self):
|
|
||||||
yield self.create_room_as("!aa:test", self.user_id)
|
|
||||||
|
|
||||||
# PUT with conflicting room ID, expect 409
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21aa%3Atest", "{}"
|
|
||||||
)
|
|
||||||
self.assertEquals(409, code)
|
|
||||||
|
|
||||||
|
|
||||||
class RoomTopicTestCase(RestTestCase):
|
class RoomTopicTestCase(RestTestCase):
|
||||||
""" Tests /rooms/$room_id/topic REST events. """
|
""" Tests /rooms/$room_id/topic REST events. """
|
||||||
|
@ -613,8 +558,6 @@ class RoomTopicTestCase(RestTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
self.path = "/rooms/%s/state/m.room.topic" % self.room_id
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
|
@ -640,7 +583,8 @@ class RoomTopicTestCase(RestTestCase):
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
# create the room
|
# create the room
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
self.path = "/rooms/%s/state/m.room.topic" % self.room_id
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
@ -717,7 +661,6 @@ class RoomMemberStateTestCase(RestTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
|
@ -742,7 +685,7 @@ class RoomMemberStateTestCase(RestTestCase):
|
||||||
|
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
@ -843,7 +786,6 @@ class RoomMessagesTestCase(RestTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
|
@ -868,7 +810,7 @@ class RoomMessagesTestCase(RestTestCase):
|
||||||
|
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -24,6 +24,7 @@ from synapse.api.constants import Membership
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
class RestTestCase(unittest.TestCase):
|
class RestTestCase(unittest.TestCase):
|
||||||
"""Contains extra helper functions to quickly and clearly perform a given
|
"""Contains extra helper functions to quickly and clearly perform a given
|
||||||
REST action, which isn't the focus of the test.
|
REST action, which isn't the focus of the test.
|
||||||
|
@ -40,18 +41,19 @@ class RestTestCase(unittest.TestCase):
|
||||||
return self.auth_user_id
|
return self.auth_user_id
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_room_as(self, room_id, room_creator, is_public=True, tok=None):
|
def create_room_as(self, room_creator, is_public=True, tok=None):
|
||||||
temp_id = self.auth_user_id
|
temp_id = self.auth_user_id
|
||||||
self.auth_user_id = room_creator
|
self.auth_user_id = room_creator
|
||||||
path = "/rooms/%s" % room_id
|
path = "/createRoom"
|
||||||
content = "{}"
|
content = "{}"
|
||||||
if not is_public:
|
if not is_public:
|
||||||
content = '{"visibility":"private"}'
|
content = '{"visibility":"private"}'
|
||||||
if tok:
|
if tok:
|
||||||
path = path + "?access_token=%s" % tok
|
path = path + "?access_token=%s" % tok
|
||||||
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
|
(code, response) = yield self.mock_resource.trigger("POST", path, content)
|
||||||
self.assertEquals(200, code, msg=str(response))
|
self.assertEquals(200, code, msg=str(response))
|
||||||
self.auth_user_id = temp_id
|
self.auth_user_id = temp_id
|
||||||
|
defer.returnValue(response["room_id"])
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def invite(self, room=None, src=None, targ=None, expect_code=200, tok=None):
|
def invite(self, room=None, src=None, targ=None, expect_code=200, tok=None):
|
||||||
|
|
|
@ -37,7 +37,11 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
mPresence.start();
|
mPresence.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.go = function(url) {
|
/**
|
||||||
|
* Open a given page.
|
||||||
|
* @param {String} url url of the page
|
||||||
|
*/
|
||||||
|
$scope.goToPage = function(url) {
|
||||||
$location.url(url);
|
$location.url(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -342,6 +342,64 @@ h1 {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** Recents ***/
|
||||||
|
.recentsTable {
|
||||||
|
max-width: 480px;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsTable tr {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.recentsTable td {
|
||||||
|
vertical-align: text-top;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoom {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoom:hover {
|
||||||
|
background-color: #f8f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoomSelected {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoomName {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-top: 7px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoomSummaryTS {
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 7em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recentsRoomSummary {
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Recents in the room page ***/
|
||||||
|
#roomRecentsTableWrapper {
|
||||||
|
float: left;
|
||||||
|
max-width: 320px;
|
||||||
|
margin-right: 20px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/*** Profile ***/
|
/*** Profile ***/
|
||||||
|
|
||||||
.profile-avatar {
|
.profile-avatar {
|
||||||
|
|
|
@ -20,6 +20,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
||||||
'LoginController',
|
'LoginController',
|
||||||
'RoomController',
|
'RoomController',
|
||||||
'HomeController',
|
'HomeController',
|
||||||
|
'RecentsController',
|
||||||
'SettingsController',
|
'SettingsController',
|
||||||
'UserController',
|
'UserController',
|
||||||
'matrixService',
|
'matrixService',
|
||||||
|
|
|
@ -97,7 +97,7 @@ angular.module('matrixService', [])
|
||||||
// Create a room
|
// Create a room
|
||||||
create: function(room_id, visibility) {
|
create: function(room_id, visibility) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
var path = "/rooms";
|
var path = "/createRoom";
|
||||||
|
|
||||||
return doRequest("POST", path, undefined, {
|
return doRequest("POST", path, undefined, {
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
|
@ -106,11 +106,20 @@ angular.module('matrixService', [])
|
||||||
},
|
},
|
||||||
|
|
||||||
// List all rooms joined or been invited to
|
// List all rooms joined or been invited to
|
||||||
rooms: function(from, to, limit) {
|
rooms: function(limit, feedback) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
|
|
||||||
var path = "/initialSync";
|
var path = "/initialSync";
|
||||||
|
|
||||||
return doRequest("GET", path);
|
var params = {};
|
||||||
|
if (limit) {
|
||||||
|
params.limit = limit;
|
||||||
|
}
|
||||||
|
if (feedback) {
|
||||||
|
params.feedback = feedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doRequest("GET", path, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Joins a room
|
// Joins a room
|
||||||
|
@ -124,7 +133,8 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
path = path.replace("$room_alias", room_alias);
|
path = path.replace("$room_alias", room_alias);
|
||||||
|
|
||||||
return doRequest("PUT", path, undefined, {});
|
// TODO: PUT with txn ID
|
||||||
|
return doRequest("POST", path, undefined, {});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invite a user to a room
|
// Invite a user to a room
|
||||||
|
@ -154,7 +164,7 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
// Retrieves the room ID corresponding to a room alias
|
// Retrieves the room ID corresponding to a room alias
|
||||||
resolveRoomAlias:function(room_alias) {
|
resolveRoomAlias:function(room_alias) {
|
||||||
var path = "/matrix/client/api/v1/ds/room/$room_alias";
|
var path = "/matrix/client/api/v1/directory/room/$room_alias";
|
||||||
room_alias = encodeURIComponent(room_alias);
|
room_alias = encodeURIComponent(room_alias);
|
||||||
|
|
||||||
path = path.replace("$room_alias", room_alias);
|
path = path.replace("$room_alias", room_alias);
|
||||||
|
@ -234,7 +244,7 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
// get a list of public rooms on your home server
|
// get a list of public rooms on your home server
|
||||||
publicRooms: function() {
|
publicRooms: function() {
|
||||||
var path = "/public/rooms"
|
var path = "/publicRooms"
|
||||||
return doRequest("GET", path);
|
return doRequest("GET", path);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -405,6 +415,40 @@ angular.module('matrixService', [])
|
||||||
config.version = configVersion;
|
config.version = configVersion;
|
||||||
localStorage.setItem("config", JSON.stringify(config));
|
localStorage.setItem("config", JSON.stringify(config));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/****** Room aliases management ******/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhance data returned by rooms() and publicRooms() by adding room_alias
|
||||||
|
* & room_display_name which are computed from data already retrieved from the server.
|
||||||
|
* @param {Array} data the response of rooms() and publicRooms()
|
||||||
|
* @returns {Array} the same array with enriched objects
|
||||||
|
*/
|
||||||
|
assignRoomAliases: function(data) {
|
||||||
|
for (var i=0; i<data.length; i++) {
|
||||||
|
var alias = this.getRoomIdToAliasMapping(data[i].room_id);
|
||||||
|
if (alias) {
|
||||||
|
// use the existing alias from storage
|
||||||
|
data[i].room_alias = alias;
|
||||||
|
data[i].room_display_name = alias;
|
||||||
|
}
|
||||||
|
else if (data[i].aliases && data[i].aliases[0]) {
|
||||||
|
// save the mapping
|
||||||
|
// TODO: select the smarter alias from the array
|
||||||
|
this.createRoomIdToAliasMapping(data[i].room_id, data[i].aliases[0]);
|
||||||
|
data[i].room_display_name = data[i].aliases[0];
|
||||||
|
}
|
||||||
|
else if (data[i].membership == "invite" && "inviter" in data[i]) {
|
||||||
|
data[i].room_display_name = data[i].inviter + "'s room"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// last resort use the room id
|
||||||
|
data[i].room_display_name = data[i].room_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
createRoomIdToAliasMapping: function(roomId, alias) {
|
createRoomIdToAliasMapping: function(roomId, alias) {
|
||||||
localStorage.setItem(MAPPING_PREFIX+roomId, alias);
|
localStorage.setItem(MAPPING_PREFIX+roomId, alias);
|
||||||
|
|
|
@ -16,12 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('HomeController', ['matrixService', 'mFileInput', 'mFileUpload', 'eventHandlerService'])
|
angular.module('HomeController', ['matrixService', 'eventHandlerService', 'RecentsController'])
|
||||||
.controller('HomeController', ['$scope', '$location', 'matrixService', 'mFileUpload', 'eventHandlerService', 'eventStreamService',
|
.controller('HomeController', ['$scope', '$location', 'matrixService', 'eventHandlerService', 'eventStreamService',
|
||||||
function($scope, $location, matrixService, mFileUpload, eventHandlerService, eventStreamService) {
|
function($scope, $location, matrixService, eventHandlerService, eventStreamService) {
|
||||||
|
|
||||||
$scope.config = matrixService.config();
|
$scope.config = matrixService.config();
|
||||||
$scope.rooms = {};
|
|
||||||
$scope.public_rooms = [];
|
$scope.public_rooms = [];
|
||||||
$scope.newRoomId = "";
|
$scope.newRoomId = "";
|
||||||
$scope.feedback = "";
|
$scope.feedback = "";
|
||||||
|
@ -32,72 +31,18 @@ angular.module('HomeController', ['matrixService', 'mFileInput', 'mFileUpload',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.goToRoom = {
|
$scope.goToRoom = {
|
||||||
room_id: "",
|
room_id: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.joinAlias = {
|
$scope.joinAlias = {
|
||||||
room_alias: "",
|
room_alias: ""
|
||||||
};
|
|
||||||
|
|
||||||
$scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
|
|
||||||
var config = matrixService.config();
|
|
||||||
if (event.state_key === config.user_id && event.content.membership === "invite") {
|
|
||||||
console.log("Invited to room " + event.room_id);
|
|
||||||
// FIXME push membership to top level key to match /im/sync
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var assignRoomAliases = function(data) {
|
|
||||||
for (var i=0; i<data.length; i++) {
|
|
||||||
var alias = matrixService.getRoomIdToAliasMapping(data[i].room_id);
|
|
||||||
if (alias) {
|
|
||||||
// use the existing alias from storage
|
|
||||||
data[i].room_alias = alias;
|
|
||||||
data[i].room_display_name = alias;
|
|
||||||
}
|
|
||||||
else if (data[i].aliases && data[i].aliases[0]) {
|
|
||||||
// save the mapping
|
|
||||||
// TODO: select the smarter alias from the array
|
|
||||||
matrixService.createRoomIdToAliasMapping(data[i].room_id, data[i].aliases[0]);
|
|
||||||
data[i].room_display_name = data[i].aliases[0];
|
|
||||||
}
|
|
||||||
else if (data[i].membership == "invite" && "inviter" in data[i]) {
|
|
||||||
data[i].room_display_name = data[i].inviter + "'s room"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// last resort use the room id
|
|
||||||
data[i].room_display_name = data[i].room_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var refresh = function() {
|
var refresh = function() {
|
||||||
// List all rooms joined or been invited to
|
|
||||||
matrixService.rooms(1,true).then(
|
|
||||||
function(response) {
|
|
||||||
var data = assignRoomAliases(response.data.rooms);
|
|
||||||
$scope.feedback = "Success";
|
|
||||||
for (var i=0; i<data.length; i++) {
|
|
||||||
$scope.rooms[data[i].room_id] = data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
var presence = response.data.presence;
|
|
||||||
for (var i = 0; i < presence.length; ++i) {
|
|
||||||
eventHandlerService.handleEvent(presence[i], false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
$scope.feedback = "Failure: " + error.data;
|
|
||||||
});
|
|
||||||
|
|
||||||
matrixService.publicRooms().then(
|
matrixService.publicRooms().then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.public_rooms = assignRoomAliases(response.data.chunk);
|
$scope.public_rooms = matrixService.assignRoomAliases(response.data.chunk);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>My rooms</h3>
|
<h3>Recents</h3>
|
||||||
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
<div class="rooms" ng-repeat="(rm_id, room) in rooms">
|
|
||||||
<div>
|
|
||||||
<a href="#/room/{{ room.room_alias ? room.room_alias : rm_id }}" >{{ room.room_display_name }}</a> {{room.membership === 'invite' ? ' (invited)' : ''}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<h3>Public rooms</h3>
|
<h3>Public rooms</h3>
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
<script src="app-filter.js"></script>
|
<script src="app-filter.js"></script>
|
||||||
<script src="home/home-controller.js"></script>
|
<script src="home/home-controller.js"></script>
|
||||||
<script src="login/login-controller.js"></script>
|
<script src="login/login-controller.js"></script>
|
||||||
|
<script src="recents/recents-controller.js"></script>
|
||||||
|
<script src="recents/recents-filter.js"></script>
|
||||||
<script src="room/room-controller.js"></script>
|
<script src="room/room-controller.js"></script>
|
||||||
<script src="room/room-directive.js"></script>
|
<script src="room/room-directive.js"></script>
|
||||||
<script src="settings/settings-controller.js"></script>
|
<script src="settings/settings-controller.js"></script>
|
||||||
|
@ -37,7 +39,7 @@
|
||||||
<header id="header">
|
<header id="header">
|
||||||
<!-- Do not show buttons on the login page -->
|
<!-- Do not show buttons on the login page -->
|
||||||
<div id="header-buttons" ng-hide="'/login' == location ">
|
<div id="header-buttons" ng-hide="'/login' == location ">
|
||||||
<button ng-click='go("settings")'>Settings</button>
|
<button ng-click='goToPage("settings")'>Settings</button>
|
||||||
<button ng-click="logout()">Log out</button>
|
<button ng-click="logout()">Log out</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
70
webclient/recents/recents-controller.js
Normal file
70
webclient/recents/recents-controller.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
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';
|
||||||
|
|
||||||
|
angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
|
||||||
|
.controller('RecentsController', ['$scope', 'matrixService', 'eventHandlerService', 'eventStreamService',
|
||||||
|
function($scope, matrixService, eventHandlerService, eventStreamService) {
|
||||||
|
$scope.rooms = {};
|
||||||
|
|
||||||
|
// $scope of the parent where the recents component is included can override this value
|
||||||
|
// in order to highlight a specific room in the list
|
||||||
|
$scope.recentsSelectedRoomID;
|
||||||
|
|
||||||
|
$scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
|
||||||
|
var config = matrixService.config();
|
||||||
|
if (event.state_key === config.user_id && event.content.membership === "invite") {
|
||||||
|
console.log("Invited to room " + event.room_id);
|
||||||
|
// FIXME push membership to top level key to match /im/sync
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var refresh = function() {
|
||||||
|
// List all rooms joined or been invited to
|
||||||
|
matrixService.rooms(1, false).then(
|
||||||
|
function(response) {
|
||||||
|
var data = matrixService.assignRoomAliases(response.data.rooms);
|
||||||
|
for (var i=0; i<data.length; i++) {
|
||||||
|
$scope.rooms[data[i].room_id] = data[i];
|
||||||
|
|
||||||
|
// Create a shortcut for the last message of this room
|
||||||
|
if (data[i].messages && data[i].messages.chunk && data[i].messages.chunk[0]) {
|
||||||
|
$scope.rooms[data[i].room_id].lastMsg = data[i].messages.chunk[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var presence = response.data.presence;
|
||||||
|
for (var i = 0; i < presence.length; ++i) {
|
||||||
|
eventHandlerService.handleEvent(presence[i], false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Failure: " + error.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.onInit = function() {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
}]);
|
||||||
|
|
47
webclient/recents/recents-filter.js
Normal file
47
webclient/recents/recents-filter.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
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';
|
||||||
|
|
||||||
|
angular.module('RecentsController')
|
||||||
|
.filter('orderRecents', function() {
|
||||||
|
return function(rooms) {
|
||||||
|
|
||||||
|
// Transform the dict into an array
|
||||||
|
// The key, room_id, is already in value objects
|
||||||
|
var filtered = [];
|
||||||
|
angular.forEach(rooms, function(value, key) {
|
||||||
|
filtered.push( value );
|
||||||
|
});
|
||||||
|
|
||||||
|
// And time sort them
|
||||||
|
// The room with the lastest message at first
|
||||||
|
filtered.sort(function (a, b) {
|
||||||
|
// Invite message does not have a body message nor ts
|
||||||
|
// Puth them at the top of the list
|
||||||
|
if (undefined === a.lastMsg) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (undefined === b.lastMsg) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return b.lastMsg.ts - a.lastMsg.ts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
});
|
56
webclient/recents/recents.html
Normal file
56
webclient/recents/recents.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<div ng-controller="RecentsController" data-ng-init="onInit()">
|
||||||
|
<table class="recentsTable">
|
||||||
|
<tbody ng-repeat="(rm_id, room) in rooms | orderRecents"
|
||||||
|
ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )"
|
||||||
|
class ="recentsRoom"
|
||||||
|
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
|
||||||
|
<tr>
|
||||||
|
<td class="recentsRoomName">
|
||||||
|
{{ room.room_display_name }}
|
||||||
|
</td>
|
||||||
|
<td class="recentsRoomSummaryTS">
|
||||||
|
{{ (room.lastMsg.ts) | date:'MMM d HH:mm' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="recentsRoomSummary">
|
||||||
|
|
||||||
|
<div ng-show="room.membership === 'invite'" >
|
||||||
|
{{ room.inviter }} invited you
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-hide="room.membership === 'invite'" ng-switch="room.lastMsg.type" >
|
||||||
|
<div ng-switch-when="m.room.member">
|
||||||
|
{{ room.lastMsg.user_id }}
|
||||||
|
{{ {"join": "joined", "leave": "left", "invite": "invited"}[room.lastMsg.content.membership] }}
|
||||||
|
{{ room.lastMsg.content.membership === "invite" ? (room.lastMsg.state_key || '') : '' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-switch-when="m.room.message">
|
||||||
|
<div ng-switch="room.lastMsg.content.msgtype">
|
||||||
|
<div ng-switch-when="m.text">
|
||||||
|
{{ room.lastMsg.user_id }} :
|
||||||
|
<span ng-bind-html="(room.lastMsg.content.body) | linky:'_blank'">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-switch-when="m.image">
|
||||||
|
{{ room.lastMsg.user_id }} sent an image
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-switch-default>
|
||||||
|
{{ room.lastMsg.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-switch-default>
|
||||||
|
{{ room.lastMsg }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -327,6 +327,9 @@ angular.module('RoomController', ['ngSanitize', 'mUtilities'])
|
||||||
var onInit2 = function() {
|
var onInit2 = function() {
|
||||||
eventHandlerService.reInitRoom($scope.room_id);
|
eventHandlerService.reInitRoom($scope.room_id);
|
||||||
|
|
||||||
|
// Make recents highlight the current room
|
||||||
|
$scope.recentsSelectedRoomID = $scope.room_id;
|
||||||
|
|
||||||
// Join the room
|
// Join the room
|
||||||
matrixService.join($scope.room_id).then(
|
matrixService.join($scope.room_id).then(
|
||||||
function() {
|
function() {
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
<div id="roomName">
|
<div id="roomName">
|
||||||
{{ room_alias || room_id }}
|
{{ room_alias || room_id }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="roomRecentsTableWrapper">
|
||||||
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="usersTableWrapper">
|
<div id="usersTableWrapper">
|
||||||
<table id="usersTable">
|
<table id="usersTable">
|
||||||
<tr ng-repeat="member in members | orderMembersList">
|
<tr ng-repeat="member in members | orderMembersList">
|
||||||
|
|
Loading…
Reference in a new issue