mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2024-12-26 02:49:24 -05:00
Merge branch 'develop' of github.com:matrix-org/synapse into room_config
This commit is contained in:
commit
10efca1a74
7
WISHLIST.rst
Normal file
7
WISHLIST.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Broad-sweeping stuff which would be nice to have
|
||||||
|
================================================
|
||||||
|
|
||||||
|
- Additional SQL backends beyond sqlite
|
||||||
|
- homeserver implementation in go
|
||||||
|
- homeserver implementation in node.js
|
||||||
|
- client SDKs
|
@ -60,7 +60,7 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
"complete_usernames": "on",
|
"complete_usernames": "on",
|
||||||
"send_delivery_receipts": "on"
|
"send_delivery_receipts": "on"
|
||||||
}
|
}
|
||||||
self.path_prefix = "/matrix/client/api/v1"
|
self.path_prefix = "/_matrix/client/api/v1"
|
||||||
self.event_stream_token = "END"
|
self.event_stream_token = "END"
|
||||||
self.prompt = ">>> "
|
self.prompt = ">>> "
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_emailrequest(self, args):
|
def _do_emailrequest(self, args):
|
||||||
url = self._identityServerUrl()+"/matrix/identity/api/v1/validate/email/requestToken"
|
url = self._identityServerUrl()+"/_matrix/identity/api/v1/validate/email/requestToken"
|
||||||
|
|
||||||
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
||||||
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
||||||
@ -274,7 +274,7 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_emailvalidate(self, args):
|
def _do_emailvalidate(self, args):
|
||||||
url = self._identityServerUrl()+"/matrix/identity/api/v1/validate/email/submitToken"
|
url = self._identityServerUrl()+"/_matrix/identity/api/v1/validate/email/submitToken"
|
||||||
|
|
||||||
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
||||||
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
||||||
@ -294,7 +294,7 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_3pidbind(self, args):
|
def _do_3pidbind(self, args):
|
||||||
url = self._identityServerUrl()+"/matrix/identity/api/v1/3pid/bind"
|
url = self._identityServerUrl()+"/_matrix/identity/api/v1/3pid/bind"
|
||||||
|
|
||||||
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
|
||||||
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
headers={'Content-Type': ['application/x-www-form-urlencoded']})
|
||||||
@ -360,14 +360,14 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
def _do_invite(self, roomid, userstring):
|
def _do_invite(self, roomid, userstring):
|
||||||
if (not userstring.startswith('@') and
|
if (not userstring.startswith('@') and
|
||||||
self._is_on("complete_usernames")):
|
self._is_on("complete_usernames")):
|
||||||
url = self._identityServerUrl()+"/matrix/identity/api/v1/lookup"
|
url = self._identityServerUrl()+"/_matrix/identity/api/v1/lookup"
|
||||||
|
|
||||||
json_res = yield self.http_client.do_request("GET", url, qparams={'medium':'email','address':userstring})
|
json_res = yield self.http_client.do_request("GET", url, qparams={'medium':'email','address':userstring})
|
||||||
|
|
||||||
mxid = None
|
mxid = None
|
||||||
|
|
||||||
if 'mxid' in json_res and 'signatures' in json_res:
|
if 'mxid' in json_res and 'signatures' in json_res:
|
||||||
url = self._identityServerUrl()+"/matrix/identity/api/v1/pubkey/ed25519"
|
url = self._identityServerUrl()+"/_matrix/identity/api/v1/pubkey/ed25519"
|
||||||
|
|
||||||
pubKey = None
|
pubKey = None
|
||||||
pubKeyObj = yield self.http_client.do_request("GET", url)
|
pubKeyObj = yield self.http_client.do_request("GET", url)
|
||||||
|
@ -30,7 +30,7 @@ Registration
|
|||||||
The aim of registration is to get a user ID and access token which you will need
|
The aim of registration is to get a user ID and access token which you will need
|
||||||
when accessing other APIs::
|
when accessing other APIs::
|
||||||
|
|
||||||
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/register"
|
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8080/_matrix/client/api/v1/register"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
||||||
@ -51,13 +51,13 @@ Login
|
|||||||
-----
|
-----
|
||||||
The aim when logging in is to get an access token for your existing user ID::
|
The aim when logging in is to get an access token for your existing user ID::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/matrix/client/api/v1/login"
|
curl -XGET "http://localhost:8080/_matrix/client/api/v1/login"
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "m.login.password"
|
"type": "m.login.password"
|
||||||
}
|
}
|
||||||
|
|
||||||
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/login"
|
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/_matrix/client/api/v1/login"
|
||||||
|
|
||||||
{
|
{
|
||||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
||||||
@ -87,7 +87,7 @@ Creating a room
|
|||||||
If you want to send a message to someone, you have to be in a room with them. To
|
If you want to send a message to someone, you have to be in a room with them. To
|
||||||
create a room::
|
create a room::
|
||||||
|
|
||||||
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8080/matrix/client/api/v1/rooms?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8080/_matrix/client/api/v1/rooms?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
||||||
|
|
||||||
{
|
{
|
||||||
"room_alias": "#tutorial:localhost",
|
"room_alias": "#tutorial:localhost",
|
||||||
@ -105,7 +105,7 @@ Sending messages
|
|||||||
----------------
|
----------------
|
||||||
You can now send messages to this room::
|
You can now send messages to this room::
|
||||||
|
|
||||||
curl -XPUT -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/messages/%40example%3Alocalhost/msgid1?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPUT -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/messages/%40example%3Alocalhost/msgid1?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
||||||
|
|
||||||
NB: There are no limitations to the types of messages which can be exchanged.
|
NB: There are no limitations to the types of messages which can be exchanged.
|
||||||
The only requirement is that ``"msgtype"`` is specified.
|
The only requirement is that ``"msgtype"`` is specified.
|
||||||
@ -127,7 +127,7 @@ Inviting a user to a room
|
|||||||
-------------------------
|
-------------------------
|
||||||
You can directly invite a user to a room like so::
|
You can directly invite a user to a room like so::
|
||||||
|
|
||||||
curl -XPUT -d '{"membership":"invite"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
curl -XPUT -d '{"membership":"invite"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
|
||||||
|
|
||||||
This informs ``@myfriend:localhost`` of the room ID
|
This informs ``@myfriend:localhost`` of the room ID
|
||||||
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
|
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
|
||||||
@ -137,7 +137,7 @@ Joining a room via an invite
|
|||||||
If you receive an invite, you can join the room by changing the membership to
|
If you receive an invite, you can join the room by changing the membership to
|
||||||
join::
|
join::
|
||||||
|
|
||||||
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
||||||
|
|
||||||
NB: Only the person invited (``@myfriend:localhost``) can change the membership
|
NB: Only the person invited (``@myfriend:localhost``) can change the membership
|
||||||
state to ``"join"``.
|
state to ``"join"``.
|
||||||
@ -147,7 +147,7 @@ Joining a room via an alias
|
|||||||
Alternatively, if you know the room alias for this room and the room config
|
Alternatively, if you know the room alias for this room and the room config
|
||||||
allows it, you can directly join a room via the alias::
|
allows it, you can directly join a room via the alias::
|
||||||
|
|
||||||
curl -XPUT -d '{}' "http://localhost:8080/matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XPUT -d '{}' "http://localhost:8080/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
||||||
|
|
||||||
{
|
{
|
||||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
||||||
@ -173,7 +173,7 @@ Getting all state
|
|||||||
If the client doesn't know any information on the rooms the user is
|
If the client doesn't know any information on the rooms the user is
|
||||||
invited/joined on, they can get all the user's state for all rooms::
|
invited/joined on, they can get all the user's state for all rooms::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
curl -XGET "http://localhost:8080/_matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -236,7 +236,7 @@ all of the messages and feedback for these rooms. This can be a LOT of data. You
|
|||||||
may just want the most recent message for each room. This can be achieved by
|
may just want the most recent message for each room. This can be achieved by
|
||||||
applying pagination stream parameters to this request::
|
applying pagination stream parameters to this request::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
|
curl -XGET "http://localhost:8080/_matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -271,7 +271,7 @@ Getting live state
|
|||||||
Once you know which rooms the client has previously interacted with, you need to
|
Once you know which rooms the client has previously interacted with, you need to
|
||||||
listen for incoming events. This can be done like so::
|
listen for incoming events. This can be done like so::
|
||||||
|
|
||||||
curl -XGET "http://localhost:8080/matrix/client/api/v1/events?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END"
|
curl -XGET "http://localhost:8080/_matrix/client/api/v1/events?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END"
|
||||||
|
|
||||||
{
|
{
|
||||||
"chunk": [],
|
"chunk": [],
|
||||||
|
@ -306,11 +306,11 @@ POST requests MUST be submitted as application/json.
|
|||||||
All paths MUST be namespaced by the version of the API being used. This should
|
All paths MUST be namespaced by the version of the API being used. This should
|
||||||
be:
|
be:
|
||||||
|
|
||||||
/matrix/client/api/v1
|
/_matrix/client/api/v1
|
||||||
|
|
||||||
All REST paths in this section MUST be prefixed with this. E.g.
|
All REST paths in this section MUST be prefixed with this. E.g.
|
||||||
REST Path: /rooms/$room_id
|
REST Path: /rooms/$room_id
|
||||||
Absolute Path: /matrix/client/api/v1/rooms/$room_id
|
Absolute Path: /_matrix/client/api/v1/rooms/$room_id
|
||||||
|
|
||||||
Registration
|
Registration
|
||||||
============
|
============
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"resourcePath": "/directory",
|
"resourcePath": "/directory",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"resourcePath": "/events",
|
"resourcePath": "/events",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"path": "/login"
|
"path": "/login"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"resourcePath": "/presence",
|
"resourcePath": "/presence",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"resourcePath": "/profile",
|
"resourcePath": "/profile",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
"path": "/register"
|
"path": "/register"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
"basePath": "http://localhost:8080/_matrix/client/api/v1",
|
||||||
"resourcePath": "/rooms",
|
"resourcePath": "/rooms",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@ -14,12 +14,12 @@
|
|||||||
},
|
},
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/send/{eventType}/{txnId}",
|
"path": "/rooms/{roomId}/send/{eventType}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Send a generic non-state event to this room.",
|
"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}",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "EventId",
|
"type": "EventId",
|
||||||
"nickname": "send_non_state_event",
|
"nickname": "send_non_state_event",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -46,13 +46,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -104,12 +97,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/send/m.room.message/{txnId}",
|
"path": "/rooms/{roomId}/send/m.room.message",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Send a message in this room.",
|
"summary": "Send a message in this room.",
|
||||||
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "EventId",
|
"type": "EventId",
|
||||||
"nickname": "send_message",
|
"nickname": "send_message",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -129,13 +122,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -195,12 +181,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/send/m.room.message.feedback/{txnId}",
|
"path": "/rooms/{roomId}/send/m.room.message.feedback",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Send feedback to a message.",
|
"summary": "Send feedback to a message.",
|
||||||
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message.feedback",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "EventId",
|
"type": "EventId",
|
||||||
"nickname": "send_feedback",
|
"nickname": "send_feedback",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -220,13 +206,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responseMessages": [
|
"responseMessages": [
|
||||||
@ -239,12 +218,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/invite/{txnId}",
|
"path": "/rooms/{roomId}/invite",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Invite a user to this room.",
|
"summary": "Invite a user to this room.",
|
||||||
"notes": "This operation can also be done as a POST to /rooms/{roomId}/invite",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"nickname": "invite",
|
"nickname": "invite",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -258,13 +237,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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",
|
"name": "body",
|
||||||
"description": "The user to invite.",
|
"description": "The user to invite.",
|
||||||
@ -277,12 +249,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/join/{txnId}",
|
"path": "/rooms/{roomId}/join",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Join this room.",
|
"summary": "Join this room.",
|
||||||
"notes": "This operation can also be done as a POST to /rooms/{roomId}/join",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"nickname": "join_room",
|
"nickname": "join_room",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -295,25 +267,18 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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}",
|
"path": "/rooms/{roomId}/leave",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "POST",
|
||||||
"summary": "Leave this room.",
|
"summary": "Leave this room.",
|
||||||
"notes": "This operation can also be done as a POST to /rooms/{roomId}/leave",
|
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"type": "void",
|
"type": "void",
|
||||||
"nickname": "leave",
|
"nickname": "leave",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -326,13 +291,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -476,7 +434,7 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"description": "The desired configuration for the room.",
|
"description": "The desired configuration for the room. This operation can also be done as a PUT by suffixing /{txnId}.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "RoomConfig",
|
"type": "RoomConfig",
|
||||||
"paramType": "body"
|
"paramType": "body"
|
||||||
|
@ -155,7 +155,7 @@ Protocol URLs
|
|||||||
|
|
||||||
All these URLs are namespaced within a prefix of
|
All these URLs are namespaced within a prefix of
|
||||||
|
|
||||||
/matrix/federation/v1/...
|
/_matrix/federation/v1/...
|
||||||
|
|
||||||
For active pushing of messages representing live activity "as it happens":
|
For active pushing of messages representing live activity "as it happens":
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ namespaced to the home server which allocated the account and looks like::
|
|||||||
|
|
||||||
@localpart:domain
|
@localpart:domain
|
||||||
|
|
||||||
The ``localpart`` of a user ID may be a user name, or an opaque ID identifying this user.
|
The ``localpart`` of a user ID may be a user name, or an opaque ID identifying this user. They are
|
||||||
|
case-insensitive.
|
||||||
|
|
||||||
A "Home Server" is a server which provides C-S APIs and has the ability to federate with other HSes.
|
A "Home Server" is a server which provides C-S APIs and has the ability to federate with other HSes.
|
||||||
It is typically responsible for multiple clients. "Federation" is the term used to describe the
|
It is typically responsible for multiple clients. "Federation" is the term used to describe the
|
||||||
@ -60,7 +60,8 @@ identified via a "Room ID", which look like::
|
|||||||
|
|
||||||
There is exactly one room ID for each room. Whilst the room ID does contain a
|
There is exactly one room ID for each room. Whilst the room ID does contain a
|
||||||
domain, it is simply for namespacing room IDs. The room does NOT reside on the
|
domain, it is simply for namespacing room IDs. The room does NOT reside on the
|
||||||
domain specified. Room IDs are not meant to be human readable.
|
domain specified. Room IDs are not meant to be human readable. They ARE
|
||||||
|
case-sensitive.
|
||||||
|
|
||||||
The following diagram shows an ``m.room.message`` event being sent in the room
|
The following diagram shows an ``m.room.message`` event being sent in the room
|
||||||
``!qporfwt:matrix.org``::
|
``!qporfwt:matrix.org``::
|
||||||
@ -102,10 +103,10 @@ Each room can also have multiple "Room Aliases", which looks like::
|
|||||||
|
|
||||||
A room alias "points" to a room ID. The room ID the alias is pointing to can be obtained
|
A room alias "points" to a room ID. The room ID the alias is pointing to can be obtained
|
||||||
by visiting the domain specified. Room aliases are designed to be human readable strings
|
by visiting the domain specified. Room aliases are designed to be human readable strings
|
||||||
which can be used to publicise rooms. Note that the mapping from a room alias to a
|
which can be used to publicise rooms. They are case-insensitive. Note that the mapping
|
||||||
room ID is not fixed, and may change over time to point to a different room ID. For this
|
from a room alias to a room ID is not fixed, and may change over time to point to a
|
||||||
reason, Clients SHOULD resolve the room alias to a room ID once and then use that ID on
|
different room ID. For this reason, Clients SHOULD resolve the room alias to a room ID
|
||||||
subsequent requests.
|
once and then use that ID on subsequent requests.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@ -214,24 +215,150 @@ In contrast, these are invalid requests::
|
|||||||
"key": "This is a put but it is missing a txnId."
|
"key": "This is a put but it is missing a txnId."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- TODO: All strings everywhere are UTF-8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Receiving live updates on a client
|
Receiving live updates on a client
|
||||||
----------------------------------
|
----------------------------------
|
||||||
- C-S longpoll event stream
|
Clients can receive new events by long-polling the home server. This will hold open the
|
||||||
- Concept of start/end tokens.
|
HTTP connection for a short period of time waiting for new events, returning early if an
|
||||||
- Mention /initialSync to get token.
|
event occurs. This is called the "Event Stream". All events which the client is authorised
|
||||||
|
to view will appear in the event stream. When the stream is closed, an ``end`` token is
|
||||||
|
returned. This token can be used in the next request to continue where the client left off.
|
||||||
|
|
||||||
|
When the client first logs in, they will need to initially synchronise with their home
|
||||||
|
server. This is achieved via the ``/initialSync`` API. This API also returns an ``end``
|
||||||
|
token which can be used with the event stream.
|
||||||
|
|
||||||
Rooms
|
Rooms
|
||||||
=====
|
=====
|
||||||
- How are they created? PDU anchor point: "root of the tree".
|
|
||||||
|
Creation
|
||||||
|
--------
|
||||||
|
To create a room, a client has to use the ``/createRoom`` API. There are various options
|
||||||
|
which can be set when creating a room:
|
||||||
|
|
||||||
|
``visibility``
|
||||||
|
Type:
|
||||||
|
String
|
||||||
|
Optional:
|
||||||
|
Yes
|
||||||
|
Value:
|
||||||
|
Either ``public`` or ``private``.
|
||||||
|
Description:
|
||||||
|
A ``public`` visibility indicates that the room will be shown in the public room list. A
|
||||||
|
``private`` visibility will hide the room from the public room list. Rooms default to
|
||||||
|
``public`` visibility if this key is not included.
|
||||||
|
|
||||||
|
``room_alias_name``
|
||||||
|
Type:
|
||||||
|
String
|
||||||
|
Optional:
|
||||||
|
Yes
|
||||||
|
Value:
|
||||||
|
The room alias localpart.
|
||||||
|
Description:
|
||||||
|
If this is included, a room alias will be created and mapped to the newly created room.
|
||||||
|
The alias will belong on the same home server which created the room, e.g.
|
||||||
|
``!qadnasoi:domain.com >>> #room_alias_name:domain.com``
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
{
|
||||||
|
"visibility": "public",
|
||||||
|
"room_alias_name": "the pub"
|
||||||
|
}
|
||||||
|
|
||||||
|
- TODO: This creates a room creation event which serves as the root of the PDU graph for this room.
|
||||||
|
|
||||||
|
Modifying aliases
|
||||||
|
-----------------
|
||||||
- Adding / removing aliases.
|
- Adding / removing aliases.
|
||||||
- Invite/join dance
|
|
||||||
- State and non-state data (+extensibility)
|
|
||||||
|
|
||||||
TODO : Room permissions / config / power levels.
|
Permissions
|
||||||
|
-----------
|
||||||
|
- TODO : Room permissions / config / power levels. What they are. How do they work. Examples.
|
||||||
|
|
||||||
Messages
|
Joining rooms
|
||||||
========
|
-------------
|
||||||
|
- What is joining? What permissions / access does it give you? How does this affect /initialSync?
|
||||||
|
- API to hit (``/join/$alias or id``). Explain how alias joining works (auto-resolving). See "Room events" for more info.
|
||||||
|
- What does the home server have to do?
|
||||||
|
- Rooms that DON'T need an invite to join. This follows through onto inviting users section.
|
||||||
|
- Outline invite join dance?
|
||||||
|
|
||||||
|
|
||||||
|
Inviting users
|
||||||
|
--------------
|
||||||
|
- Can invite users to a room if the room config key TODO is set to TODO. Must have required power level.
|
||||||
|
- Outline invite join dance. What is it? Why is it required? How does it work?
|
||||||
|
- What does the home server have to do?
|
||||||
|
|
||||||
|
The purpose of inviting users to a room is to notify them that the room exists
|
||||||
|
so they can choose to become a member of that room. Some rooms require that all
|
||||||
|
users who join a room are previously invited to it (an "invite-only" room).
|
||||||
|
Whether a given room is an "invite-only" room is determined by the room config
|
||||||
|
key ``TODO``. It can have one of the following values:
|
||||||
|
|
||||||
|
- TODO Room config invite only value explanation
|
||||||
|
- TODO Room config free-to-join value explanation
|
||||||
|
|
||||||
|
Only users who have a membership state of ``join`` in a room can invite new
|
||||||
|
users to said room. The person being invited must not be in the ``join`` state
|
||||||
|
in the room. The fully-qualified user ID must be specified when inviting a user,
|
||||||
|
as the user may reside on a different home server. To invite a user, send the
|
||||||
|
following request to ``/rooms/<room id>/invite``, which will manage the
|
||||||
|
entire invitation process::
|
||||||
|
|
||||||
|
{
|
||||||
|
"user_id": "<user id to invite>"
|
||||||
|
}
|
||||||
|
|
||||||
|
Alternatively, the membership state for this user in this room can be modified
|
||||||
|
directly by sending the following request to
|
||||||
|
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
|
||||||
|
|
||||||
|
{
|
||||||
|
"membership": "invite"
|
||||||
|
}
|
||||||
|
|
||||||
|
See the "Room events" section for more information on ``m.room.member``.
|
||||||
|
|
||||||
|
- TODO: In what circumstances will this NOT be equivalent to ``/invite``?
|
||||||
|
|
||||||
|
Leaving rooms
|
||||||
|
-------------
|
||||||
|
- API to hit (``$roomid/leave``). See "Room events" for more info.
|
||||||
|
- Must be joined to leave. How does this affect /initialSync?
|
||||||
|
- Not ever being in a room is NOT equivalent to have left it (due to membership: leave).
|
||||||
|
- Need to be re-invited if invite-only room.
|
||||||
|
- If no more HSes in room, can delete room?
|
||||||
|
- Is there a dance?
|
||||||
|
|
||||||
|
Events in a room
|
||||||
|
----------------
|
||||||
|
- Split into state and non-state data
|
||||||
|
- Explain what they are, semantics, give examples of clobbering / not, use cases (msgs vs room names).
|
||||||
|
Not too much detail on the actual event contents.
|
||||||
|
- API to hit.
|
||||||
|
- Extensibility provided by the API for custom events. Examples.
|
||||||
|
- How this hooks into ``initialSync``.
|
||||||
|
- See the "Room Events" section for actual spec on each type.
|
||||||
|
|
||||||
|
Syncing a room
|
||||||
|
--------------
|
||||||
|
- Single room initial sync. API to hit. Why it might be used (lazy loading)
|
||||||
|
|
||||||
|
Getting grouped state events
|
||||||
|
----------------------------
|
||||||
|
- ``/members`` and ``/messages`` and the events they return.
|
||||||
|
- ``/state`` and it returns ALL THE THINGS.
|
||||||
|
|
||||||
|
Room Events
|
||||||
|
===========
|
||||||
|
|
||||||
This specification outlines several standard event types, all of which are
|
This specification outlines several standard event types, all of which are
|
||||||
prefixed with ``m.``
|
prefixed with ``m.``
|
||||||
@ -244,7 +371,8 @@ State messages
|
|||||||
- m.room.config
|
- m.room.config
|
||||||
- m.room.invite_join
|
- m.room.invite_join
|
||||||
|
|
||||||
What are they, when are they used, what do they contain, how should they be used
|
What are they, when are they used, what do they contain, how should they be used.
|
||||||
|
Link back to explanatory sections (e.g. invite/join/leave sections for m.room.member)
|
||||||
|
|
||||||
Non-state messages
|
Non-state messages
|
||||||
------------------
|
------------------
|
||||||
|
@ -120,7 +120,7 @@ def make_graph(pdus, room, filename_prefix):
|
|||||||
def get_pdus(host, room):
|
def get_pdus(host, room):
|
||||||
transaction = json.loads(
|
transaction = json.loads(
|
||||||
urllib2.urlopen(
|
urllib2.urlopen(
|
||||||
"http://%s/matrix/federation/v1/context/%s/" % (host, room)
|
"http://%s/_matrix/federation/v1/context/%s/" % (host, room)
|
||||||
).read()
|
).read()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ $('.login').live('click', function() {
|
|||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/login",
|
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
@ -25,11 +25,12 @@ $('.login').live('click', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
|
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.length; ++i) {
|
var rooms = data.rooms;
|
||||||
data[i].latest_message = data[i].messages.chunk[0].content.body;
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
addRoom(data[i]);
|
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
||||||
|
addRoom(rooms[i]);
|
||||||
}
|
}
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
alert(JSON.stringify($.parseJSON(err.responseText)));
|
||||||
@ -43,7 +44,7 @@ $('.createRoom').live('click', function() {
|
|||||||
data.room_alias_name = roomAlias;
|
data.room_alias_name = roomAlias;
|
||||||
}
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
|
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
@ -78,11 +79,9 @@ $('.sendMessage').live('click', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
|
|
||||||
url = url.replace("$msgid", msgId);
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
@ -91,7 +90,7 @@ $('.sendMessage').live('click', function() {
|
|||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "PUT",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
|
@ -7,7 +7,7 @@ var eventStreamInfo = {
|
|||||||
var roomInfo = [];
|
var roomInfo = [];
|
||||||
|
|
||||||
var longpollEventStream = function() {
|
var longpollEventStream = function() {
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/events?access_token=$token&from=$from";
|
var url = "http://localhost:8080/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
url = url.replace("$from", eventStreamInfo.from);
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ $('.login').live('click', function() {
|
|||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/login",
|
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
@ -65,14 +65,15 @@ $('.login').live('click', function() {
|
|||||||
|
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
$("#roomId").val("");
|
$("#roomId").val("");
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
|
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.length; ++i) {
|
var rooms = data.rooms;
|
||||||
if ("messages" in data[i]) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
data[i].latest_message = data[i].messages.chunk[0].content.body;
|
if ("messages" in rooms[i]) {
|
||||||
|
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomInfo = data;
|
roomInfo = rooms;
|
||||||
setRooms(roomInfo);
|
setRooms(roomInfo);
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
alert(JSON.stringify($.parseJSON(err.responseText)));
|
||||||
@ -92,17 +93,14 @@ $('.sendMessage').live('click', function() {
|
|||||||
|
|
||||||
var sendMessage = function(roomId) {
|
var sendMessage = function(roomId) {
|
||||||
var body = "jsfiddle message @" + $.now();
|
var body = "jsfiddle message @" + $.now();
|
||||||
var msgId = $.now();
|
|
||||||
|
|
||||||
if (roomId.length === 0) {
|
if (roomId.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
|
|
||||||
url = url.replace("$msgid", msgId);
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
@ -111,7 +109,7 @@ var sendMessage = function(roomId) {
|
|||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "PUT",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
|
@ -10,7 +10,7 @@ var viewingRoomId;
|
|||||||
|
|
||||||
// ************** Event Streaming **************
|
// ************** Event Streaming **************
|
||||||
var longpollEventStream = function() {
|
var longpollEventStream = function() {
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/events?access_token=$token&from=$from";
|
var url = "http://localhost:8080/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
url = url.replace("$from", eventStreamInfo.from);
|
||||||
|
|
||||||
@ -38,8 +38,9 @@ var longpollEventStream = function() {
|
|||||||
else if (data.chunk[i].type === "m.room.member") {
|
else if (data.chunk[i].type === "m.room.member") {
|
||||||
if (viewingRoomId === data.chunk[i].room_id) {
|
if (viewingRoomId === data.chunk[i].room_id) {
|
||||||
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
|
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
|
||||||
|
addMessage(data.chunk[i]);
|
||||||
for (j=0; j<memberInfo.length; ++j) {
|
for (j=0; j<memberInfo.length; ++j) {
|
||||||
if (memberInfo[j].target_user_id === data.chunk[i].target_user_id) {
|
if (memberInfo[j].state_key === data.chunk[i].state_key) {
|
||||||
memberInfo[j] = data.chunk[i];
|
memberInfo[j] = data.chunk[i];
|
||||||
updatedMemberList = true;
|
updatedMemberList = true;
|
||||||
break;
|
break;
|
||||||
@ -50,7 +51,7 @@ var longpollEventStream = function() {
|
|||||||
updatedMemberList = true;
|
updatedMemberList = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.chunk[i].target_user_id === accountInfo.user_id) {
|
if (data.chunk[i].state_key === accountInfo.user_id) {
|
||||||
getCurrentRoomList(); // update our join/invite list
|
getCurrentRoomList(); // update our join/invite list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +89,7 @@ $('.login').live('click', function() {
|
|||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/login",
|
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
@ -106,7 +107,7 @@ $('.register').live('click', function() {
|
|||||||
var user = $("#userReg").val();
|
var user = $("#userReg").val();
|
||||||
var password = $("#passwordReg").val();
|
var password = $("#passwordReg").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/register",
|
url: "http://localhost:8080/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user_id: user, password: password }),
|
||||||
@ -133,7 +134,7 @@ $('.createRoom').live('click', function() {
|
|||||||
data.room_alias_name = roomAlias;
|
data.room_alias_name = roomAlias;
|
||||||
}
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
|
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
@ -154,14 +155,15 @@ $('.createRoom').live('click', function() {
|
|||||||
|
|
||||||
// ************** Getting current state **************
|
// ************** Getting current state **************
|
||||||
var getCurrentRoomList = function() {
|
var getCurrentRoomList = function() {
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
|
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.length; ++i) {
|
var rooms = data.rooms;
|
||||||
if ("messages" in data[i]) {
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
data[i].latest_message = data[i].messages.chunk[0].content.body;
|
if ("messages" in rooms[i]) {
|
||||||
|
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomInfo = data;
|
roomInfo = rooms;
|
||||||
setRooms(roomInfo);
|
setRooms(roomInfo);
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
alert(JSON.stringify($.parseJSON(err.responseText)));
|
||||||
@ -179,7 +181,8 @@ var loadRoomContent = function(roomId) {
|
|||||||
|
|
||||||
var getMessages = function(roomId) {
|
var getMessages = function(roomId) {
|
||||||
$("#messages").empty();
|
$("#messages").empty();
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/" + roomId + "/messages/list?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=10";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/" +
|
||||||
|
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=data.chunk.length-1; i>=0; --i) {
|
for (var i=data.chunk.length-1; i>=0; --i) {
|
||||||
addMessage(data.chunk[i]);
|
addMessage(data.chunk[i]);
|
||||||
@ -190,7 +193,8 @@ var getMessages = function(roomId) {
|
|||||||
var getMemberList = function(roomId) {
|
var getMemberList = function(roomId) {
|
||||||
$("#members").empty();
|
$("#members").empty();
|
||||||
memberInfo = [];
|
memberInfo = [];
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/" + roomId + "/members/list?access_token=" + accountInfo.access_token;
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/" +
|
||||||
|
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
for (var i=0; i<data.chunk.length; ++i) {
|
||||||
memberInfo.push(data.chunk[i]);
|
memberInfo.push(data.chunk[i]);
|
||||||
@ -212,11 +216,9 @@ $('.sendMessage').live('click', function() {
|
|||||||
var sendMessage = function(roomId, body) {
|
var sendMessage = function(roomId, body) {
|
||||||
var msgId = $.now();
|
var msgId = $.now();
|
||||||
|
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
|
|
||||||
url = url.replace("$msgid", msgId);
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
@ -225,7 +227,7 @@ var sendMessage = function(roomId, body) {
|
|||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "PUT",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
@ -260,13 +262,12 @@ var setRooms = function(roomList) {
|
|||||||
var membership = $(this).find('td:eq(1)').text();
|
var membership = $(this).find('td:eq(1)').text();
|
||||||
if (membership !== "join") {
|
if (membership !== "join") {
|
||||||
console.log("Joining room " + roomId);
|
console.log("Joining room " + roomId);
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/members/$user/state?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "PUT",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({membership: "join"}),
|
data: JSON.stringify({membership: "join"}),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
@ -286,16 +287,33 @@ var setRooms = function(roomList) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var addMessage = function(data) {
|
var addMessage = function(data) {
|
||||||
|
|
||||||
|
var msg = data.content.body;
|
||||||
|
if (data.type === "m.room.member") {
|
||||||
|
if (data.content.membership === "invite") {
|
||||||
|
msg = "<em>invited " + data.state_key + " to the room</em>";
|
||||||
|
}
|
||||||
|
else if (data.content.membership === "join") {
|
||||||
|
msg = "<em>joined the room</em>";
|
||||||
|
}
|
||||||
|
else if (data.content.membership === "leave") {
|
||||||
|
msg = "<em>left the room</em>";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg = "<em>" + data.content.membership + "</em>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var row = "<tr>" +
|
var row = "<tr>" +
|
||||||
"<td>"+data.user_id+"</td>" +
|
"<td>"+data.user_id+"</td>" +
|
||||||
"<td>"+data.content.body+"</td>" +
|
"<td>"+msg+"</td>" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
$("#messages").append(row);
|
$("#messages").append(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addMember = function(data) {
|
var addMember = function(data) {
|
||||||
var row = "<tr>" +
|
var row = "<tr>" +
|
||||||
"<td>"+data.target_user_id+"</td>" +
|
"<td>"+data.state_key+"</td>" +
|
||||||
"<td>"+data.content.membership+"</td>" +
|
"<td>"+data.content.membership+"</td>" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
$("#members").append(row);
|
$("#members").append(row);
|
||||||
|
@ -11,7 +11,7 @@ $('.register').live('click', function() {
|
|||||||
var user = $("#user").val();
|
var user = $("#user").val();
|
||||||
var password = $("#password").val();
|
var password = $("#password").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/register",
|
url: "http://localhost:8080/_matrix/client/api/v1/register",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user_id: user, password: password }),
|
data: JSON.stringify({ user_id: user, password: password }),
|
||||||
@ -27,7 +27,7 @@ $('.register').live('click', function() {
|
|||||||
|
|
||||||
var login = function(user, password) {
|
var login = function(user, password) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/login",
|
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
@ -44,8 +44,8 @@ var login = function(user, password) {
|
|||||||
$('.login').live('click', function() {
|
$('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.getJSON("http://localhost:8080/matrix/client/api/v1/login", function(data) {
|
$.getJSON("http://localhost:8080/_matrix/client/api/v1/login", function(data) {
|
||||||
if (data.type !== "m.login.password") {
|
if (data.flows[0].type !== "m.login.password") {
|
||||||
alert("I don't know how to login with this type: " + data.type);
|
alert("I don't know how to login with this type: " + data.type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ $('.logout').live('click', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('.testToken').live('click', function() {
|
$('.testToken').live('click', function() {
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
|
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
<input type="text" id="roomId" placeholder="Room ID"></input>
|
<input type="text" id="roomId" placeholder="Room ID"></input>
|
||||||
<input type="text" id="targetUser" placeholder="Target User ID"></input>
|
<input type="text" id="targetUser" placeholder="Target User ID"></input>
|
||||||
<select id="membership">
|
<select id="membership">
|
||||||
<option value="invite">Invite</option>
|
<option value="invite">invite</option>
|
||||||
<option value="join">Join</option>
|
<option value="join">join</option>
|
||||||
<option value="leave">Leave</option>
|
<option value="leave">leave</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="button" class="changeMembership" value="Change Membership"></input>
|
<input type="button" class="changeMembership" value="Change Membership"></input>
|
||||||
</form>
|
</form>
|
||||||
|
@ -4,13 +4,21 @@ var showLoggedIn = function(data) {
|
|||||||
accountInfo = data;
|
accountInfo = data;
|
||||||
getCurrentRoomList();
|
getCurrentRoomList();
|
||||||
$(".loggedin").css({visibility: "visible"});
|
$(".loggedin").css({visibility: "visible"});
|
||||||
|
$("#membership").change(function() {
|
||||||
|
if ($("#membership").val() === "invite") {
|
||||||
|
$("#targetUser").css({visibility: "visible"});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#targetUser").css({visibility: "hidden"});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
$('.login').live('click', function() {
|
||||||
var user = $("#userLogin").val();
|
var user = $("#userLogin").val();
|
||||||
var password = $("#passwordLogin").val();
|
var password = $("#passwordLogin").val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/login",
|
url: "http://localhost:8080/_matrix/client/api/v1/login",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
||||||
@ -31,10 +39,11 @@ var getCurrentRoomList = function() {
|
|||||||
// solution but that is out of scope of this fiddle.
|
// solution but that is out of scope of this fiddle.
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
$("#rooms").find("tr:gt(0)").remove();
|
||||||
|
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
|
var url = "http://localhost:8080/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
||||||
$.getJSON(url, function(data) {
|
$.getJSON(url, function(data) {
|
||||||
for (var i=0; i<data.length; ++i) {
|
var rooms = data.rooms;
|
||||||
addRoom(data[i]);
|
for (var i=0; i<rooms.length; ++i) {
|
||||||
|
addRoom(rooms[i]);
|
||||||
}
|
}
|
||||||
}).fail(function(err) {
|
}).fail(function(err) {
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
alert(JSON.stringify($.parseJSON(err.responseText)));
|
||||||
@ -44,7 +53,7 @@ var getCurrentRoomList = function() {
|
|||||||
$('.createRoom').live('click', function() {
|
$('.createRoom').live('click', function() {
|
||||||
var data = {};
|
var data = {};
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
|
url: "http://localhost:8080/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
@ -78,54 +87,42 @@ $('.changeMembership').live('click', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/members/$user/state?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
url = url.replace("$roomid", encodeURIComponent(roomId));
|
||||||
url = url.replace("$user", encodeURIComponent(member));
|
url = url.replace("$membership", membership);
|
||||||
|
|
||||||
if (membership === "leave") {
|
var data = {};
|
||||||
$.ajax({
|
|
||||||
url: url,
|
if (membership === "invite") {
|
||||||
type: "DELETE",
|
data = {
|
||||||
contentType: "application/json; charset=utf-8",
|
user_id: member
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var data = {
|
|
||||||
membership: membership
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "PUT",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data) {
|
||||||
|
getCurrentRoomList();
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
alert(JSON.stringify($.parseJSON(err.responseText)));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.joinAlias').live('click', function() {
|
$('.joinAlias').live('click', function() {
|
||||||
var roomAlias = $("#roomAlias").val();
|
var roomAlias = $("#roomAlias").val();
|
||||||
var url = "http://localhost:8080/matrix/client/api/v1/join/$roomalias?access_token=$token";
|
var url = "http://localhost:8080/_matrix/client/api/v1/join/$roomalias?access_token=$token";
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
url = url.replace("$token", accountInfo.access_token);
|
||||||
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "PUT",
|
type: "POST",
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
data: JSON.stringify({}),
|
data: JSON.stringify({}),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
"""Contains the URL paths to prefix various aspects of the server with. """
|
"""Contains the URL paths to prefix various aspects of the server with. """
|
||||||
|
|
||||||
CLIENT_PREFIX = "/matrix/client/api/v1"
|
CLIENT_PREFIX = "/_matrix/client/api/v1"
|
||||||
FEDERATION_PREFIX = "/matrix/federation/v1"
|
FEDERATION_PREFIX = "/_matrix/federation/v1"
|
||||||
WEB_CLIENT_PREFIX = "/matrix/client"
|
WEB_CLIENT_PREFIX = "/_matrix/client"
|
||||||
CONTENT_REPO_PREFIX = "/matrix/content"
|
CONTENT_REPO_PREFIX = "/_matrix/content"
|
@ -274,11 +274,11 @@ class MessageHandler(BaseRoomHandler):
|
|||||||
messages, token = yield self.store.get_recent_events_for_room(
|
messages, token = yield self.store.get_recent_events_for_room(
|
||||||
event.room_id,
|
event.room_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
end_token=now_token.events_key,
|
end_token=now_token.room_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("events_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token[0])
|
||||||
end_token = now_token.copy_and_replace("events_key", token[1])
|
end_token = now_token.copy_and_replace("room_key", token[1])
|
||||||
|
|
||||||
d["messages"] = {
|
d["messages"] = {
|
||||||
"chunk": [m.get_dict() for m in messages],
|
"chunk": [m.get_dict() for m in messages],
|
||||||
|
@ -260,19 +260,18 @@ class PresenceHandler(BaseHandler):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def user_joined_room(self, user, room_id):
|
def user_joined_room(self, user, room_id):
|
||||||
|
|
||||||
if user.is_mine:
|
if user.is_mine:
|
||||||
|
statuscache = self._get_or_make_usercache(user)
|
||||||
|
|
||||||
|
# No actual update but we need to bump the serial anyway for the
|
||||||
|
# event source
|
||||||
|
self._user_cachemap_latest_serial += 1
|
||||||
|
statuscache.update({}, serial=self._user_cachemap_latest_serial)
|
||||||
|
|
||||||
self.push_update_to_local_and_remote(
|
self.push_update_to_local_and_remote(
|
||||||
observed_user=user,
|
observed_user=user,
|
||||||
room_ids=[room_id],
|
room_ids=[room_id],
|
||||||
statuscache=self._get_or_offline_usercache(user),
|
statuscache=statuscache,
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.push_update_to_clients(
|
|
||||||
observed_user=user,
|
|
||||||
room_ids=[room_id],
|
|
||||||
statuscache=self._get_or_offline_usercache(user),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# We also want to tell them about current presence of people.
|
# We also want to tell them about current presence of people.
|
||||||
@ -722,6 +721,78 @@ class PresenceHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PresenceEventSource(object):
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.hs = hs
|
||||||
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
|
from_key = int(from_key)
|
||||||
|
|
||||||
|
presence = self.hs.get_handlers().presence_handler
|
||||||
|
cachemap = presence._user_cachemap
|
||||||
|
|
||||||
|
# TODO(paul): limit, and filter by visibility
|
||||||
|
updates = [(k, cachemap[k]) for k in cachemap
|
||||||
|
if from_key < cachemap[k].serial]
|
||||||
|
|
||||||
|
if updates:
|
||||||
|
clock = self.clock
|
||||||
|
|
||||||
|
latest_serial = max([x[1].serial for x in updates])
|
||||||
|
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
||||||
|
|
||||||
|
return ((data, latest_serial))
|
||||||
|
else:
|
||||||
|
return (([], presence._user_cachemap_latest_serial))
|
||||||
|
|
||||||
|
def get_current_key(self):
|
||||||
|
presence = self.hs.get_handlers().presence_handler
|
||||||
|
return presence._user_cachemap_latest_serial
|
||||||
|
|
||||||
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
|
# TODO (erikj): Does this make sense? Ordering?
|
||||||
|
|
||||||
|
from_token = pagination_config.from_token
|
||||||
|
to_token = pagination_config.to_token
|
||||||
|
|
||||||
|
from_key = int(from_token.presence_key)
|
||||||
|
|
||||||
|
if to_token:
|
||||||
|
to_key = int(to_token.presence_key)
|
||||||
|
else:
|
||||||
|
to_key = -1
|
||||||
|
|
||||||
|
presence = self.hs.get_handlers().presence_handler
|
||||||
|
cachemap = presence._user_cachemap
|
||||||
|
|
||||||
|
# TODO(paul): limit, and filter by visibility
|
||||||
|
updates = [(k, cachemap[k]) for k in cachemap
|
||||||
|
if to_key < cachemap[k].serial < from_key]
|
||||||
|
|
||||||
|
if updates:
|
||||||
|
clock = self.clock
|
||||||
|
|
||||||
|
earliest_serial = max([x[1].serial for x in updates])
|
||||||
|
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
||||||
|
|
||||||
|
if to_token:
|
||||||
|
next_token = to_token
|
||||||
|
else:
|
||||||
|
next_token = from_token
|
||||||
|
|
||||||
|
next_token = next_token.copy_and_replace(
|
||||||
|
"presence_key", earliest_serial
|
||||||
|
)
|
||||||
|
return ((data, next_token))
|
||||||
|
else:
|
||||||
|
if not to_token:
|
||||||
|
to_token = from_token.copy_and_replace(
|
||||||
|
"presence_key", 0
|
||||||
|
)
|
||||||
|
return (([], to_token))
|
||||||
|
|
||||||
|
|
||||||
class UserPresenceCache(object):
|
class UserPresenceCache(object):
|
||||||
"""Store an observed user's state and status message.
|
"""Store an observed user's state and status message.
|
||||||
|
|
||||||
|
@ -497,3 +497,49 @@ class RoomListHandler(BaseRoomHandler):
|
|||||||
chunk = yield self.store.get_rooms(is_public=True)
|
chunk = yield self.store.get_rooms(is_public=True)
|
||||||
# FIXME (erikj): START is no longer a valid value
|
# FIXME (erikj): START is no longer a valid value
|
||||||
defer.returnValue({"start": "START", "end": "END", "chunk": chunk})
|
defer.returnValue({"start": "START", "end": "END", "chunk": chunk})
|
||||||
|
|
||||||
|
|
||||||
|
class RoomEventSource(object):
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.store = hs.get_datastore()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
|
# We just ignore the key for now.
|
||||||
|
|
||||||
|
to_key = yield self.get_current_key()
|
||||||
|
|
||||||
|
events, end_key = yield self.store.get_room_events_stream(
|
||||||
|
user_id=user.to_string(),
|
||||||
|
from_key=from_key,
|
||||||
|
to_key=to_key,
|
||||||
|
room_id=None,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue((events, end_key))
|
||||||
|
|
||||||
|
def get_current_key(self):
|
||||||
|
return self.store.get_room_events_max_id()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
|
from_token = pagination_config.from_token
|
||||||
|
to_token = pagination_config.to_token
|
||||||
|
limit = pagination_config.limit
|
||||||
|
direction = pagination_config.direction
|
||||||
|
|
||||||
|
to_key = to_token.room_key if to_token else None
|
||||||
|
|
||||||
|
events, next_key = yield self.store.paginate_room_events(
|
||||||
|
room_id=key,
|
||||||
|
from_key=from_token.room_key,
|
||||||
|
to_key=to_key,
|
||||||
|
direction=direction,
|
||||||
|
limit=limit,
|
||||||
|
with_feedback=True
|
||||||
|
)
|
||||||
|
|
||||||
|
next_token = from_token.copy_and_replace("room_key", next_key)
|
||||||
|
|
||||||
|
defer.returnValue((events, next_token))
|
||||||
|
@ -145,3 +145,17 @@ class TypingNotificationHandler(BaseHandler):
|
|||||||
typing):
|
typing):
|
||||||
# TODO(paul) steal this from presence.py
|
# TODO(paul) steal this from presence.py
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TypingNotificationEventSource(object):
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.hs = hs
|
||||||
|
|
||||||
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
|
return ([], from_key)
|
||||||
|
|
||||||
|
def get_current_key(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
|
return ([], pagination_config.from_token)
|
||||||
|
@ -325,7 +325,7 @@ class ContentRepoResource(resource.Resource):
|
|||||||
|
|
||||||
# FIXME (erikj): These should use constants.
|
# FIXME (erikj): These should use constants.
|
||||||
file_name = os.path.basename(fname)
|
file_name = os.path.basename(fname)
|
||||||
url = "http://%s/matrix/content/%s" % (
|
url = "http://%s/_matrix/content/%s" % (
|
||||||
self.hs.domain_with_port, file_name
|
self.hs.domain_with_port, file_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class Notifier(object):
|
|||||||
"""
|
"""
|
||||||
room_id = event.room_id
|
room_id = event.room_id
|
||||||
|
|
||||||
source = self.event_sources.sources["room"]
|
room_source = self.event_sources.sources["room"]
|
||||||
|
|
||||||
listeners = self.rooms_to_listeners.get(room_id, set()).copy()
|
listeners = self.rooms_to_listeners.get(room_id, set()).copy()
|
||||||
|
|
||||||
@ -107,13 +107,17 @@ class Notifier(object):
|
|||||||
# TODO (erikj): Can we make this more efficient by hitting the
|
# TODO (erikj): Can we make this more efficient by hitting the
|
||||||
# db once?
|
# db once?
|
||||||
for listener in listeners:
|
for listener in listeners:
|
||||||
events, end_token = yield source.get_new_events_for_user(
|
events, end_key = yield room_source.get_new_events_for_user(
|
||||||
listener.user,
|
listener.user,
|
||||||
listener.from_token,
|
listener.from_token.room_key,
|
||||||
listener.limit,
|
listener.limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
if events:
|
if events:
|
||||||
|
end_token = listener.from_token.copy_and_replace(
|
||||||
|
"room_key", end_key
|
||||||
|
)
|
||||||
|
|
||||||
listener.notify(
|
listener.notify(
|
||||||
self, events, listener.from_token, end_token
|
self, events, listener.from_token, end_token
|
||||||
)
|
)
|
||||||
@ -126,7 +130,7 @@ class Notifier(object):
|
|||||||
|
|
||||||
Will wake up all listeners for the given users and rooms.
|
Will wake up all listeners for the given users and rooms.
|
||||||
"""
|
"""
|
||||||
source = self.event_sources.sources["presence"]
|
presence_source = self.event_sources.sources["presence"]
|
||||||
|
|
||||||
listeners = set()
|
listeners = set()
|
||||||
|
|
||||||
@ -137,13 +141,17 @@ class Notifier(object):
|
|||||||
listeners |= self.rooms_to_listeners.get(room, set()).copy()
|
listeners |= self.rooms_to_listeners.get(room, set()).copy()
|
||||||
|
|
||||||
for listener in listeners:
|
for listener in listeners:
|
||||||
events, end_token = yield source.get_new_events_for_user(
|
events, end_key = yield presence_source.get_new_events_for_user(
|
||||||
listener.user,
|
listener.user,
|
||||||
listener.from_token,
|
listener.from_token.presence_key,
|
||||||
listener.limit,
|
listener.limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
if events:
|
if events:
|
||||||
|
end_token = listener.from_token.copy_and_replace(
|
||||||
|
"presence_key", end_key
|
||||||
|
)
|
||||||
|
|
||||||
listener.notify(
|
listener.notify(
|
||||||
self, events, listener.from_token, end_token
|
self, events, listener.from_token, end_token
|
||||||
)
|
)
|
||||||
@ -216,16 +224,18 @@ class Notifier(object):
|
|||||||
limit = listener.limit
|
limit = listener.limit
|
||||||
|
|
||||||
# TODO (erikj): DeferredList?
|
# TODO (erikj): DeferredList?
|
||||||
for source in self.event_sources.sources.values():
|
for name, source in self.event_sources.sources.items():
|
||||||
stuff, new_token = yield source.get_new_events_for_user(
|
keyname = "%s_key" % name
|
||||||
|
|
||||||
|
stuff, new_key = yield source.get_new_events_for_user(
|
||||||
listener.user,
|
listener.user,
|
||||||
from_token,
|
getattr(from_token, keyname),
|
||||||
limit,
|
limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
events.extend(stuff)
|
events.extend(stuff)
|
||||||
|
|
||||||
from_token = new_token
|
from_token = from_token.copy_and_replace(keyname, new_key)
|
||||||
|
|
||||||
end_token = from_token
|
end_token = from_token
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from synapse.types import StreamToken
|
from synapse.types import StreamToken
|
||||||
|
|
||||||
|
from synapse.handlers.presence import PresenceEventSource
|
||||||
|
from synapse.handlers.room import RoomEventSource
|
||||||
|
from synapse.handlers.typing import TypingNotificationEventSource
|
||||||
|
|
||||||
|
|
||||||
class NullSource(object):
|
class NullSource(object):
|
||||||
"""This event source never yields any events and its token remains at
|
"""This event source never yields any events and its token remains at
|
||||||
@ -24,146 +28,21 @@ class NullSource(object):
|
|||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_new_events_for_user(self, user, from_token, limit):
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
return defer.succeed(([], from_token))
|
return defer.succeed(([], from_key))
|
||||||
|
|
||||||
def get_current_token_part(self):
|
def get_current_key(self):
|
||||||
return defer.succeed(0)
|
return defer.succeed(0)
|
||||||
|
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
return defer.succeed(([], pagination_config.from_token))
|
return defer.succeed(([], pagination_config.from_token))
|
||||||
|
|
||||||
|
|
||||||
class RoomEventSource(object):
|
|
||||||
def __init__(self, hs):
|
|
||||||
self.store = hs.get_datastore()
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def get_new_events_for_user(self, user, from_token, limit):
|
|
||||||
# We just ignore the key for now.
|
|
||||||
|
|
||||||
to_key = yield self.get_current_token_part()
|
|
||||||
|
|
||||||
events, end_key = yield self.store.get_room_events_stream(
|
|
||||||
user_id=user.to_string(),
|
|
||||||
from_key=from_token.events_key,
|
|
||||||
to_key=to_key,
|
|
||||||
room_id=None,
|
|
||||||
limit=limit,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_token = from_token.copy_and_replace("events_key", end_key)
|
|
||||||
|
|
||||||
defer.returnValue((events, end_token))
|
|
||||||
|
|
||||||
def get_current_token_part(self):
|
|
||||||
return self.store.get_room_events_max_id()
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
|
||||||
from_token = pagination_config.from_token
|
|
||||||
to_token = pagination_config.to_token
|
|
||||||
limit = pagination_config.limit
|
|
||||||
direction = pagination_config.direction
|
|
||||||
|
|
||||||
to_key = to_token.events_key if to_token else None
|
|
||||||
|
|
||||||
events, next_key = yield self.store.paginate_room_events(
|
|
||||||
room_id=key,
|
|
||||||
from_key=from_token.events_key,
|
|
||||||
to_key=to_key,
|
|
||||||
direction=direction,
|
|
||||||
limit=limit,
|
|
||||||
with_feedback=True
|
|
||||||
)
|
|
||||||
|
|
||||||
next_token = from_token.copy_and_replace("events_key", next_key)
|
|
||||||
|
|
||||||
defer.returnValue((events, next_token))
|
|
||||||
|
|
||||||
|
|
||||||
class PresenceSource(object):
|
|
||||||
def __init__(self, hs):
|
|
||||||
self.hs = hs
|
|
||||||
self.clock = hs.get_clock()
|
|
||||||
|
|
||||||
def get_new_events_for_user(self, user, from_token, limit):
|
|
||||||
from_key = int(from_token.presence_key)
|
|
||||||
|
|
||||||
presence = self.hs.get_handlers().presence_handler
|
|
||||||
cachemap = presence._user_cachemap
|
|
||||||
|
|
||||||
# TODO(paul): limit, and filter by visibility
|
|
||||||
updates = [(k, cachemap[k]) for k in cachemap
|
|
||||||
if from_key < cachemap[k].serial]
|
|
||||||
|
|
||||||
if updates:
|
|
||||||
clock = self.clock
|
|
||||||
|
|
||||||
latest_serial = max([x[1].serial for x in updates])
|
|
||||||
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
|
||||||
|
|
||||||
end_token = from_token.copy_and_replace(
|
|
||||||
"presence_key", latest_serial
|
|
||||||
)
|
|
||||||
return ((data, end_token))
|
|
||||||
else:
|
|
||||||
end_token = from_token.copy_and_replace(
|
|
||||||
"presence_key", presence._user_cachemap_latest_serial
|
|
||||||
)
|
|
||||||
return (([], end_token))
|
|
||||||
|
|
||||||
def get_current_token_part(self):
|
|
||||||
presence = self.hs.get_handlers().presence_handler
|
|
||||||
return presence._user_cachemap_latest_serial
|
|
||||||
|
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
|
||||||
# TODO (erikj): Does this make sense? Ordering?
|
|
||||||
|
|
||||||
from_token = pagination_config.from_token
|
|
||||||
to_token = pagination_config.to_token
|
|
||||||
|
|
||||||
from_key = int(from_token.presence_key)
|
|
||||||
|
|
||||||
if to_token:
|
|
||||||
to_key = int(to_token.presence_key)
|
|
||||||
else:
|
|
||||||
to_key = -1
|
|
||||||
|
|
||||||
presence = self.hs.get_handlers().presence_handler
|
|
||||||
cachemap = presence._user_cachemap
|
|
||||||
|
|
||||||
# TODO(paul): limit, and filter by visibility
|
|
||||||
updates = [(k, cachemap[k]) for k in cachemap
|
|
||||||
if to_key < cachemap[k].serial < from_key]
|
|
||||||
|
|
||||||
if updates:
|
|
||||||
clock = self.clock
|
|
||||||
|
|
||||||
earliest_serial = max([x[1].serial for x in updates])
|
|
||||||
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
|
||||||
|
|
||||||
if to_token:
|
|
||||||
next_token = to_token
|
|
||||||
else:
|
|
||||||
next_token = from_token
|
|
||||||
|
|
||||||
next_token = next_token.copy_and_replace(
|
|
||||||
"presence_key", earliest_serial
|
|
||||||
)
|
|
||||||
return ((data, next_token))
|
|
||||||
else:
|
|
||||||
if not to_token:
|
|
||||||
to_token = from_token.copy_and_replace(
|
|
||||||
"presence_key", 0
|
|
||||||
)
|
|
||||||
return (([], to_token))
|
|
||||||
|
|
||||||
|
|
||||||
class EventSources(object):
|
class EventSources(object):
|
||||||
SOURCE_TYPES = {
|
SOURCE_TYPES = {
|
||||||
"room": RoomEventSource,
|
"room": RoomEventSource,
|
||||||
"presence": PresenceSource,
|
"presence": PresenceEventSource,
|
||||||
|
"typing": TypingNotificationEventSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
@ -172,24 +51,29 @@ class EventSources(object):
|
|||||||
for name, cls in EventSources.SOURCE_TYPES.items()
|
for name, cls in EventSources.SOURCE_TYPES.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_token(events_key, presence_key):
|
|
||||||
return StreamToken(events_key=events_key, presence_key=presence_key)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_current_token(self):
|
def get_current_token(self):
|
||||||
events_key = yield self.sources["room"].get_current_token_part()
|
token = StreamToken(
|
||||||
presence_key = yield self.sources["presence"].get_current_token_part()
|
room_key=(
|
||||||
token = EventSources.create_token(events_key, presence_key)
|
yield self.sources["room"].get_current_key()
|
||||||
|
),
|
||||||
|
presence_key=(
|
||||||
|
yield self.sources["presence"].get_current_key()
|
||||||
|
),
|
||||||
|
typing_key=(
|
||||||
|
yield self.sources["typing"].get_current_key()
|
||||||
|
)
|
||||||
|
)
|
||||||
defer.returnValue(token)
|
defer.returnValue(token)
|
||||||
|
|
||||||
|
|
||||||
class StreamSource(object):
|
class StreamSource(object):
|
||||||
def get_new_events_for_user(self, user, from_token, limit):
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
|
"""from_key is the key within this event source."""
|
||||||
raise NotImplementedError("get_new_events_for_user")
|
raise NotImplementedError("get_new_events_for_user")
|
||||||
|
|
||||||
def get_current_token_part(self):
|
def get_current_key(self):
|
||||||
raise NotImplementedError("get_current_token_part")
|
raise NotImplementedError("get_current_key")
|
||||||
|
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
raise NotImplementedError("get_rows")
|
raise NotImplementedError("get_rows")
|
||||||
|
@ -97,7 +97,7 @@ class RoomID(DomainSpecificString):
|
|||||||
class StreamToken(
|
class StreamToken(
|
||||||
namedtuple(
|
namedtuple(
|
||||||
"Token",
|
"Token",
|
||||||
("events_key", "presence_key")
|
("room_key", "presence_key", "typing_key")
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_SEPARATOR = "_"
|
_SEPARATOR = "_"
|
||||||
@ -105,21 +105,14 @@ class StreamToken(
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, string):
|
def from_string(cls, string):
|
||||||
try:
|
try:
|
||||||
events_key, presence_key = string.split(cls._SEPARATOR)
|
keys = string.split(cls._SEPARATOR)
|
||||||
|
|
||||||
return cls(
|
return cls(*keys)
|
||||||
events_key=events_key,
|
|
||||||
presence_key=presence_key,
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
raise SynapseError(400, "Invalid Token")
|
raise SynapseError(400, "Invalid Token")
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
return "".join([
|
return self._SEPARATOR.join([str(k) for k in self])
|
||||||
str(self.events_key),
|
|
||||||
self._SEPARATOR,
|
|
||||||
str(self.presence_key),
|
|
||||||
])
|
|
||||||
|
|
||||||
def copy_and_replace(self, key, new_value):
|
def copy_and_replace(self, key, new_value):
|
||||||
d = self._asdict()
|
d = self._asdict()
|
||||||
|
@ -87,7 +87,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
# Empty context initially
|
# Empty context initially
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/matrix/federation/v1/state/my-context/", None)
|
"/_matrix/federation/v1/state/my-context/", None)
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertFalse(response["pdus"])
|
self.assertFalse(response["pdus"])
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/matrix/federation/v1/state/my-context/", None)
|
"/_matrix/federation/v1/state/my-context/", None)
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals(1, len(response["pdus"]))
|
self.assertEquals(1, len(response["pdus"]))
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/matrix/federation/v1/pdu/red/abc123def456/", None)
|
"/_matrix/federation/v1/pdu/red/abc123def456/", None)
|
||||||
self.assertEquals(404, code)
|
self.assertEquals(404, code)
|
||||||
|
|
||||||
# Now insert such a PDU
|
# Now insert such a PDU
|
||||||
@ -142,7 +142,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/matrix/federation/v1/pdu/red/abc123def456/", None)
|
"/_matrix/federation/v1/pdu/red/abc123def456/", None)
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals(1, len(response["pdus"]))
|
self.assertEquals(1, len(response["pdus"]))
|
||||||
self.assertEquals("m.text", response["pdus"][0]["pdu_type"])
|
self.assertEquals("m.text", response["pdus"][0]["pdu_type"])
|
||||||
@ -168,7 +168,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.mock_http_client.put_json.assert_called_with(
|
self.mock_http_client.put_json.assert_called_with(
|
||||||
"remote",
|
"remote",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data={
|
data={
|
||||||
"ts": 1000000,
|
"ts": 1000000,
|
||||||
"origin": "test",
|
"origin": "test",
|
||||||
@ -203,7 +203,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
# MockClock ensures we can guess these timestamps
|
# MockClock ensures we can guess these timestamps
|
||||||
self.mock_http_client.put_json.assert_called_with(
|
self.mock_http_client.put_json.assert_called_with(
|
||||||
"remote",
|
"remote",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data={
|
data={
|
||||||
"origin": "test",
|
"origin": "test",
|
||||||
"ts": 1000000,
|
"ts": 1000000,
|
||||||
@ -226,7 +226,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
self.federation.register_edu_handler("m.test", recv_observer)
|
self.federation.register_edu_handler("m.test", recv_observer)
|
||||||
|
|
||||||
yield self.mock_resource.trigger("PUT",
|
yield self.mock_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1001000/",
|
"/_matrix/federation/v1/send/1001000/",
|
||||||
"""{
|
"""{
|
||||||
"origin": "remote",
|
"origin": "remote",
|
||||||
"ts": 1001000,
|
"ts": 1001000,
|
||||||
@ -261,7 +261,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.mock_http_client.get_json.assert_called_with(
|
self.mock_http_client.get_json.assert_called_with(
|
||||||
destination="remote",
|
destination="remote",
|
||||||
path="/matrix/federation/v1/query/a-question",
|
path="/_matrix/federation/v1/query/a-question",
|
||||||
args={"one": "1", "two": "2"}
|
args={"one": "1", "two": "2"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ class FederationTestCase(unittest.TestCase):
|
|||||||
self.federation.register_query_handler("a-question", recv_handler)
|
self.federation.register_query_handler("a-question", recv_handler)
|
||||||
|
|
||||||
code, response = yield self.mock_resource.trigger("GET",
|
code, response = yield self.mock_resource.trigger("GET",
|
||||||
"/matrix/federation/v1/query/a-question?three=3&four=4", None)
|
"/_matrix/federation/v1/query/a-question?three=3&four=4", None)
|
||||||
|
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals({"another": "response"}, response)
|
self.assertEquals({"another": "response"}, response)
|
||||||
|
@ -314,7 +314,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("elsewhere",
|
call("elsewhere",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("elsewhere", "m.presence_invite",
|
data=_expect_edu("elsewhere", "m.presence_invite",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@apple:test",
|
"observer_user": "@apple:test",
|
||||||
@ -340,7 +340,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("elsewhere",
|
call("elsewhere",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("elsewhere", "m.presence_accept",
|
data=_expect_edu("elsewhere", "m.presence_accept",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@cabbage:elsewhere",
|
"observer_user": "@cabbage:elsewhere",
|
||||||
@ -352,7 +352,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("elsewhere", "m.presence_invite",
|
_make_edu_json("elsewhere", "m.presence_invite",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@cabbage:elsewhere",
|
"observer_user": "@cabbage:elsewhere",
|
||||||
@ -371,7 +371,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("elsewhere",
|
call("elsewhere",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("elsewhere", "m.presence_deny",
|
data=_expect_edu("elsewhere", "m.presence_deny",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@cabbage:elsewhere",
|
"observer_user": "@cabbage:elsewhere",
|
||||||
@ -383,7 +383,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("elsewhere", "m.presence_invite",
|
_make_edu_json("elsewhere", "m.presence_invite",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@cabbage:elsewhere",
|
"observer_user": "@cabbage:elsewhere",
|
||||||
@ -397,7 +397,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_accepted_remote(self):
|
def test_accepted_remote(self):
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("elsewhere", "m.presence_accept",
|
_make_edu_json("elsewhere", "m.presence_accept",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@apple:test",
|
"observer_user": "@apple:test",
|
||||||
@ -415,7 +415,7 @@ class PresenceInvitesTestCase(unittest.TestCase):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_denied_remote(self):
|
def test_denied_remote(self):
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("elsewhere", "m.presence_deny",
|
_make_edu_json("elsewhere", "m.presence_deny",
|
||||||
content={
|
content={
|
||||||
"observer_user": "@apple:test",
|
"observer_user": "@apple:test",
|
||||||
@ -514,13 +514,6 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
hs.handlers = JustPresenceHandlers(hs)
|
hs.handlers = JustPresenceHandlers(hs)
|
||||||
|
|
||||||
def update(*args,**kwargs):
|
|
||||||
# print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,)
|
|
||||||
return defer.succeed(None)
|
|
||||||
|
|
||||||
self.mock_update_client = Mock()
|
|
||||||
self.mock_update_client.side_effect = update
|
|
||||||
|
|
||||||
self.datastore = hs.get_datastore()
|
self.datastore = hs.get_datastore()
|
||||||
|
|
||||||
def get_received_txn_response(*args):
|
def get_received_txn_response(*args):
|
||||||
@ -528,7 +521,7 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
self.datastore.get_received_txn_response = get_received_txn_response
|
self.datastore.get_received_txn_response = get_received_txn_response
|
||||||
|
|
||||||
self.handler = hs.get_handlers().presence_handler
|
self.handler = hs.get_handlers().presence_handler
|
||||||
self.handler.push_update_to_clients = self.mock_update_client
|
self.event_source = hs.get_event_sources().sources["presence"]
|
||||||
|
|
||||||
# Mock the RoomMemberHandler
|
# Mock the RoomMemberHandler
|
||||||
hs.handlers.room_member_handler = Mock(spec=[
|
hs.handlers.room_member_handler = Mock(spec=[
|
||||||
@ -622,16 +615,23 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
apple_set.add(self.u_banana)
|
apple_set.add(self.u_banana)
|
||||||
apple_set.add(self.u_clementine)
|
apple_set.add(self.u_clementine)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
yield self.handler.set_state(self.u_apple, self.u_apple,
|
yield self.handler.set_state(self.u_apple, self.u_apple,
|
||||||
{"state": ONLINE})
|
{"state": ONLINE})
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
call(users_to_push=set([self.u_apple, self.u_banana, self.u_clementine]),
|
self.assertEquals(
|
||||||
room_ids=["a-room"],
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
observed_user=self.u_apple,
|
[
|
||||||
statuscache=ANY), # self-reflection
|
{"type": "m.presence",
|
||||||
], any_order=True)
|
"content": {
|
||||||
self.mock_update_client.reset_mock()
|
"user_id": "@apple:test",
|
||||||
|
"state": ONLINE,
|
||||||
|
"mtime_age": 0,
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
presence = yield self.handler.get_presence_list(
|
presence = yield self.handler.get_presence_list(
|
||||||
observer_user=self.u_apple, accepted=True)
|
observer_user=self.u_apple, accepted=True)
|
||||||
@ -657,31 +657,24 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
"state": OFFLINE},
|
"state": OFFLINE},
|
||||||
], presence)
|
], presence)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.assertEquals(self.event_source.get_current_key(), 2)
|
||||||
call(users_to_push=set([self.u_banana]),
|
self.assertEquals(
|
||||||
room_ids=[],
|
self.event_source.get_new_events_for_user(
|
||||||
observed_user=self.u_banana,
|
self.u_banana, 1, None
|
||||||
statuscache=ANY), # self-reflection
|
)[0],
|
||||||
]) # and no others...
|
[
|
||||||
|
{"type": "m.presence",
|
||||||
|
"content": {
|
||||||
|
"user_id": "@banana:test",
|
||||||
|
"state": ONLINE,
|
||||||
|
"mtime_age": 2000
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_push_remote(self):
|
def test_push_remote(self):
|
||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
# put_json.expect_call_and_return(
|
|
||||||
# call("remote",
|
|
||||||
# path=ANY, # Can't guarantee which txn ID will be which
|
|
||||||
# data=_expect_edu("remote", "m.presence",
|
|
||||||
# content={
|
|
||||||
# "push": [
|
|
||||||
# {"user_id": "@apple:test",
|
|
||||||
# "state": "online",
|
|
||||||
# "mtime_age": 0},
|
|
||||||
# ],
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
# ),
|
|
||||||
# defer.succeed((200, "OK"))
|
|
||||||
# )
|
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("farm",
|
call("farm",
|
||||||
path=ANY, # Can't guarantee which txn ID will be which
|
path=ANY, # Can't guarantee which txn ID will be which
|
||||||
@ -724,8 +717,10 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.room_members = [self.u_banana, self.u_potato]
|
self.room_members = [self.u_banana, self.u_potato]
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("elsewhere", "m.presence",
|
_make_edu_json("elsewhere", "m.presence",
|
||||||
content={
|
content={
|
||||||
"push": [
|
"push": [
|
||||||
@ -737,12 +732,20 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
call(users_to_push=set([self.u_apple]),
|
self.assertEquals(
|
||||||
room_ids=["a-room"],
|
self.event_source.get_new_events_for_user(
|
||||||
observed_user=self.u_potato,
|
self.u_apple, 0, None
|
||||||
statuscache=ANY),
|
)[0],
|
||||||
], any_order=True)
|
[
|
||||||
|
{"type": "m.presence",
|
||||||
|
"content": {
|
||||||
|
"user_id": "@potato:remote",
|
||||||
|
"state": ONLINE,
|
||||||
|
"mtime_age": 1000,
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
self.clock.advance_time(2)
|
self.clock.advance_time(2)
|
||||||
|
|
||||||
@ -754,24 +757,35 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
def test_join_room_local(self):
|
def test_join_room_local(self):
|
||||||
self.room_members = [self.u_apple, self.u_banana]
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
yield self.distributor.fire("user_joined_room", self.u_elderberry,
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
|
# TODO(paul): Gut-wrenching
|
||||||
|
self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
|
||||||
|
self.handler._user_cachemap[self.u_clementine].update(
|
||||||
|
{
|
||||||
|
"state": PresenceState.ONLINE,
|
||||||
|
"mtime": self.clock.time_msec(),
|
||||||
|
}, self.u_clementine
|
||||||
|
)
|
||||||
|
|
||||||
|
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
||||||
"a-room"
|
"a-room"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
call(room_ids=["a-room"],
|
self.assertEquals(
|
||||||
observed_user=self.u_elderberry,
|
self.event_source.get_new_events_for_user(
|
||||||
users_to_push=set(),
|
self.u_apple, 0, None
|
||||||
statuscache=ANY),
|
)[0],
|
||||||
call(users_to_push=set([self.u_elderberry]),
|
[
|
||||||
observed_user=self.u_apple,
|
{"type": "m.presence",
|
||||||
room_ids=[],
|
"content": {
|
||||||
statuscache=ANY),
|
"user_id": "@clementine:test",
|
||||||
call(users_to_push=set([self.u_elderberry]),
|
"state": ONLINE,
|
||||||
observed_user=self.u_banana,
|
"mtime_age": 0,
|
||||||
room_ids=[],
|
}}
|
||||||
statuscache=ANY),
|
]
|
||||||
], any_order=True)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_join_room_remote(self):
|
def test_join_room_remote(self):
|
||||||
@ -822,7 +836,7 @@ class PresencePushTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("remote",
|
call("remote",
|
||||||
path="/matrix/federation/v1/send/1000002/",
|
path="/_matrix/federation/v1/send/1000002/",
|
||||||
data=_expect_edu("remote", "m.presence",
|
data=_expect_edu("remote", "m.presence",
|
||||||
content={
|
content={
|
||||||
"push": [
|
"push": [
|
||||||
@ -1116,7 +1130,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("remote",
|
call("remote",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("remote", "m.presence",
|
data=_expect_edu("remote", "m.presence",
|
||||||
content={
|
content={
|
||||||
"push": [
|
"push": [
|
||||||
@ -1131,7 +1145,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("remote", "m.presence",
|
_make_edu_json("remote", "m.presence",
|
||||||
content={
|
content={
|
||||||
"poll": [ "@banana:test" ],
|
"poll": [ "@banana:test" ],
|
||||||
@ -1145,7 +1159,7 @@ class PresencePollingTestCase(unittest.TestCase):
|
|||||||
self.assertTrue(self.u_banana in self.handler._remote_sendmap)
|
self.assertTrue(self.u_banana in self.handler._remote_sendmap)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000001/",
|
"/_matrix/federation/v1/send/1000001/",
|
||||||
_make_edu_json("remote", "m.presence",
|
_make_edu_json("remote", "m.presence",
|
||||||
content={
|
content={
|
||||||
"unpoll": [ "@banana:test" ],
|
"unpoll": [ "@banana:test" ],
|
||||||
|
@ -166,7 +166,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("farm",
|
call("farm",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("farm", "m.typing",
|
data=_expect_edu("farm", "m.typing",
|
||||||
content={
|
content={
|
||||||
"room_id": self.room_id,
|
"room_id": self.room_id,
|
||||||
@ -192,7 +192,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||||||
self.room_members = [self.u_apple, self.u_onion]
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("farm", "m.typing",
|
_make_edu_json("farm", "m.typing",
|
||||||
content={
|
content={
|
||||||
"room_id": self.room_id,
|
"room_id": self.room_id,
|
||||||
@ -216,7 +216,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||||||
put_json = self.mock_http_client.put_json
|
put_json = self.mock_http_client.put_json
|
||||||
put_json.expect_call_and_return(
|
put_json.expect_call_and_return(
|
||||||
call("farm",
|
call("farm",
|
||||||
path="/matrix/federation/v1/send/1000000/",
|
path="/_matrix/federation/v1/send/1000000/",
|
||||||
data=_expect_edu("farm", "m.typing",
|
data=_expect_edu("farm", "m.typing",
|
||||||
content={
|
content={
|
||||||
"room_id": self.room_id,
|
"room_id": self.room_id,
|
||||||
|
@ -36,7 +36,7 @@ from mock import Mock
|
|||||||
|
|
||||||
logging.getLogger().addHandler(logging.NullHandler())
|
logging.getLogger().addHandler(logging.NullHandler())
|
||||||
|
|
||||||
PATH_PREFIX = "/matrix/client/api/v1"
|
PATH_PREFIX = "/_matrix/client/api/v1"
|
||||||
|
|
||||||
|
|
||||||
class EventStreamPaginationApiTestCase(unittest.TestCase):
|
class EventStreamPaginationApiTestCase(unittest.TestCase):
|
||||||
|
@ -37,7 +37,7 @@ ONLINE = PresenceState.ONLINE
|
|||||||
|
|
||||||
|
|
||||||
myid = "@apple:test"
|
myid = "@apple:test"
|
||||||
PATH_PREFIX = "/matrix/client/api/v1"
|
PATH_PREFIX = "/_matrix/client/api/v1"
|
||||||
|
|
||||||
|
|
||||||
class JustPresenceHandlers(object):
|
class JustPresenceHandlers(object):
|
||||||
@ -229,7 +229,7 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||||||
# HIDEOUS HACKERY
|
# HIDEOUS HACKERY
|
||||||
# TODO(paul): This should be injected in via the HomeServer DI system
|
# TODO(paul): This should be injected in via the HomeServer DI system
|
||||||
from synapse.streams.events import (
|
from synapse.streams.events import (
|
||||||
PresenceSource, NullSource, EventSources
|
PresenceEventSource, NullSource, EventSources
|
||||||
)
|
)
|
||||||
|
|
||||||
old_SOURCE_TYPES = EventSources.SOURCE_TYPES
|
old_SOURCE_TYPES = EventSources.SOURCE_TYPES
|
||||||
@ -240,7 +240,7 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||||||
EventSources.SOURCE_TYPES = {
|
EventSources.SOURCE_TYPES = {
|
||||||
k: NullSource for k in old_SOURCE_TYPES.keys()
|
k: NullSource for k in old_SOURCE_TYPES.keys()
|
||||||
}
|
}
|
||||||
EventSources.SOURCE_TYPES["presence"] = PresenceSource
|
EventSources.SOURCE_TYPES["presence"] = PresenceEventSource
|
||||||
|
|
||||||
hs = HomeServer("test",
|
hs = HomeServer("test",
|
||||||
db_pool=None,
|
db_pool=None,
|
||||||
@ -274,6 +274,15 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||||||
lambda u: defer.succeed([]))
|
lambda u: defer.succeed([]))
|
||||||
|
|
||||||
self.mock_datastore = hs.get_datastore()
|
self.mock_datastore = hs.get_datastore()
|
||||||
|
|
||||||
|
def get_profile_displayname(user_id):
|
||||||
|
return defer.succeed("Frank")
|
||||||
|
self.mock_datastore.get_profile_displayname = get_profile_displayname
|
||||||
|
|
||||||
|
def get_profile_avatar_url(user_id):
|
||||||
|
return defer.succeed(None)
|
||||||
|
self.mock_datastore.get_profile_avatar_url = get_profile_avatar_url
|
||||||
|
|
||||||
self.presence = hs.get_handlers().presence_handler
|
self.presence = hs.get_handlers().presence_handler
|
||||||
|
|
||||||
self.u_apple = hs.parse_userid("@apple:test")
|
self.u_apple = hs.parse_userid("@apple:test")
|
||||||
@ -295,7 +304,9 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||||||
# all be ours
|
# all be ours
|
||||||
|
|
||||||
# I'll already get my own presence state change
|
# I'll already get my own presence state change
|
||||||
self.assertEquals({"start": "0_1", "end": "0_1", "chunk": []}, response)
|
self.assertEquals({"start": "0_1_0", "end": "0_1_0", "chunk": []},
|
||||||
|
response
|
||||||
|
)
|
||||||
|
|
||||||
self.mock_datastore.set_presence_state.return_value = defer.succeed(
|
self.mock_datastore.set_presence_state.return_value = defer.succeed(
|
||||||
{"state": ONLINE})
|
{"state": ONLINE})
|
||||||
@ -306,14 +317,15 @@ class PresenceEventStreamTestCase(unittest.TestCase):
|
|||||||
state={"state": ONLINE})
|
state={"state": ONLINE})
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/events?from=0_1&timeout=0", None)
|
"/events?from=0_1_0&timeout=0", None)
|
||||||
|
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals({"start": "0_1", "end": "0_2", "chunk": [
|
self.assertEquals({"start": "0_1_0", "end": "0_2_0", "chunk": [
|
||||||
{"type": "m.presence",
|
{"type": "m.presence",
|
||||||
"content": {
|
"content": {
|
||||||
"user_id": "@banana:test",
|
"user_id": "@banana:test",
|
||||||
"state": ONLINE,
|
"state": ONLINE,
|
||||||
|
"displayname": "Frank",
|
||||||
"mtime_age": 0,
|
"mtime_age": 0,
|
||||||
}},
|
}},
|
||||||
]}, response)
|
]}, response)
|
||||||
|
@ -26,7 +26,7 @@ from synapse.api.errors import SynapseError, AuthError
|
|||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
myid = "@1234ABCD:test"
|
myid = "@1234ABCD:test"
|
||||||
PATH_PREFIX = "/matrix/client/api/v1"
|
PATH_PREFIX = "/_matrix/client/api/v1"
|
||||||
|
|
||||||
class ProfileTestCase(unittest.TestCase):
|
class ProfileTestCase(unittest.TestCase):
|
||||||
""" Tests profile management. """
|
""" Tests profile management. """
|
||||||
|
@ -32,7 +32,7 @@ from .utils import RestTestCase
|
|||||||
|
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
PATH_PREFIX = "/matrix/client/api/v1"
|
PATH_PREFIX = "/_matrix/client/api/v1"
|
||||||
|
|
||||||
|
|
||||||
class RoomPermissionsTestCase(RestTestCase):
|
class RoomPermissionsTestCase(RestTestCase):
|
||||||
|
@ -37,6 +37,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||||||
mPresence.start();
|
mPresence.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.user_id = matrixService.config().user_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a given page.
|
* Open a given page.
|
||||||
* @param {String} url url of the page
|
* @param {String} url url of the page
|
||||||
@ -45,6 +47,16 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||||||
$location.url(url);
|
$location.url(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Open the given user profile page
|
||||||
|
$scope.goToUserPage = function(user_id) {
|
||||||
|
if (user_id === $scope.user_id) {
|
||||||
|
$location.url("/settings");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$location.url("/user/" + user_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Logs the user out
|
// Logs the user out
|
||||||
$scope.logout = function() {
|
$scope.logout = function() {
|
||||||
|
|
||||||
@ -69,11 +81,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
|||||||
$scope.logout();
|
$scope.logout();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.requestNotifications = function() {
|
$scope.updateHeader = function() {
|
||||||
if (window.Notification) {
|
$scope.user_id = matrixService.config().user_id;
|
||||||
console.log("Notification.permission: " + window.Notification.permission);
|
|
||||||
window.Notification.requestPermission(function(){});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@ -32,7 +32,12 @@ angular.module('matrixWebClient')
|
|||||||
.directive('ngFocus', ['$timeout', function($timeout) {
|
.directive('ngFocus', ['$timeout', function($timeout) {
|
||||||
return {
|
return {
|
||||||
link: function(scope, element, attr) {
|
link: function(scope, element, attr) {
|
||||||
$timeout(function() { element[0].focus(); }, 0);
|
// XXX: slightly evil hack to disable autofocus on iOS, as in general
|
||||||
|
// it causes more problems than it fixes, by bouncing the page
|
||||||
|
// around
|
||||||
|
if (!/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
|
||||||
|
$timeout(function() { element[0].focus(); }, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
323
webclient/app.css
Normal file → Executable file
323
webclient/app.css
Normal file → Executable file
@ -1,121 +1,194 @@
|
|||||||
/*** Mobile voodoo ***/
|
/** Common layout **/
|
||||||
@media all and (max-device-width: 640px) {
|
|
||||||
|
html {
|
||||||
#messageTableWrapper {
|
height: 100%;
|
||||||
margin-right: 0px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftBlock {
|
|
||||||
width: 8em ! important;
|
|
||||||
font-size: 8px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightBlock {
|
|
||||||
width: 0px ! important;
|
|
||||||
display: none ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 36px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header,
|
|
||||||
#messageTable,
|
|
||||||
#wrapper,
|
|
||||||
#roomName,
|
|
||||||
#controls {
|
|
||||||
max-width: 640px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#userIdCell,
|
|
||||||
#usersTableWrapper,
|
|
||||||
#extraControls {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#buttonsCell {
|
|
||||||
width: 60px ! important;
|
|
||||||
padding-left: 20px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#roomLogo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#roomName {
|
|
||||||
text-align: left ! important;
|
|
||||||
top: -35px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble {
|
|
||||||
font-size: 12px ! important;
|
|
||||||
min-height: 20px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page {
|
|
||||||
top: 35px ! important;
|
|
||||||
bottom: 70px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header,
|
|
||||||
#page {
|
|
||||||
margin: 5px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header {
|
|
||||||
padding: 5px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stop zoom on select */
|
|
||||||
select:focus,
|
|
||||||
textarea,
|
|
||||||
input
|
|
||||||
{
|
|
||||||
font-size: 16px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
height: 100%;
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-family: Helvetica, Arial, sans-serif;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Overall page layout ***/
|
a:link { color: #666; }
|
||||||
|
a:visited { color: #666; }
|
||||||
|
a:hover { color: #000; }
|
||||||
|
a:active { color: #000; }
|
||||||
|
|
||||||
#page {
|
#page {
|
||||||
position: absolute;
|
min-height: 100%;
|
||||||
top: 80px;
|
margin-bottom: -32px; /* to make room for the footer */
|
||||||
bottom: 100px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1280px;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #333;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent {
|
||||||
|
color: #ccc;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: right;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent a:link,
|
||||||
|
#headerContent a:visited,
|
||||||
|
#headerContent a:hover,
|
||||||
|
#headerContent a:active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
border-top: #666 1px solid;
|
||||||
|
background-color: #aaa;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footerContent
|
||||||
|
{
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #fff;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#genericHeading
|
||||||
|
{
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feedback {
|
||||||
|
color: #800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invited {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Login Pages ***/
|
||||||
|
|
||||||
|
.loginWrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm {
|
||||||
|
text-align: left;
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px #ccc solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm input[type='radio'] {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig,
|
||||||
|
#serverConfig input,
|
||||||
|
#serverConfig button
|
||||||
|
{
|
||||||
|
font-size: 10pt ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smallPrint {
|
||||||
|
color: #888;
|
||||||
|
font-size: 9pt ! important;
|
||||||
|
font-style: italic ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig label {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm,
|
||||||
|
#loginForm input,
|
||||||
|
#loginForm button,
|
||||||
|
#loginForm select {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Room page ***/
|
||||||
|
|
||||||
|
#roomPage {
|
||||||
|
position: absolute;
|
||||||
|
top: 120px;
|
||||||
|
bottom: 120px;
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomWrapper {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#roomName {
|
#roomName {
|
||||||
max-width: 1280px;
|
float: right;
|
||||||
width: 100%;
|
|
||||||
text-align: right;
|
|
||||||
top: -40px;
|
|
||||||
position: absolute;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomHeader {
|
||||||
|
margin: auto;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-top: 53px;
|
||||||
|
max-width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controlPanel {
|
#controlPanel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
border-top: #aaa 1px solid;
|
border-top: #aaa 1px solid;
|
||||||
}
|
}
|
||||||
@ -146,10 +219,6 @@ h1 {
|
|||||||
background-color: #faa;
|
background-color: #faa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mouse-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Participant list ***/
|
/*** Participant list ***/
|
||||||
|
|
||||||
#usersTableWrapper {
|
#usersTableWrapper {
|
||||||
@ -300,7 +369,7 @@ h1 {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
@ -310,6 +379,11 @@ h1 {
|
|||||||
-webkit-text-size-adjust:100%
|
-webkit-text-size-adjust:100%
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bubble img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.differentUser td {
|
.differentUser td {
|
||||||
padding-bottom: 5px ! important;
|
padding-bottom: 5px ! important;
|
||||||
}
|
}
|
||||||
@ -341,8 +415,8 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#room-fullscreen-image img {
|
#room-fullscreen-image img {
|
||||||
max-width: 100%;
|
max-width: 90%;
|
||||||
max-height: 100%;
|
max-height: 90%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@ -350,9 +424,14 @@ h1 {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
|
||||||
|
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Recents ***/
|
/*** Recents ***/
|
||||||
|
|
||||||
.recentsTable {
|
.recentsTable {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -402,11 +481,14 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*** Recents in the room page ***/
|
/*** Recents in the room page ***/
|
||||||
|
|
||||||
#roomRecentsTableWrapper {
|
#roomRecentsTableWrapper {
|
||||||
float: left;
|
float: left;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
margin-right: 20px;
|
padding-right: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,55 +503,14 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar img {
|
.profile-avatar img {
|
||||||
max-width: 100%;
|
width: 100%;
|
||||||
max-height: 100%;
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** User profile page ***/
|
/*** User profile page ***/
|
||||||
#user-ids {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#user-displayname {
|
#user-displayname {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
/******************************/
|
|
||||||
|
|
||||||
#header
|
|
||||||
{
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo,
|
|
||||||
#roomLogo {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header-buttons {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text_entry_section {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 100;
|
|
||||||
left: 0;
|
|
||||||
right: 10em;
|
|
||||||
width: 100%;
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_invited {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_joined {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_left {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
@ -18,6 +18,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
|||||||
'ngRoute',
|
'ngRoute',
|
||||||
'MatrixWebClientController',
|
'MatrixWebClientController',
|
||||||
'LoginController',
|
'LoginController',
|
||||||
|
'RegisterController',
|
||||||
'RoomController',
|
'RoomController',
|
||||||
'HomeController',
|
'HomeController',
|
||||||
'RecentsController',
|
'RecentsController',
|
||||||
@ -38,6 +39,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
|||||||
templateUrl: 'login/login.html',
|
templateUrl: 'login/login.html',
|
||||||
controller: 'LoginController'
|
controller: 'LoginController'
|
||||||
}).
|
}).
|
||||||
|
when('/register', {
|
||||||
|
templateUrl: 'login/register.html',
|
||||||
|
controller: 'RegisterController'
|
||||||
|
}).
|
||||||
when('/room/:room_id_or_alias', {
|
when('/room/:room_id_or_alias', {
|
||||||
templateUrl: 'room/room.html',
|
templateUrl: 'room/room.html',
|
||||||
controller: 'RoomController'
|
controller: 'RoomController'
|
||||||
@ -84,7 +89,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
|||||||
matrixWebClient.run(['$location', 'matrixService', function($location, matrixService) {
|
matrixWebClient.run(['$location', 'matrixService', function($location, matrixService) {
|
||||||
|
|
||||||
// If user auth details are not in cache, go to the login page
|
// If user auth details are not in cache, go to the login page
|
||||||
if (!matrixService.isUserLoggedIn()) {
|
if (!matrixService.isUserLoggedIn() &&
|
||||||
|
$location.path() !== "/login" &&
|
||||||
|
$location.path() !== "/register")
|
||||||
|
{
|
||||||
$location.path("login");
|
$location.path("login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
|
|||||||
*/
|
*/
|
||||||
this.uploadFile = function(file) {
|
this.uploadFile = function(file) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
console.log("Uploading " + file.name + "... to /matrix/content");
|
console.log("Uploading " + file.name + "... to /_matrix/content");
|
||||||
matrixService.uploadContent(file).then(
|
matrixService.uploadContent(file).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
var content_url = response.data.content_token;
|
var content_url = response.data.content_token;
|
||||||
|
@ -36,7 +36,7 @@ var forAllTracksOnStream = function(s, f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
angular.module('MatrixCall', [])
|
angular.module('MatrixCall', [])
|
||||||
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', function MatrixCallFactory(matrixService, matrixPhoneService) {
|
.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope) {
|
||||||
var MatrixCall = function(room_id) {
|
var MatrixCall = function(room_id) {
|
||||||
this.room_id = room_id;
|
this.room_id = room_id;
|
||||||
this.call_id = "c" + new Date().getTime();
|
this.call_id = "c" + new Date().getTime();
|
||||||
@ -73,9 +73,7 @@ angular.module('MatrixCall', [])
|
|||||||
this.state = 'wait_local_media';
|
this.state = 'wait_local_media';
|
||||||
};
|
};
|
||||||
|
|
||||||
MatrixCall.prototype.hangup = function() {
|
MatrixCall.prototype.stopAllMedia = function() {
|
||||||
console.trace("Ending call "+this.call_id);
|
|
||||||
|
|
||||||
if (this.localAVStream) {
|
if (this.localAVStream) {
|
||||||
forAllTracksOnStream(this.localAVStream, function(t) {
|
forAllTracksOnStream(this.localAVStream, function(t) {
|
||||||
t.stop();
|
t.stop();
|
||||||
@ -86,6 +84,12 @@ angular.module('MatrixCall', [])
|
|||||||
t.stop();
|
t.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MatrixCall.prototype.hangup = function() {
|
||||||
|
console.trace("Ending call "+this.call_id);
|
||||||
|
|
||||||
|
this.stopAllMedia();
|
||||||
|
|
||||||
var content = {
|
var content = {
|
||||||
version: 0,
|
version: 0,
|
||||||
@ -204,6 +208,7 @@ angular.module('MatrixCall', [])
|
|||||||
// ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
|
// ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
|
||||||
if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') {
|
if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') {
|
||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
|
$rootScope.$apply();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,8 +237,9 @@ angular.module('MatrixCall', [])
|
|||||||
t.onstarted = self.onRemoteStreamTrackStarted;
|
t.onstarted = self.onRemoteStreamTrackStarted;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
event.stream.onended = function(e) { self.onRemoteStreamEnded(e); };
|
||||||
// not currently implemented in chrome
|
// not currently implemented in chrome
|
||||||
event.stream.onstarted = this.onRemoteStreamStarted;
|
event.stream.onstarted = function(e) { self.onRemoteStreamStarted(e); };
|
||||||
var player = new Audio();
|
var player = new Audio();
|
||||||
player.src = URL.createObjectURL(s);
|
player.src = URL.createObjectURL(s);
|
||||||
player.play();
|
player.play();
|
||||||
@ -243,24 +249,19 @@ angular.module('MatrixCall', [])
|
|||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MatrixCall.prototype.onRemoteStreamEnded = function(event) {
|
||||||
|
this.state = 'ended';
|
||||||
|
this.stopAllMedia();
|
||||||
|
this.onHangup();
|
||||||
|
};
|
||||||
|
|
||||||
MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) {
|
MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) {
|
||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
};
|
};
|
||||||
|
|
||||||
MatrixCall.prototype.onHangupReceived = function() {
|
MatrixCall.prototype.onHangupReceived = function() {
|
||||||
this.state = 'ended';
|
this.state = 'ended';
|
||||||
|
this.stopAllMedia();
|
||||||
if (this.localAVStream) {
|
|
||||||
forAllTracksOnStream(this.localAVStream, function(t) {
|
|
||||||
t.stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.remoteAVStream) {
|
|
||||||
forAllTracksOnStream(this.remoteAVStream, function(t) {
|
|
||||||
t.stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onHangup();
|
this.onHangup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ angular.module('matrixService', [])
|
|||||||
|
|
||||||
// Current version of permanent storage
|
// Current version of permanent storage
|
||||||
var configVersion = 0;
|
var configVersion = 0;
|
||||||
var prefixPath = "/matrix/client/api/v1";
|
var prefixPath = "/_matrix/client/api/v1";
|
||||||
var MAPPING_PREFIX = "alias_for_";
|
var MAPPING_PREFIX = "alias_for_";
|
||||||
|
|
||||||
var doRequest = function(method, path, params, data, $httpParams) {
|
var doRequest = function(method, path, params, data, $httpParams) {
|
||||||
@ -95,14 +95,18 @@ angular.module('matrixService', [])
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Create a room
|
// Create a room
|
||||||
create: function(room_id, visibility) {
|
create: function(room_alias, visibility) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
var path = "/createRoom";
|
var path = "/createRoom";
|
||||||
|
|
||||||
return doRequest("POST", path, undefined, {
|
var req = {
|
||||||
visibility: visibility,
|
"visibility": visibility
|
||||||
room_alias_name: room_id
|
};
|
||||||
});
|
if (room_alias) {
|
||||||
|
req.room_alias_name = room_alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doRequest("POST", path, undefined, req);
|
||||||
},
|
},
|
||||||
|
|
||||||
// List all rooms joined or been invited to
|
// List all rooms joined or been invited to
|
||||||
@ -164,7 +168,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/directory/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);
|
||||||
@ -304,7 +308,7 @@ angular.module('matrixService', [])
|
|||||||
|
|
||||||
// hit the Identity Server for a 3PID request.
|
// hit the Identity Server for a 3PID request.
|
||||||
linkEmail: function(email, clientSecret, sendAttempt) {
|
linkEmail: function(email, clientSecret, sendAttempt) {
|
||||||
var path = "/matrix/identity/api/v1/validate/email/requestToken"
|
var path = "/_matrix/identity/api/v1/validate/email/requestToken"
|
||||||
var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt;
|
var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt;
|
||||||
var headers = {};
|
var headers = {};
|
||||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||||
@ -312,7 +316,7 @@ angular.module('matrixService', [])
|
|||||||
},
|
},
|
||||||
|
|
||||||
authEmail: function(clientSecret, tokenId, code) {
|
authEmail: function(clientSecret, tokenId, code) {
|
||||||
var path = "/matrix/identity/api/v1/validate/email/submitToken";
|
var path = "/_matrix/identity/api/v1/validate/email/submitToken";
|
||||||
var data = "token="+code+"&sid="+tokenId+"&clientSecret="+clientSecret;
|
var data = "token="+code+"&sid="+tokenId+"&clientSecret="+clientSecret;
|
||||||
var headers = {};
|
var headers = {};
|
||||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||||
@ -320,7 +324,7 @@ angular.module('matrixService', [])
|
|||||||
},
|
},
|
||||||
|
|
||||||
bindEmail: function(userId, tokenId, clientSecret) {
|
bindEmail: function(userId, tokenId, clientSecret) {
|
||||||
var path = "/matrix/identity/api/v1/3pid/bind";
|
var path = "/_matrix/identity/api/v1/3pid/bind";
|
||||||
var data = "mxid="+encodeURIComponent(userId)+"&sid="+tokenId+"&clientSecret="+clientSecret;
|
var data = "mxid="+encodeURIComponent(userId)+"&sid="+tokenId+"&clientSecret="+clientSecret;
|
||||||
var headers = {};
|
var headers = {};
|
||||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||||
@ -328,7 +332,7 @@ angular.module('matrixService', [])
|
|||||||
},
|
},
|
||||||
|
|
||||||
uploadContent: function(file) {
|
uploadContent: function(file) {
|
||||||
var path = "/matrix/content";
|
var path = "/_matrix/content";
|
||||||
var headers = {
|
var headers = {
|
||||||
"Content-Type": undefined // undefined means angular will figure it out
|
"Content-Type": undefined // undefined means angular will figure it out
|
||||||
};
|
};
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
angular.module('mPresence', [])
|
angular.module('mPresence', [])
|
||||||
.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
|
.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
|
||||||
|
|
||||||
// Time in ms after that a user is considered as offline/away
|
// Time in ms after that a user is considered as unavailable/away
|
||||||
var OFFLINE_TIME = 5 * 60000; // 5 mins
|
var UNAVAILABLE_TIME = 5 * 60000; // 5 mins
|
||||||
|
|
||||||
// The current presence state
|
// The current presence state
|
||||||
var state = undefined;
|
var state = undefined;
|
||||||
|
|
||||||
@ -88,11 +88,11 @@ angular.module('mPresence', [])
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback called when the user made no action on the page for OFFLINE_TIME ms.
|
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function onOfflineTimerFire() {
|
function onUnvailableTimerFire() {
|
||||||
self.setState(matrixService.presence.offline);
|
self.setState(matrixService.presence.unavailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,7 +105,7 @@ angular.module('mPresence', [])
|
|||||||
|
|
||||||
// Re-arm the timer
|
// Re-arm the timer
|
||||||
$timeout.cancel(timer);
|
$timeout.cancel(timer);
|
||||||
timer = $timeout(onOfflineTimerFire, OFFLINE_TIME);
|
timer = $timeout(onUnvailableTimerFire, UNAVAILABLE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
@ -37,6 +37,11 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
|
|||||||
$scope.joinAlias = {
|
$scope.joinAlias = {
|
||||||
room_alias: ""
|
room_alias: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.profile = {
|
||||||
|
displayName: "",
|
||||||
|
avatarUrl: ""
|
||||||
|
};
|
||||||
|
|
||||||
var refresh = function() {
|
var refresh = function() {
|
||||||
|
|
||||||
@ -53,14 +58,14 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createNewRoom = function(room_id, isPrivate) {
|
$scope.createNewRoom = function(room_alias, isPrivate) {
|
||||||
|
|
||||||
var visibility = "public";
|
var visibility = "public";
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
visibility = "private";
|
visibility = "private";
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixService.create(room_id, visibility).then(
|
matrixService.create(room_alias, visibility).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
// This room has been created. Refresh the rooms list
|
// This room has been created. Refresh the rooms list
|
||||||
console.log("Created room " + response.data.room_alias + " with id: "+
|
console.log("Created room " + response.data.room_alias + " with id: "+
|
||||||
@ -108,6 +113,26 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onInit = function() {
|
$scope.onInit = function() {
|
||||||
|
// Load profile data
|
||||||
|
// Display name
|
||||||
|
matrixService.getDisplayName($scope.config.user_id).then(
|
||||||
|
function(response) {
|
||||||
|
$scope.profile.displayName = response.data.displayname;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Can't load display name";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Avatar
|
||||||
|
matrixService.getProfilePictureUrl($scope.config.user_id).then(
|
||||||
|
function(response) {
|
||||||
|
$scope.profile.avatarUrl = response.data.avatar_url;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Can't load avatar URL";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
<div ng-controller="HomeController" data-ng-init="onInit()">
|
<div ng-controller="HomeController" data-ng-init="onInit()">
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<div>
|
<div id="genericHeading">
|
||||||
<form>
|
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="profile-avatar">
|
|
||||||
<img ng-src="{{ config.avatarUrl || 'img/default-profile.jpg' }}"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div id="user-ids">
|
|
||||||
<div id="user-displayname">{{ config.displayName }}</div>
|
|
||||||
<div>{{ config.user_id }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Recents</h3>
|
<h1>Welcome to homeserver {{ config.homeserver }}</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}"/>
|
||||||
|
</div>
|
||||||
|
<div id="user-ids">
|
||||||
|
<div id="user-displayname">{{ profile.displayName }}</div>
|
||||||
|
<div>{{ config.user_id }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Recent conversations</h3>
|
||||||
<div ng-include="'recents/recents.html'"></div>
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
@ -38,9 +33,9 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<input size="40" ng-model="newRoom.room_id" ng-enter="createNewRoom(newRoom.room_id, newRoom.private)" placeholder="(e.g. foo_channel)"/>
|
<input size="40" ng-model="newRoom.room_alias" ng-enter="createNewRoom(newRoom.room_alias, newRoom.private)" placeholder="(e.g. foo_channel)"/>
|
||||||
<input type="checkbox" ng-model="newRoom.private">private
|
<input type="checkbox" ng-model="newRoom.private">private
|
||||||
<button ng-disabled="!newRoom.room_id" ng-click="createNewRoom(newRoom.room_id, newRoom.private)">Create room</button>
|
<button ng-disabled="!newRoom.room_alias" ng-click="createNewRoom(newRoom.room_alias, newRoom.private)">Create room</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -54,5 +49,4 @@
|
|||||||
{{ feedback }}
|
{{ feedback }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB |
BIN
webclient/img/default-profile.png
Normal file
BIN
webclient/img/default-profile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
webclient/img/logo-small.png
Normal file
BIN
webclient/img/logo-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 910 B |
BIN
webclient/img/logo.png
Normal file
BIN
webclient/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
@ -4,6 +4,8 @@
|
|||||||
<title>[matrix]</title>
|
<title>[matrix]</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="app.css">
|
<link rel="stylesheet" href="app.css">
|
||||||
|
<link rel="stylesheet" href="mobile.css">
|
||||||
|
|
||||||
<link rel="icon" href="favicon.ico">
|
<link rel="icon" href="favicon.ico">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
@ -19,6 +21,7 @@
|
|||||||
<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="login/register-controller.js"></script>
|
||||||
<script src="recents/recents-controller.js"></script>
|
<script src="recents/recents-controller.js"></script>
|
||||||
<script src="recents/recents-filter.js"></script>
|
<script src="recents/recents-filter.js"></script>
|
||||||
<script src="room/room-controller.js"></script>
|
<script src="room/room-controller.js"></script>
|
||||||
@ -38,15 +41,23 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header id="header">
|
<div 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="headerContent" ng-hide="'/login' == location || '/register' == location">
|
||||||
|
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
|
||||||
|
|
||||||
|
<button ng-click='goToPage("/")'>Home</button>
|
||||||
<button ng-click='goToPage("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>
|
</div>
|
||||||
|
|
||||||
<div ng-view></div>
|
<div id="page" ng-view></div>
|
||||||
|
|
||||||
|
<div id="footer" ng-hide="location.indexOf('/room') == 0">
|
||||||
|
<div id="footerContent">
|
||||||
|
© 2014 Matrix.org
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
angular.module('LoginController', ['matrixService'])
|
angular.module('LoginController', ['matrixService'])
|
||||||
.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService',
|
.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService',
|
||||||
function($scope, $location, matrixService, eventStreamService) {
|
function($scope, $location, matrixService, eventStreamService) {
|
||||||
@ -7,7 +23,10 @@ angular.module('LoginController', ['matrixService'])
|
|||||||
// Assume that this is hosted on the home server, in which case the URL
|
// Assume that this is hosted on the home server, in which case the URL
|
||||||
// contains the home server.
|
// contains the home server.
|
||||||
var hs_url = $location.protocol() + "://" + $location.host();
|
var hs_url = $location.protocol() + "://" + $location.host();
|
||||||
if ($location.port()) {
|
if ($location.port() &&
|
||||||
|
!($location.protocol() === "http" && $location.port() === 80) &&
|
||||||
|
!($location.protocol() === "https" && $location.port() === 443))
|
||||||
|
{
|
||||||
hs_url += ":" + $location.port();
|
hs_url += ":" + $location.port();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,57 +35,18 @@ angular.module('LoginController', ['matrixService'])
|
|||||||
desired_user_name: "",
|
desired_user_name: "",
|
||||||
user_id: "",
|
user_id: "",
|
||||||
password: "",
|
password: "",
|
||||||
identityServer: "",
|
identityServer: "http://matrix.org:8090",
|
||||||
pwd1: "",
|
pwd1: "",
|
||||||
pwd2: ""
|
pwd2: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.login_types = [ "email", "mxid" ];
|
||||||
|
$scope.login_type_label = {
|
||||||
// Set the urls
|
"email": "Email address",
|
||||||
matrixService.setConfig({
|
"mxid": "Matrix ID (e.g. @bob:matrix.org or bob)",
|
||||||
homeserver: $scope.account.homeserver,
|
|
||||||
identityServer: $scope.account.identityServer
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.account.pwd1 !== $scope.account.pwd2) {
|
|
||||||
$scope.feedback = "Passwords don't match.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if ($scope.account.pwd1.length < 6) {
|
|
||||||
$scope.feedback = "Password must be at least 6 characters.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixService.register($scope.account.desired_user_name, $scope.account.pwd1).then(
|
|
||||||
function(response) {
|
|
||||||
$scope.feedback = "Success";
|
|
||||||
// Update the current config
|
|
||||||
var config = matrixService.config();
|
|
||||||
angular.extend(config, {
|
|
||||||
access_token: response.data.access_token,
|
|
||||||
user_id: response.data.user_id
|
|
||||||
});
|
|
||||||
matrixService.setConfig(config);
|
|
||||||
|
|
||||||
// And permanently save it
|
|
||||||
matrixService.saveConfig();
|
|
||||||
eventStreamService.resume();
|
|
||||||
// Go to the user's rooms list page
|
|
||||||
$location.url("home");
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
if (error.data) {
|
|
||||||
if (error.data.errcode === "M_USER_IN_USE") {
|
|
||||||
$scope.feedback = "Username already taken.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (error.status === 0) {
|
|
||||||
$scope.feedback = "Unable to talk to the server.";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
$scope.login_type = 'mxid'; // TODO: remember the user's preferred login_type
|
||||||
|
|
||||||
$scope.login = function() {
|
$scope.login = function() {
|
||||||
matrixService.setConfig({
|
matrixService.setConfig({
|
||||||
homeserver: $scope.account.homeserver,
|
homeserver: $scope.account.homeserver,
|
||||||
|
@ -1,55 +1,49 @@
|
|||||||
<div ng-controller="LoginController" class="login">
|
<div ng-controller="LoginController" class="login">
|
||||||
<h1 id="logo">[matrix]</h1>
|
<div id="wrapper" class="loginWrapper">
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
|
||||||
|
|
||||||
{{ feedback }}
|
|
||||||
|
|
||||||
<h3>Register for an account:</h3>
|
<a href ng-click="goToPage('/')">
|
||||||
<form novalidate>
|
<img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/>
|
||||||
<input id="desired_user_name" size="70" type="text" auto-focus ng-model="account.desired_user_name" placeholder="User name (ex:bob)"/>
|
</a>
|
||||||
<br/>
|
|
||||||
<input id="pwd1" size="70" type="password" auto-focus ng-model="account.pwd1" placeholder="Type a password"/>
|
|
||||||
<br/>
|
|
||||||
<input id="pwd2" size="70" type="password" auto-focus ng-model="account.pwd2" placeholder="Re-type your password"/>
|
|
||||||
<br/>
|
|
||||||
<!-- New user registration -->
|
|
||||||
<div>
|
|
||||||
<br/>
|
|
||||||
<button ng-click="register()" ng-disabled="!account.desired_user_name || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Register</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h3>Got an account?</h3>
|
|
||||||
<form novalidate>
|
|
||||||
<!-- Login with an registered user -->
|
|
||||||
<div>{{ login_error_msg }} </div>
|
|
||||||
<div>
|
|
||||||
<input id="user_id" size="70" type="text" auto-focus ng-model="account.user_id" placeholder="User ID (ex:@bob:localhost or bob)"/>
|
|
||||||
<br />
|
|
||||||
<input id="password" size="70" type="password" ng-model="account.password" placeholder="Password"/><br />
|
|
||||||
<br/>
|
|
||||||
<button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h3>Servers</h3>
|
|
||||||
<form novalidate>
|
|
||||||
<div>
|
|
||||||
Home Server:
|
|
||||||
<input id="homeserver" size="57" type="text" ng-model="account.homeserver" placeholder="Home server URL (ex: http://localhost:8080)"/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
Identity Server:
|
|
||||||
<input id="identityServer" size="56" type="text" ng-model="account.identityServer" placeholder="Identity server URL (ex: http://localhost:8090)"/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
</form>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<form id="loginForm" novalidate>
|
||||||
|
<!-- Login with an registered user -->
|
||||||
|
<div>
|
||||||
|
Log in using:<br/>
|
||||||
|
|
||||||
|
<div ng-repeat="type in login_types">
|
||||||
|
<input type="radio" ng-model="$parent.login_type" value="{{ type }}" id="radio_{{ type }}"/>
|
||||||
|
<label for="radio_{{ type }}">{{ login_type_label[type] }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<br/>
|
||||||
|
<input id="user_id" size="32" type="text" ng-focus="true" ng-model="account.user_id" placeholder="{{ login_type_label[login_type] }}"/>
|
||||||
|
<br/>
|
||||||
|
<input id="password" size="32" type="password" ng-model="account.password" placeholder="Password"/>
|
||||||
|
<br/><br/>
|
||||||
|
<button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
|
||||||
|
<br/><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feedback">{{ feedback }} {{ login_error_msg }}</div>
|
||||||
|
|
||||||
|
<div id="serverConfig">
|
||||||
|
<label for="homeserver">Home Server:</label>
|
||||||
|
<input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/>
|
||||||
|
<div class="smallPrint">Your home server stores all your conversation and account data.</div>
|
||||||
|
<label for="identityServer">Identity Server:</label>
|
||||||
|
<input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/>
|
||||||
|
<div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/>
|
||||||
|
Only http://matrix.org:8090 currently exists.</div>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<a href="#/register" style="padding-right: 3em">Create account</a>
|
||||||
|
<a href="#/reset_password">Forgotten password?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
102
webclient/login/register-controller.js
Normal file
102
webclient/login/register-controller.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
angular.module('RegisterController', ['matrixService'])
|
||||||
|
.controller('RegisterController', ['$scope', '$location', 'matrixService', 'eventStreamService',
|
||||||
|
function($scope, $location, matrixService, eventStreamService) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// FIXME: factor out duplication with login-controller.js
|
||||||
|
|
||||||
|
// Assume that this is hosted on the home server, in which case the URL
|
||||||
|
// contains the home server.
|
||||||
|
var hs_url = $location.protocol() + "://" + $location.host();
|
||||||
|
if ($location.port() &&
|
||||||
|
!($location.protocol() === "http" && $location.port() === 80) &&
|
||||||
|
!($location.protocol() === "https" && $location.port() === 443))
|
||||||
|
{
|
||||||
|
hs_url += ":" + $location.port();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.account = {
|
||||||
|
homeserver: hs_url,
|
||||||
|
desired_user_id: "",
|
||||||
|
desired_user_name: "",
|
||||||
|
password: "",
|
||||||
|
identityServer: "http://matrix.org:8090",
|
||||||
|
pwd1: "",
|
||||||
|
pwd2: "",
|
||||||
|
displayName : ""
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.register = function() {
|
||||||
|
|
||||||
|
// Set the urls
|
||||||
|
matrixService.setConfig({
|
||||||
|
homeserver: $scope.account.homeserver,
|
||||||
|
identityServer: $scope.account.identityServer
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.account.pwd1 !== $scope.account.pwd2) {
|
||||||
|
$scope.feedback = "Passwords don't match.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ($scope.account.pwd1.length < 6) {
|
||||||
|
$scope.feedback = "Password must be at least 6 characters.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrixService.register($scope.account.desired_user_id, $scope.account.pwd1).then(
|
||||||
|
function(response) {
|
||||||
|
$scope.feedback = "Success";
|
||||||
|
// Update the current config
|
||||||
|
var config = matrixService.config();
|
||||||
|
angular.extend(config, {
|
||||||
|
access_token: response.data.access_token,
|
||||||
|
user_id: response.data.user_id
|
||||||
|
});
|
||||||
|
matrixService.setConfig(config);
|
||||||
|
|
||||||
|
// And permanently save it
|
||||||
|
matrixService.saveConfig();
|
||||||
|
|
||||||
|
// Update the global scoped used_id var (used in the app header)
|
||||||
|
$scope.updateHeader();
|
||||||
|
|
||||||
|
eventStreamService.resume();
|
||||||
|
|
||||||
|
if ($scope.account.displayName) {
|
||||||
|
// FIXME: handle errors setting displayName
|
||||||
|
matrixService.setDisplayName($scope.account.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the user's rooms list page
|
||||||
|
$location.url("home");
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
if (error.data) {
|
||||||
|
if (error.data.errcode === "M_USER_IN_USE") {
|
||||||
|
$scope.feedback = "Username already taken.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (error.status === 0) {
|
||||||
|
$scope.feedback = "Unable to talk to the server.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}]);
|
||||||
|
|
48
webclient/login/register.html
Normal file
48
webclient/login/register.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<div ng-controller="RegisterController" class="register">
|
||||||
|
<div id="wrapper" class="loginWrapper">
|
||||||
|
|
||||||
|
<a href ng-click="goToPage('/')">
|
||||||
|
<img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/>
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<form id="loginForm" novalidate>
|
||||||
|
<div>
|
||||||
|
Create account:<br/>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<br/>
|
||||||
|
<input id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)"/>
|
||||||
|
<div class="smallPrint">Specifying an email address lets other users find you on Matrix more easily,<br/>
|
||||||
|
and gives you a way to reset your password</div>
|
||||||
|
<input id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/>
|
||||||
|
<br/>
|
||||||
|
<input id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/>
|
||||||
|
<br/>
|
||||||
|
<input id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/>
|
||||||
|
<br/>
|
||||||
|
<input id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<button ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button>
|
||||||
|
<br/><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feedback">{{ feedback }} {{ login_error_msg }}</div>
|
||||||
|
|
||||||
|
<div id="serverConfig">
|
||||||
|
<label for="homeserver">Home Server:</label>
|
||||||
|
<input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/>
|
||||||
|
<div class="smallPrint">Your home server stores all your conversation and account data.</div>
|
||||||
|
<label for="identityServer">Identity Server:</label>
|
||||||
|
<input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/>
|
||||||
|
<div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/>
|
||||||
|
Only http://matrix.org:8090 currently exists.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
92
webclient/mobile.css
Normal file
92
webclient/mobile.css
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*** Mobile voodoo ***/
|
||||||
|
@media all and (max-device-width: 640px) {
|
||||||
|
|
||||||
|
#messageTableWrapper {
|
||||||
|
margin-right: 0px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftBlock {
|
||||||
|
width: 8em ! important;
|
||||||
|
font-size: 8px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightBlock {
|
||||||
|
width: 0px ! important;
|
||||||
|
display: none ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 36px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent button {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messageTable,
|
||||||
|
#wrapper,
|
||||||
|
#controls {
|
||||||
|
max-width: 640px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerUserId,
|
||||||
|
#roomHeader img,
|
||||||
|
#userIdCell,
|
||||||
|
#roomRecentsTableWrapper,
|
||||||
|
#usersTableWrapper,
|
||||||
|
.extraControls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonsCell {
|
||||||
|
width: 60px ! important;
|
||||||
|
padding-left: 20px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomLogo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
font-size: 12px ! important;
|
||||||
|
min-height: 20px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomHeader {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomName {
|
||||||
|
float: left;
|
||||||
|
font-size: 14px ! important;
|
||||||
|
margin-top: 0px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomPage {
|
||||||
|
top: 35px ! important;
|
||||||
|
left: 5px ! important;
|
||||||
|
right: 5px ! important;
|
||||||
|
bottom: 70px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controlPanel {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stop zoom on select */
|
||||||
|
select:focus,
|
||||||
|
textarea,
|
||||||
|
input
|
||||||
|
{
|
||||||
|
font-size: 16px ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -88,7 +88,7 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
call.onHangup = $scope.onCallHangup;
|
call.onHangup = $scope.onCallHangup;
|
||||||
$scope.currentCall = call;
|
$scope.currentCall = call;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.memberCount = function() {
|
$scope.memberCount = function() {
|
||||||
return Object.keys($scope.members).length;
|
return Object.keys($scope.members).length;
|
||||||
};
|
};
|
||||||
@ -175,6 +175,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
|
|
||||||
// set target_user_id to keep things clear
|
// set target_user_id to keep things clear
|
||||||
var target_user_id = chunk.state_key;
|
var target_user_id = chunk.state_key;
|
||||||
|
|
||||||
|
var now = new Date().getTime();
|
||||||
|
|
||||||
var isNewMember = !(target_user_id in $scope.members);
|
var isNewMember = !(target_user_id in $scope.members);
|
||||||
if (isNewMember) {
|
if (isNewMember) {
|
||||||
@ -185,44 +187,14 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
if ("mtime_age" in chunk.content) {
|
if ("mtime_age" in chunk.content) {
|
||||||
chunk.mtime_age = chunk.content.mtime_age;
|
chunk.mtime_age = chunk.content.mtime_age;
|
||||||
}
|
}
|
||||||
// Once the HS reliably returns the displaynames & avatar_urls for both
|
|
||||||
// local and remote users, we should use this rather than the evalAsync block
|
|
||||||
// below
|
|
||||||
if ("displayname" in chunk.content) {
|
if ("displayname" in chunk.content) {
|
||||||
chunk.displayname = chunk.content.displayname;
|
chunk.displayname = chunk.content.displayname;
|
||||||
}
|
}
|
||||||
if ("avatar_url" in chunk.content) {
|
if ("avatar_url" in chunk.content) {
|
||||||
chunk.avatar_url = chunk.content.avatar_url;
|
chunk.avatar_url = chunk.content.avatar_url;
|
||||||
}
|
}
|
||||||
$scope.members[target_user_id] = chunk;
|
chunk.last_updated = now;
|
||||||
|
$scope.members[target_user_id] = chunk;
|
||||||
/*
|
|
||||||
// Stale code for explicitly hammering the homeserver for every displayname & avatar_url
|
|
||||||
|
|
||||||
// get their display name and profile picture and set it to their
|
|
||||||
// member entry in $scope.members. We HAVE to use $timeout with 0 delay
|
|
||||||
// to make this function run AFTER the current digest cycle, else the
|
|
||||||
// response may update a STALE VERSION of the member list (manifesting
|
|
||||||
// as no member names appearing, or appearing sporadically).
|
|
||||||
$scope.$evalAsync(function() {
|
|
||||||
matrixService.getDisplayName(chunk.target_user_id).then(
|
|
||||||
function(response) {
|
|
||||||
var member = $scope.members[chunk.target_user_id];
|
|
||||||
if (member !== undefined) {
|
|
||||||
member.displayname = response.data.displayname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
matrixService.getProfilePictureUrl(chunk.target_user_id).then(
|
|
||||||
function(response) {
|
|
||||||
var member = $scope.members[chunk.target_user_id];
|
|
||||||
if (member !== undefined) {
|
|
||||||
member.avatar_url = response.data.avatar_url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (target_user_id in $rootScope.presence) {
|
if (target_user_id in $rootScope.presence) {
|
||||||
updatePresence($rootScope.presence[target_user_id]);
|
updatePresence($rootScope.presence[target_user_id]);
|
||||||
@ -234,6 +206,12 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
member.content.membership = chunk.content.membership;
|
member.content.membership = chunk.content.membership;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var updateMemberListPresenceAge = function() {
|
||||||
|
$scope.now = new Date().getTime();
|
||||||
|
// TODO: don't bother polling every 5s if we know none of our counters are younger than 1 minute
|
||||||
|
$timeout(updateMemberListPresenceAge, 5 * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
var updatePresence = function(chunk) {
|
var updatePresence = function(chunk) {
|
||||||
if (!(chunk.content.user_id in $scope.members)) {
|
if (!(chunk.content.user_id in $scope.members)) {
|
||||||
@ -275,6 +253,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
if ($scope.textInput.indexOf("/me") === 0) {
|
if ($scope.textInput.indexOf("/me") === 0) {
|
||||||
promise = matrixService.sendEmoteMessage($scope.room_id, $scope.textInput.substr(4));
|
promise = matrixService.sendEmoteMessage($scope.room_id, $scope.textInput.substr(4));
|
||||||
}
|
}
|
||||||
|
else if ($scope.textInput.indexOf("/nick ") === 0) {
|
||||||
|
// Change user display name
|
||||||
|
promise = matrixService.setDisplayName($scope.textInput.substr(6));
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
promise = matrixService.sendTextMessage($scope.room_id, $scope.textInput);
|
promise = matrixService.sendTextMessage($scope.room_id, $scope.textInput);
|
||||||
}
|
}
|
||||||
@ -395,8 +377,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
|
|
||||||
// Make recents highlight the current room
|
// Make recents highlight the current room
|
||||||
$scope.recentsSelectedRoomID = $scope.room_id;
|
$scope.recentsSelectedRoomID = $scope.room_id;
|
||||||
|
|
||||||
paginate(MESSAGES_PER_PAGINATION);
|
paginate(MESSAGES_PER_PAGINATION);
|
||||||
|
|
||||||
|
updateMemberListPresenceAge();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.inviteUser = function(user_id) {
|
$scope.inviteUser = function(user_id) {
|
||||||
@ -404,18 +388,13 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
matrixService.invite($scope.room_id, user_id).then(
|
matrixService.invite($scope.room_id, user_id).then(
|
||||||
function() {
|
function() {
|
||||||
console.log("Invited.");
|
console.log("Invited.");
|
||||||
$scope.feedback = "Request for invitation succeeds";
|
$scope.feedback = "Invite sent successfully";
|
||||||
},
|
},
|
||||||
function(reason) {
|
function(reason) {
|
||||||
$scope.feedback = "Failure: " + reason;
|
$scope.feedback = "Failure: " + reason;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open the user profile page
|
|
||||||
$scope.goToUserPage = function(user_id) {
|
|
||||||
$location.url("/user/" + user_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.leaveRoom = function() {
|
$scope.leaveRoom = function() {
|
||||||
|
|
||||||
matrixService.leave($scope.room_id).then(
|
matrixService.leave($scope.room_id).then(
|
||||||
@ -487,7 +466,5 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.onCallHangup = function() {
|
$scope.onCallHangup = function() {
|
||||||
$scope.feedback = "Call ended";
|
|
||||||
$scope.currentCall = undefined;
|
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
<div ng-controller="RoomController" data-ng-init="onInit()" class="room">
|
<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;">
|
||||||
<h1 id="roomLogo">[matrix]</h1>
|
|
||||||
|
|
||||||
<div id="page">
|
<div id="roomHeader">
|
||||||
<div id="wrapper">
|
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||||
|
<div id="roomName">
|
||||||
<div id="roomName">
|
{{ room_alias || room_id }}
|
||||||
{{ room_alias || room_id }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="roomPage">
|
||||||
|
<div id="roomWrapper">
|
||||||
|
|
||||||
<div id="roomRecentsTableWrapper">
|
<div id="roomRecentsTableWrapper">
|
||||||
<div ng-include="'recents/recents.html'"></div>
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -15,17 +17,17 @@
|
|||||||
<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">
|
||||||
<td class="userAvatar mouse-pointer" ng-click="goToUserPage(member.id)">
|
<td class="userAvatar mouse-pointer" ng-click="$parent.goToUserPage(member.id)" ng-class="member.membership == 'invite' ? 'invited' : ''">
|
||||||
<img class="userAvatarImage"
|
<img class="userAvatarImage"
|
||||||
ng-src="{{member.avatar_url || 'img/default-profile.jpg'}}"
|
ng-src="{{member.avatar_url || 'img/default-profile.png'}}"
|
||||||
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
|
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
|
||||||
title="{{ member.id }}"
|
title="{{ member.id }}"
|
||||||
width="80" height="80"/>
|
width="80" height="80"/>
|
||||||
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
||||||
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="userPresence" ng-class="member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')">
|
<td class="userPresence" ng-class="(member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
||||||
{{ member.mtime_age | duration }}<br/>{{ member.mtime_age ? "ago" : "" }}
|
<span ng-show="member.mtime_age">{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>ago</span>
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +42,7 @@
|
|||||||
<div class="timestamp">{{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}</div>
|
<div class="timestamp">{{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="avatar">
|
<td class="avatar">
|
||||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
|
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
|
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
|
||||||
</td>
|
</td>
|
||||||
<td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'">
|
<td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'">
|
||||||
@ -64,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="rightBlock">
|
<td class="rightBlock">
|
||||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
|
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -86,12 +88,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td id="buttonsCell">
|
<td id="buttonsCell">
|
||||||
<button ng-click="send()">Send</button>
|
<button ng-click="send()">Send</button>
|
||||||
<button m-file-input="imageFileToSend">Image</button>
|
<button m-file-input="imageFileToSend" class="extraControls">Image</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="extraControls">
|
<div class="extraControls">
|
||||||
<span>
|
<span>
|
||||||
Invite a user:
|
Invite a user:
|
||||||
<input ng-model="userIDToInvite" size="32" type="text" placeholder="User ID (ex:@user:homeserver)"/>
|
<input ng-model="userIDToInvite" size="32" type="text" placeholder="User ID (ex:@user:homeserver)"/>
|
||||||
@ -105,6 +107,10 @@
|
|||||||
<button ng-click="hangupCall()">Reject</button>
|
<button ng-click="hangupCall()">Reject</button>
|
||||||
</div>
|
</div>
|
||||||
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing'">Hang up</button>
|
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing'">Hang up</button>
|
||||||
|
<span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
|
||||||
|
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
|
||||||
|
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
|
||||||
|
<span ng-show="currentCall.state == 'ended'">Call Ended</span>
|
||||||
<span style="display: none; ">{{ currentCall.state }}</span>
|
<span style="display: none; ">{{ currentCall.state }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -22,8 +22,38 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
|
|||||||
$scope.config = matrixService.config();
|
$scope.config = matrixService.config();
|
||||||
|
|
||||||
$scope.profile = {
|
$scope.profile = {
|
||||||
displayName: $scope.config.displayName,
|
displayName: "",
|
||||||
avatarUrl: $scope.config.avatarUrl
|
avatarUrl: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// The profile as stored on the server
|
||||||
|
$scope.profileOnServer = {
|
||||||
|
displayName: "",
|
||||||
|
avatarUrl: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.onInit = function() {
|
||||||
|
// Load profile data
|
||||||
|
// Display name
|
||||||
|
matrixService.getDisplayName($scope.config.user_id).then(
|
||||||
|
function(response) {
|
||||||
|
$scope.profile.displayName = response.data.displayname;
|
||||||
|
$scope.profileOnServer.displayName = response.data.displayname;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Can't load display name";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Avatar
|
||||||
|
matrixService.getProfilePictureUrl($scope.config.user_id).then(
|
||||||
|
function(response) {
|
||||||
|
$scope.profile.avatarUrl = response.data.avatar_url;
|
||||||
|
$scope.profileOnServer.avatarUrl = response.data.avatar_url;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Can't load avatar URL";
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.$watch("profile.avatarFile", function(newValue, oldValue) {
|
$scope.$watch("profile.avatarFile", function(newValue, oldValue) {
|
||||||
@ -41,10 +71,10 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
|
|||||||
});
|
});
|
||||||
|
|
||||||
$scope.saveProfile = function() {
|
$scope.saveProfile = function() {
|
||||||
if ($scope.profile.displayName !== $scope.config.displayName) {
|
if ($scope.profile.displayName !== $scope.profileOnServer.displayName) {
|
||||||
setDisplayName($scope.profile.displayName);
|
setDisplayName($scope.profile.displayName);
|
||||||
}
|
}
|
||||||
if ($scope.profile.avatarUrl !== $scope.config.avatarUrl) {
|
if ($scope.profile.avatarUrl !== $scope.profileOnServer.avatarUrl) {
|
||||||
setAvatar($scope.profile.avatarUrl);
|
setAvatar($scope.profile.avatarUrl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -53,11 +83,6 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
|
|||||||
matrixService.setDisplayName(displayName).then(
|
matrixService.setDisplayName(displayName).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.feedback = "Updated display name.";
|
$scope.feedback = "Updated display name.";
|
||||||
|
|
||||||
var config = matrixService.config();
|
|
||||||
config.displayName = displayName;
|
|
||||||
matrixService.setConfig(config);
|
|
||||||
matrixService.saveConfig();
|
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
$scope.feedback = "Can't update display name: " + error.data;
|
$scope.feedback = "Can't update display name: " + error.data;
|
||||||
@ -71,11 +96,6 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
|
|||||||
function(response) {
|
function(response) {
|
||||||
console.log("Updated avatar");
|
console.log("Updated avatar");
|
||||||
$scope.feedback = "Updated avatar.";
|
$scope.feedback = "Updated avatar.";
|
||||||
|
|
||||||
var config = matrixService.config();
|
|
||||||
config.avatarUrl = avatarURL;
|
|
||||||
matrixService.setConfig(config);
|
|
||||||
matrixService.saveConfig();
|
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
$scope.feedback = "Can't update avatar: " + error.data;
|
$scope.feedback = "Can't update avatar: " + error.data;
|
||||||
@ -143,4 +163,23 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*** Desktop notifications section ***/
|
||||||
|
$scope.settings = {
|
||||||
|
notifications: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the browser supports it, check the desktop notification state
|
||||||
|
if ("Notification" in window) {
|
||||||
|
$scope.settings.notifications = window.Notification.permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.requestNotifications = function() {
|
||||||
|
console.log("requestNotifications");
|
||||||
|
window.Notification.requestPermission(function (permission) {
|
||||||
|
console.log(" -> User decision: " + permission);
|
||||||
|
$scope.settings.notifications = permission;
|
||||||
|
});
|
||||||
|
};
|
||||||
}]);
|
}]);
|
@ -1,35 +1,29 @@
|
|||||||
<div ng-controller="SettingsController" class="user">
|
<div ng-controller="SettingsController" class="user" data-ng-init="onInit()">
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<h3>Me</h3>
|
<div id="genericHeading">
|
||||||
<div>
|
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<div class="section">
|
||||||
<form>
|
<form>
|
||||||
<table>
|
<div class="profile-avatar">
|
||||||
<tr>
|
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}" m-file-input="profile.avatarFile"/>
|
||||||
<td>
|
</div>
|
||||||
<div class="profile-avatar">
|
<div id="user-ids">
|
||||||
<img ng-src="{{ profile.avatarUrl || 'img/default-profile.jpg' }}" m-file-input="profile.avatarFile"/>
|
<input size="40" ng-model="profile.displayName" placeholder="Your display name"/>
|
||||||
</div>
|
<br/>
|
||||||
</td>
|
<button ng-disabled="(profile.displayName == profileOnServer.displayName) && (profile.avatarUrl == profileOnServer.avatarUrl)"
|
||||||
<td>
|
ng-click="saveProfile()">Save</button>
|
||||||
<div id="user-ids">
|
</div>
|
||||||
<input size="40" ng-model="profile.displayName" placeholder="Your name"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button ng-disabled="(profile.displayName == config.displayName) && (profile.avatarUrl == config.avatarUrl)"
|
|
||||||
ng-click="saveProfile()">Save</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<h3>Linked emails</h3>
|
<h3>Linked emails</h3>
|
||||||
<div>
|
<div class="section">
|
||||||
<form>
|
<form>
|
||||||
<input size="40" ng-model="linkedEmails.linkNewEmail" ng-enter="linkEmail(linkedEmails.linkNewEmail)" />
|
<input size="40" ng-model="linkedEmails.linkNewEmail" ng-enter="linkEmail(linkedEmails.linkNewEmail)" />
|
||||||
<button ng-disabled="!linkedEmails.linkNewEmail" ng-click="linkEmail(linkedEmails.linkNewEmail)">
|
<button ng-disabled="!linkedEmails.linkNewEmail" ng-click="linkEmail(linkedEmails.linkNewEmail)">
|
||||||
@ -52,22 +46,35 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<h3>Configuration</h3>
|
<h3>Desktop notifications</h3>
|
||||||
<div>
|
<div class="section" ng-switch="settings.notifications">
|
||||||
<div>Home server: {{ config.homeserver }} </div>
|
<div ng-switch-when="granted">
|
||||||
<div>User ID: {{ config.user_id }} </div>
|
Notifications are enabled.
|
||||||
<div>Access token: {{ config.access_token }} </div>
|
</div>
|
||||||
|
<div ng-switch-when="denied">
|
||||||
|
You have denied permission for notifications.<br/>
|
||||||
|
To enable it, reset the notification setting for this web site into your browser settings.
|
||||||
|
</div>
|
||||||
|
<div ng-switch-when="default">
|
||||||
|
<button ng-click="requestNotifications()" style="font-size: 14pt">Enable desktop notifications</button>
|
||||||
|
</div>
|
||||||
|
<div ng-switch-default="">
|
||||||
|
Sorry, your browser does not support notifications.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<div>
|
<h3>Configuration</h3>
|
||||||
<div><button ng-click="requestNotifications()">Request notifications</button></div>
|
<div class="section">
|
||||||
|
<div>Home server: {{ config.homeserver }} </div>
|
||||||
|
<div>Identity server: {{ config.identityServer }} </div>
|
||||||
|
<div>User ID: {{ config.user_id }} </div>
|
||||||
|
<div>Access token: {{ config.access_token }} </div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
{{ feedback }}
|
{{ feedback }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,14 +25,42 @@ angular.module('UserController', ['matrixService'])
|
|||||||
avatar_url: undefined
|
avatar_url: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.user_id = matrixService.config().user_id;
|
||||||
|
|
||||||
matrixService.getDisplayName($scope.user.id).then(
|
matrixService.getDisplayName($scope.user.id).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.user.displayname = response.data.displayname;
|
$scope.user.displayname = response.data.displayname;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
matrixService.getProfilePictureUrl($scope.user.id).then(
|
matrixService.getProfilePictureUrl($scope.user.id).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.user.avatar_url = response.data.avatar_url;
|
$scope.user.avatar_url = response.data.avatar_url;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$scope.messageUser = function() {
|
||||||
|
|
||||||
|
// FIXME: create a new room every time, for now
|
||||||
|
|
||||||
|
matrixService.create(null, 'private').then(
|
||||||
|
function(response) {
|
||||||
|
// This room has been created. Refresh the rooms list
|
||||||
|
var room_id = response.data.room_id;
|
||||||
|
console.log("Created room with id: "+ room_id);
|
||||||
|
|
||||||
|
matrixService.invite(room_id, $scope.user.id).then(
|
||||||
|
function() {
|
||||||
|
$scope.feedback = "Invite sent successfully";
|
||||||
|
$scope.$parent.goToPage("/room/" + room_id);
|
||||||
|
},
|
||||||
|
function(reason) {
|
||||||
|
$scope.feedback = "Failure: " + JSON.stringify(reason);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Failure: " + JSON.stringify(error.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
@ -1,31 +1,25 @@
|
|||||||
<div ng-controller="UserController" class="user">
|
<div ng-controller="UserController" class="user">
|
||||||
<h1 id="logo">[matrix]</h1>
|
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<div>
|
<div id="genericHeading">
|
||||||
<form>
|
<a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="profile-avatar">
|
|
||||||
<img ng-src="{{ user.avatar_url || 'img/default-profile.jpg' }}"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div id="user-ids">
|
|
||||||
<div id="user-displayname">{{ user.displayname }}</div>
|
|
||||||
<div>{{ user.id }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h1>{{ user.displayname || user.id }}</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<img ng-src="{{ user.avatar_url || 'img/default-profile.png' }}"/>
|
||||||
|
</div>
|
||||||
|
<div id="user-ids">
|
||||||
|
<div>{{ user.id }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button ng-hide="user.id == user_id" ng-click="messageUser()" style="font-size: 14pt; margin-top: 40px; margin-bottom: 40px">Start chat</button>
|
||||||
|
<br/>
|
||||||
{{ feedback }}
|
{{ feedback }}
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user