mirror of
https://mau.dev/maunium/synapse.git
synced 2024-11-06 22:59:22 +01:00
Merge branch 'develop' of github.com:matrix-org/synapse into rejections_storage
Conflicts: synapse/storage/__init__.py synapse/storage/schema/delta/v12.sql
This commit is contained in:
commit
e0b7c521cb
125 changed files with 3018 additions and 2067 deletions
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -26,17 +26,18 @@ htmlcov
|
||||||
|
|
||||||
demo/*.db
|
demo/*.db
|
||||||
demo/*.log
|
demo/*.log
|
||||||
|
demo/*.log.*
|
||||||
demo/*.pid
|
demo/*.pid
|
||||||
|
demo/media_store.*
|
||||||
demo/etc
|
demo/etc
|
||||||
|
|
||||||
graph/*.svg
|
|
||||||
graph/*.png
|
|
||||||
graph/*.dot
|
|
||||||
|
|
||||||
**/webclient/config.js
|
|
||||||
**/webclient/test/coverage/
|
|
||||||
**/webclient/test/environment-protractor.js
|
|
||||||
|
|
||||||
uploads
|
uploads
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
media_store/
|
||||||
|
|
||||||
|
*.tac
|
||||||
|
|
||||||
|
build/
|
||||||
|
|
||||||
|
localhost-800*/
|
||||||
|
|
16
MANIFEST.in
16
MANIFEST.in
|
@ -1,4 +1,14 @@
|
||||||
recursive-include docs *
|
include synctl
|
||||||
recursive-include tests *.py
|
include LICENSE
|
||||||
|
include VERSION
|
||||||
|
include *.rst
|
||||||
|
include demo/README
|
||||||
|
|
||||||
recursive-include synapse/storage/schema *.sql
|
recursive-include synapse/storage/schema *.sql
|
||||||
recursive-include syweb/webclient *
|
|
||||||
|
recursive-include demo *.dh
|
||||||
|
recursive-include demo *.py
|
||||||
|
recursive-include demo *.sh
|
||||||
|
recursive-include docs *
|
||||||
|
recursive-include scripts *
|
||||||
|
recursive-include tests *.py
|
||||||
|
|
|
@ -52,7 +52,7 @@ resulting conflicts during the upgrade process.
|
||||||
Before running the command the homeserver should be first completely
|
Before running the command the homeserver should be first completely
|
||||||
shutdown. To run it, simply specify the location of the database, e.g.:
|
shutdown. To run it, simply specify the location of the database, e.g.:
|
||||||
|
|
||||||
./database-prepare-for-0.5.0.sh "homeserver.db"
|
./scripts/database-prepare-for-0.5.0.sh "homeserver.db"
|
||||||
|
|
||||||
Once this has successfully completed it will be safe to restart the
|
Once this has successfully completed it will be safe to restart the
|
||||||
homeserver. You may notice that the homeserver takes a few seconds longer to
|
homeserver. You may notice that the homeserver takes a few seconds longer to
|
||||||
|
@ -147,7 +147,7 @@ rooms the home server was a member of and room alias mappings.
|
||||||
Before running the command the homeserver should be first completely
|
Before running the command the homeserver should be first completely
|
||||||
shutdown. To run it, simply specify the location of the database, e.g.:
|
shutdown. To run it, simply specify the location of the database, e.g.:
|
||||||
|
|
||||||
./database-prepare-for-0.0.1.sh "homeserver.db"
|
./scripts/database-prepare-for-0.0.1.sh "homeserver.db"
|
||||||
|
|
||||||
Once this has successfully completed it will be safe to restart the
|
Once this has successfully completed it will be safe to restart the
|
||||||
homeserver. You may notice that the homeserver takes a few seconds longer to
|
homeserver. You may notice that the homeserver takes a few seconds longer to
|
||||||
|
|
|
@ -39,43 +39,43 @@ ROOMDOMAIN="meet.jit.si"
|
||||||
#ROOMDOMAIN="conference.jitsi.vuc.me"
|
#ROOMDOMAIN="conference.jitsi.vuc.me"
|
||||||
|
|
||||||
class TrivialMatrixClient:
|
class TrivialMatrixClient:
|
||||||
def __init__(self, access_token):
|
def __init__(self, access_token):
|
||||||
self.token = None
|
self.token = None
|
||||||
self.access_token = access_token
|
self.access_token = access_token
|
||||||
|
|
||||||
def getEvent(self):
|
def getEvent(self):
|
||||||
while True:
|
while True:
|
||||||
url = MATRIXBASE+'events?access_token='+self.access_token+"&timeout=60000"
|
url = MATRIXBASE+'events?access_token='+self.access_token+"&timeout=60000"
|
||||||
if self.token:
|
if self.token:
|
||||||
url += "&from="+self.token
|
url += "&from="+self.token
|
||||||
req = grequests.get(url)
|
req = grequests.get(url)
|
||||||
resps = grequests.map([req])
|
resps = grequests.map([req])
|
||||||
obj = json.loads(resps[0].content)
|
obj = json.loads(resps[0].content)
|
||||||
print "incoming from matrix",obj
|
print "incoming from matrix",obj
|
||||||
if 'end' not in obj:
|
if 'end' not in obj:
|
||||||
continue
|
continue
|
||||||
self.token = obj['end']
|
self.token = obj['end']
|
||||||
if len(obj['chunk']):
|
if len(obj['chunk']):
|
||||||
return obj['chunk'][0]
|
return obj['chunk'][0]
|
||||||
|
|
||||||
def joinRoom(self, roomId):
|
def joinRoom(self, roomId):
|
||||||
url = MATRIXBASE+'rooms/'+roomId+'/join?access_token='+self.access_token
|
url = MATRIXBASE+'rooms/'+roomId+'/join?access_token='+self.access_token
|
||||||
print url
|
print url
|
||||||
headers={ 'Content-Type': 'application/json' }
|
headers={ 'Content-Type': 'application/json' }
|
||||||
req = grequests.post(url, headers=headers, data='{}')
|
req = grequests.post(url, headers=headers, data='{}')
|
||||||
resps = grequests.map([req])
|
resps = grequests.map([req])
|
||||||
obj = json.loads(resps[0].content)
|
obj = json.loads(resps[0].content)
|
||||||
print "response: ",obj
|
print "response: ",obj
|
||||||
|
|
||||||
def sendEvent(self, roomId, evType, event):
|
def sendEvent(self, roomId, evType, event):
|
||||||
url = MATRIXBASE+'rooms/'+roomId+'/send/'+evType+'?access_token='+self.access_token
|
url = MATRIXBASE+'rooms/'+roomId+'/send/'+evType+'?access_token='+self.access_token
|
||||||
print url
|
print url
|
||||||
print json.dumps(event)
|
print json.dumps(event)
|
||||||
headers={ 'Content-Type': 'application/json' }
|
headers={ 'Content-Type': 'application/json' }
|
||||||
req = grequests.post(url, headers=headers, data=json.dumps(event))
|
req = grequests.post(url, headers=headers, data=json.dumps(event))
|
||||||
resps = grequests.map([req])
|
resps = grequests.map([req])
|
||||||
obj = json.loads(resps[0].content)
|
obj = json.loads(resps[0].content)
|
||||||
print "response: ",obj
|
print "response: ",obj
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,178 +83,178 @@ xmppClients = {}
|
||||||
|
|
||||||
|
|
||||||
def matrixLoop():
|
def matrixLoop():
|
||||||
while True:
|
while True:
|
||||||
ev = matrixCli.getEvent()
|
ev = matrixCli.getEvent()
|
||||||
print ev
|
print ev
|
||||||
if ev['type'] == 'm.room.member':
|
if ev['type'] == 'm.room.member':
|
||||||
print 'membership event'
|
print 'membership event'
|
||||||
if ev['membership'] == 'invite' and ev['state_key'] == MYUSERNAME:
|
if ev['membership'] == 'invite' and ev['state_key'] == MYUSERNAME:
|
||||||
roomId = ev['room_id']
|
roomId = ev['room_id']
|
||||||
print "joining room %s" % (roomId)
|
print "joining room %s" % (roomId)
|
||||||
matrixCli.joinRoom(roomId)
|
matrixCli.joinRoom(roomId)
|
||||||
elif ev['type'] == 'm.room.message':
|
elif ev['type'] == 'm.room.message':
|
||||||
if ev['room_id'] in xmppClients:
|
if ev['room_id'] in xmppClients:
|
||||||
print "already have a bridge for that user, ignoring"
|
print "already have a bridge for that user, ignoring"
|
||||||
continue
|
continue
|
||||||
print "got message, connecting"
|
print "got message, connecting"
|
||||||
xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
|
xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
|
||||||
gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
|
gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
|
||||||
elif ev['type'] == 'm.call.invite':
|
elif ev['type'] == 'm.call.invite':
|
||||||
print "Incoming call"
|
print "Incoming call"
|
||||||
#sdp = ev['content']['offer']['sdp']
|
#sdp = ev['content']['offer']['sdp']
|
||||||
#print "sdp: %s" % (sdp)
|
#print "sdp: %s" % (sdp)
|
||||||
#xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
|
#xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
|
||||||
#gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
|
#gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
|
||||||
elif ev['type'] == 'm.call.answer':
|
elif ev['type'] == 'm.call.answer':
|
||||||
print "Call answered"
|
print "Call answered"
|
||||||
sdp = ev['content']['answer']['sdp']
|
sdp = ev['content']['answer']['sdp']
|
||||||
if ev['room_id'] not in xmppClients:
|
if ev['room_id'] not in xmppClients:
|
||||||
print "We didn't have a call for that room"
|
print "We didn't have a call for that room"
|
||||||
continue
|
continue
|
||||||
# should probably check call ID too
|
# should probably check call ID too
|
||||||
xmppCli = xmppClients[ev['room_id']]
|
xmppCli = xmppClients[ev['room_id']]
|
||||||
xmppCli.sendAnswer(sdp)
|
xmppCli.sendAnswer(sdp)
|
||||||
elif ev['type'] == 'm.call.hangup':
|
elif ev['type'] == 'm.call.hangup':
|
||||||
if ev['room_id'] in xmppClients:
|
if ev['room_id'] in xmppClients:
|
||||||
xmppClients[ev['room_id']].stop()
|
xmppClients[ev['room_id']].stop()
|
||||||
del xmppClients[ev['room_id']]
|
del xmppClients[ev['room_id']]
|
||||||
|
|
||||||
class TrivialXmppClient:
|
class TrivialXmppClient:
|
||||||
def __init__(self, matrixRoom, userId):
|
def __init__(self, matrixRoom, userId):
|
||||||
self.rid = 0
|
self.rid = 0
|
||||||
self.matrixRoom = matrixRoom
|
self.matrixRoom = matrixRoom
|
||||||
self.userId = userId
|
self.userId = userId
|
||||||
self.running = True
|
self.running = True
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def nextRid(self):
|
def nextRid(self):
|
||||||
self.rid += 1
|
self.rid += 1
|
||||||
return '%d' % (self.rid)
|
return '%d' % (self.rid)
|
||||||
|
|
||||||
def sendIq(self, xml):
|
def sendIq(self, xml):
|
||||||
fullXml = "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s'>%s</body>" % (self.nextRid(), self.sid, xml)
|
fullXml = "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s'>%s</body>" % (self.nextRid(), self.sid, xml)
|
||||||
#print "\t>>>%s" % (fullXml)
|
#print "\t>>>%s" % (fullXml)
|
||||||
return self.xmppPoke(fullXml)
|
return self.xmppPoke(fullXml)
|
||||||
|
|
||||||
def xmppPoke(self, xml):
|
def xmppPoke(self, xml):
|
||||||
headers = {'Content-Type': 'application/xml'}
|
headers = {'Content-Type': 'application/xml'}
|
||||||
req = grequests.post(HTTPBIND, verify=False, headers=headers, data=xml)
|
req = grequests.post(HTTPBIND, verify=False, headers=headers, data=xml)
|
||||||
resps = grequests.map([req])
|
resps = grequests.map([req])
|
||||||
obj = BeautifulSoup(resps[0].content)
|
obj = BeautifulSoup(resps[0].content)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def sendAnswer(self, answer):
|
def sendAnswer(self, answer):
|
||||||
print "sdp from matrix client",answer
|
print "sdp from matrix client",answer
|
||||||
p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--sdp'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--sdp'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
jingle, out_err = p.communicate(answer)
|
jingle, out_err = p.communicate(answer)
|
||||||
jingle = jingle % {
|
jingle = jingle % {
|
||||||
'tojid': self.callfrom,
|
'tojid': self.callfrom,
|
||||||
'action': 'session-accept',
|
'action': 'session-accept',
|
||||||
'initiator': self.callfrom,
|
'initiator': self.callfrom,
|
||||||
'responder': self.jid,
|
'responder': self.jid,
|
||||||
'sid': self.callsid
|
'sid': self.callsid
|
||||||
}
|
}
|
||||||
print "answer jingle from sdp",jingle
|
print "answer jingle from sdp",jingle
|
||||||
res = self.sendIq(jingle)
|
res = self.sendIq(jingle)
|
||||||
print "reply from answer: ",res
|
print "reply from answer: ",res
|
||||||
|
|
||||||
self.ssrcs = {}
|
self.ssrcs = {}
|
||||||
jingleSoup = BeautifulSoup(jingle)
|
jingleSoup = BeautifulSoup(jingle)
|
||||||
for cont in jingleSoup.iq.jingle.findAll('content'):
|
for cont in jingleSoup.iq.jingle.findAll('content'):
|
||||||
if cont.description:
|
if cont.description:
|
||||||
self.ssrcs[cont['name']] = cont.description['ssrc']
|
self.ssrcs[cont['name']] = cont.description['ssrc']
|
||||||
print "my ssrcs:",self.ssrcs
|
print "my ssrcs:",self.ssrcs
|
||||||
|
|
||||||
gevent.joinall([
|
gevent.joinall([
|
||||||
gevent.spawn(self.advertiseSsrcs)
|
gevent.spawn(self.advertiseSsrcs)
|
||||||
])
|
])
|
||||||
|
|
||||||
def advertiseSsrcs(self):
|
def advertiseSsrcs(self):
|
||||||
time.sleep(7)
|
time.sleep(7)
|
||||||
print "SSRC spammer started"
|
print "SSRC spammer started"
|
||||||
while self.running:
|
while self.running:
|
||||||
ssrcMsg = "<presence to='%(tojid)s' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%(nick)s</nick><stats xmlns='http://jitsi.org/jitmeet/stats'><stat name='bitrate_download' value='175'/><stat name='bitrate_upload' value='176'/><stat name='packetLoss_total' value='0'/><stat name='packetLoss_download' value='0'/><stat name='packetLoss_upload' value='0'/></stats><media xmlns='http://estos.de/ns/mjs'><source type='audio' ssrc='%(assrc)s' direction='sendre'/><source type='video' ssrc='%(vssrc)s' direction='sendre'/></media></presence>" % { 'tojid': "%s@%s/%s" % (ROOMNAME, ROOMDOMAIN, self.shortJid), 'nick': self.userId, 'assrc': self.ssrcs['audio'], 'vssrc': self.ssrcs['video'] }
|
ssrcMsg = "<presence to='%(tojid)s' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%(nick)s</nick><stats xmlns='http://jitsi.org/jitmeet/stats'><stat name='bitrate_download' value='175'/><stat name='bitrate_upload' value='176'/><stat name='packetLoss_total' value='0'/><stat name='packetLoss_download' value='0'/><stat name='packetLoss_upload' value='0'/></stats><media xmlns='http://estos.de/ns/mjs'><source type='audio' ssrc='%(assrc)s' direction='sendre'/><source type='video' ssrc='%(vssrc)s' direction='sendre'/></media></presence>" % { 'tojid': "%s@%s/%s" % (ROOMNAME, ROOMDOMAIN, self.shortJid), 'nick': self.userId, 'assrc': self.ssrcs['audio'], 'vssrc': self.ssrcs['video'] }
|
||||||
res = self.sendIq(ssrcMsg)
|
res = self.sendIq(ssrcMsg)
|
||||||
print "reply from ssrc announce: ",res
|
print "reply from ssrc announce: ",res
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def xmppLoop(self):
|
def xmppLoop(self):
|
||||||
self.matrixCallId = time.time()
|
self.matrixCallId = time.time()
|
||||||
res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' to='%s' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), HOST))
|
res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' to='%s' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), HOST))
|
||||||
|
|
||||||
print res
|
print res
|
||||||
self.sid = res.body['sid']
|
self.sid = res.body['sid']
|
||||||
print "sid %s" % (self.sid)
|
print "sid %s" % (self.sid)
|
||||||
|
|
||||||
res = self.sendIq("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>")
|
res = self.sendIq("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>")
|
||||||
|
|
||||||
res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s' to='%s' xml:lang='en' xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), self.sid, HOST))
|
res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s' to='%s' xml:lang='en' xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), self.sid, HOST))
|
||||||
|
|
||||||
res = self.sendIq("<iq type='set' id='_bind_auth_2' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>")
|
res = self.sendIq("<iq type='set' id='_bind_auth_2' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>")
|
||||||
print res
|
print res
|
||||||
|
|
||||||
self.jid = res.body.iq.bind.jid.string
|
self.jid = res.body.iq.bind.jid.string
|
||||||
print "jid: %s" % (self.jid)
|
print "jid: %s" % (self.jid)
|
||||||
self.shortJid = self.jid.split('-')[0]
|
self.shortJid = self.jid.split('-')[0]
|
||||||
|
|
||||||
res = self.sendIq("<iq type='set' id='_session_auth_2' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>")
|
res = self.sendIq("<iq type='set' id='_session_auth_2' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>")
|
||||||
|
|
||||||
#randomthing = res.body.iq['to']
|
#randomthing = res.body.iq['to']
|
||||||
#whatsitpart = randomthing.split('-')[0]
|
#whatsitpart = randomthing.split('-')[0]
|
||||||
|
|
||||||
#print "other random bind thing: %s" % (randomthing)
|
#print "other random bind thing: %s" % (randomthing)
|
||||||
|
|
||||||
# advertise preence to the jitsi room, with our nick
|
# advertise preence to the jitsi room, with our nick
|
||||||
res = self.sendIq("<iq type='get' to='%s' xmlns='jabber:client' id='1:sendIQ'><services xmlns='urn:xmpp:extdisco:1'><service host='%s'/></services></iq><presence to='%s@%s/d98f6c40' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%s</nick></presence>" % (HOST, TURNSERVER, ROOMNAME, ROOMDOMAIN, self.userId))
|
res = self.sendIq("<iq type='get' to='%s' xmlns='jabber:client' id='1:sendIQ'><services xmlns='urn:xmpp:extdisco:1'><service host='%s'/></services></iq><presence to='%s@%s/d98f6c40' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%s</nick></presence>" % (HOST, TURNSERVER, ROOMNAME, ROOMDOMAIN, self.userId))
|
||||||
self.muc = {'users': []}
|
self.muc = {'users': []}
|
||||||
for p in res.body.findAll('presence'):
|
for p in res.body.findAll('presence'):
|
||||||
u = {}
|
u = {}
|
||||||
u['shortJid'] = p['from'].split('/')[1]
|
u['shortJid'] = p['from'].split('/')[1]
|
||||||
if p.c and p.c.nick:
|
if p.c and p.c.nick:
|
||||||
u['nick'] = p.c.nick.string
|
u['nick'] = p.c.nick.string
|
||||||
self.muc['users'].append(u)
|
self.muc['users'].append(u)
|
||||||
print "muc: ",self.muc
|
print "muc: ",self.muc
|
||||||
|
|
||||||
# wait for stuff
|
# wait for stuff
|
||||||
while True:
|
while True:
|
||||||
print "waiting..."
|
print "waiting..."
|
||||||
res = self.sendIq("")
|
res = self.sendIq("")
|
||||||
print "got from stream: ",res
|
print "got from stream: ",res
|
||||||
if res.body.iq:
|
if res.body.iq:
|
||||||
jingles = res.body.iq.findAll('jingle')
|
jingles = res.body.iq.findAll('jingle')
|
||||||
if len(jingles):
|
if len(jingles):
|
||||||
self.callfrom = res.body.iq['from']
|
self.callfrom = res.body.iq['from']
|
||||||
self.handleInvite(jingles[0])
|
self.handleInvite(jingles[0])
|
||||||
elif 'type' in res.body and res.body['type'] == 'terminate':
|
elif 'type' in res.body and res.body['type'] == 'terminate':
|
||||||
self.running = False
|
self.running = False
|
||||||
del xmppClients[self.matrixRoom]
|
del xmppClients[self.matrixRoom]
|
||||||
return
|
return
|
||||||
|
|
||||||
def handleInvite(self, jingle):
|
def handleInvite(self, jingle):
|
||||||
self.initiator = jingle['initiator']
|
self.initiator = jingle['initiator']
|
||||||
self.callsid = jingle['sid']
|
self.callsid = jingle['sid']
|
||||||
p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--jingle'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--jingle'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
print "raw jingle invite",str(jingle)
|
print "raw jingle invite",str(jingle)
|
||||||
sdp, out_err = p.communicate(str(jingle))
|
sdp, out_err = p.communicate(str(jingle))
|
||||||
print "transformed remote offer sdp",sdp
|
print "transformed remote offer sdp",sdp
|
||||||
inviteEvent = {
|
inviteEvent = {
|
||||||
'offer': {
|
'offer': {
|
||||||
'type': 'offer',
|
'type': 'offer',
|
||||||
'sdp': sdp
|
'sdp': sdp
|
||||||
},
|
},
|
||||||
'call_id': self.matrixCallId,
|
'call_id': self.matrixCallId,
|
||||||
'version': 0,
|
'version': 0,
|
||||||
'lifetime': 30000
|
'lifetime': 30000
|
||||||
}
|
}
|
||||||
matrixCli.sendEvent(self.matrixRoom, 'm.call.invite', inviteEvent)
|
matrixCli.sendEvent(self.matrixRoom, 'm.call.invite', inviteEvent)
|
||||||
|
|
||||||
matrixCli = TrivialMatrixClient(ACCESS_TOKEN)
|
matrixCli = TrivialMatrixClient(ACCESS_TOKEN)
|
||||||
|
|
||||||
gevent.joinall([
|
gevent.joinall([
|
||||||
gevent.spawn(matrixLoop)
|
gevent.spawn(matrixLoop)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,8 @@ for port in 8080 8081 8082; do
|
||||||
-D --pid-file "$DIR/$port.pid" \
|
-D --pid-file "$DIR/$port.pid" \
|
||||||
--manhole $((port + 1000)) \
|
--manhole $((port + 1000)) \
|
||||||
--tls-dh-params-path "demo/demo.tls.dh" \
|
--tls-dh-params-path "demo/demo.tls.dh" \
|
||||||
$PARAMS $SYNAPSE_PARAMS
|
--media-store-path "demo/media_store.$port" \
|
||||||
|
$PARAMS $SYNAPSE_PARAMS \
|
||||||
|
|
||||||
python -m synapse.app.homeserver \
|
python -m synapse.app.homeserver \
|
||||||
--config-path "demo/etc/$port.config" \
|
--config-path "demo/etc/$port.config" \
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room alias (optional)"></input>
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="text" id="roomId" placeholder="Room ID"></input>
|
|
||||||
<input type="text" id="messageBody" placeholder="Message body"></input>
|
|
||||||
<input type="button" class="sendMessage" value="Send Message"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Room Alias</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
addRoom(rooms[i]);
|
|
||||||
}
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var data = {};
|
|
||||||
if (roomAlias.length > 0) {
|
|
||||||
data.room_alias_name = roomAlias;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
data.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
data.latest_message = "";
|
|
||||||
addRoom(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var addRoom = function(data) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+data.room_id+"</td>" +
|
|
||||||
"<td>"+data.membership+"</td>" +
|
|
||||||
"<td>"+data.room_alias+"</td>" +
|
|
||||||
"<td>"+data.latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#rooms").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
var roomId = $("#roomId").val();
|
|
||||||
var body = $("#messageBody").val();
|
|
||||||
var msgId = $.now();
|
|
||||||
|
|
||||||
if (roomId.length === 0 || body.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#messageBody").val("");
|
|
||||||
// wipe the table and reload it. Using the event stream would be the best
|
|
||||||
// solution but that is out of scope of this fiddle.
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This event stream demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="button" class="sendMessage" value="Send random message"></input>
|
|
||||||
</form>
|
|
||||||
<p id="streamErrorText"></p>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var eventStreamInfo = {
|
|
||||||
from: "END"
|
|
||||||
};
|
|
||||||
|
|
||||||
var roomInfo = [];
|
|
||||||
|
|
||||||
var longpollEventStream = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
|
||||||
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
eventStreamInfo.from = data.end;
|
|
||||||
|
|
||||||
var hasNewLatestMessage = false;
|
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
|
||||||
if (data.chunk[i].type === "m.room.message") {
|
|
||||||
for (var j=0; j<roomInfo.length; ++j) {
|
|
||||||
if (roomInfo[j].room_id === data.chunk[i].room_id) {
|
|
||||||
roomInfo[j].latest_message = data.chunk[i].content.body;
|
|
||||||
hasNewLatestMessage = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewLatestMessage) {
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}
|
|
||||||
$("#streamErrorText").text("");
|
|
||||||
longpollEventStream();
|
|
||||||
}).fail(function(err) {
|
|
||||||
$("#streamErrorText").text("Event stream error: "+JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
setTimeout(longpollEventStream, 5000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
longpollEventStream();
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
$("#roomId").val("");
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
if ("messages" in rooms[i]) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roomInfo = rooms;
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
if (roomInfo.length === 0) {
|
|
||||||
alert("There is no room to send a message to!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = Math.floor(Math.random() * roomInfo.length);
|
|
||||||
|
|
||||||
sendMessage(roomInfo[index].room_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
var sendMessage = function(roomId) {
|
|
||||||
var body = "jsfiddle message @" + $.now();
|
|
||||||
|
|
||||||
if (roomId.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#messageBody").val("");
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var setRooms = function(roomList) {
|
|
||||||
// wipe existing entries
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var rows = "";
|
|
||||||
for (var i=0; i<roomList.length; ++i) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+roomList[i].room_id+"</td>" +
|
|
||||||
"<td>"+roomList[i].latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
rows += row;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#rooms").append(rows);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
.roomListDashboard, .roomContents, .sendMessageForm {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomList {
|
|
||||||
background-color: #909090;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageWrapper {
|
|
||||||
background-color: #EEEEEE;
|
|
||||||
height: 400px;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.membersWrapper {
|
|
||||||
background-color: #EEEEEE;
|
|
||||||
height: 200px;
|
|
||||||
width: 50%;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textEntry {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomList tr:not(:first-child):hover {
|
|
||||||
background-color: orange;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
name: Example Matrix Client
|
|
||||||
description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
|
|
||||||
authors:
|
|
||||||
- matrix.org
|
|
||||||
resources:
|
|
||||||
- http://matrix.org
|
|
||||||
normalize_css: no
|
|
|
@ -1,56 +0,0 @@
|
||||||
<div class="signUp">
|
|
||||||
<p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
|
|
||||||
<form class="registrationForm">
|
|
||||||
<p>No account? Register:</p>
|
|
||||||
<input type="text" id="userReg" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordReg" placeholder="Password"></input>
|
|
||||||
<input type="button" class="register" value="Register"></input>
|
|
||||||
</form>
|
|
||||||
<form class="loginForm">
|
|
||||||
<p>Got an account? Login:</p>
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="roomListDashboard">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room alias"></input>
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms" class="roomList">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="roomContents">
|
|
||||||
<p id="roomName">Select a room</p>
|
|
||||||
<div class="messageWrapper">
|
|
||||||
<table id="messages">
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="text" class="textEntry" id="body" placeholder="Enter text here..." onkeydown="javascript:if (event.keyCode == 13) document.getElementById('sendMsg').focus()"></input>
|
|
||||||
<input type="button" class="sendMessage" id="sendMsg" value="Send"></input>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>Member list:</p>
|
|
||||||
<div class="membersWrapper">
|
|
||||||
<table id="members">
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var eventStreamInfo = {
|
|
||||||
from: "END"
|
|
||||||
};
|
|
||||||
|
|
||||||
var roomInfo = [];
|
|
||||||
var memberInfo = [];
|
|
||||||
var viewingRoomId;
|
|
||||||
|
|
||||||
// ************** Event Streaming **************
|
|
||||||
var longpollEventStream = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
|
||||||
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
eventStreamInfo.from = data.end;
|
|
||||||
|
|
||||||
var hasNewLatestMessage = false;
|
|
||||||
var updatedMemberList = false;
|
|
||||||
var i=0;
|
|
||||||
var j=0;
|
|
||||||
for (i=0; i<data.chunk.length; ++i) {
|
|
||||||
if (data.chunk[i].type === "m.room.message") {
|
|
||||||
console.log("Got new message: " + JSON.stringify(data.chunk[i]));
|
|
||||||
if (viewingRoomId === data.chunk[i].room_id) {
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j=0; j<roomInfo.length; ++j) {
|
|
||||||
if (roomInfo[j].room_id === data.chunk[i].room_id) {
|
|
||||||
roomInfo[j].latest_message = data.chunk[i].content.body;
|
|
||||||
hasNewLatestMessage = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (data.chunk[i].type === "m.room.member") {
|
|
||||||
if (viewingRoomId === data.chunk[i].room_id) {
|
|
||||||
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
for (j=0; j<memberInfo.length; ++j) {
|
|
||||||
if (memberInfo[j].state_key === data.chunk[i].state_key) {
|
|
||||||
memberInfo[j] = data.chunk[i];
|
|
||||||
updatedMemberList = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updatedMemberList) {
|
|
||||||
memberInfo.push(data.chunk[i]);
|
|
||||||
updatedMemberList = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.chunk[i].state_key === accountInfo.user_id) {
|
|
||||||
getCurrentRoomList(); // update our join/invite list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Discarding: " + JSON.stringify(data.chunk[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewLatestMessage) {
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}
|
|
||||||
if (updatedMemberList) {
|
|
||||||
$("#members").empty();
|
|
||||||
for (i=0; i<memberInfo.length; ++i) {
|
|
||||||
addMember(memberInfo[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
longpollEventStream();
|
|
||||||
}).fail(function(err) {
|
|
||||||
setTimeout(longpollEventStream, 5000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Registration and Login **************
|
|
||||||
var onLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
longpollEventStream();
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".roomListDashboard").css({visibility: "visible"});
|
|
||||||
$(".roomContents").css({visibility: "visible"});
|
|
||||||
$(".signUp").css({display: "none"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
onLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert("Unable to login: is the home server running?");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.register').live('click', function() {
|
|
||||||
var user = $("#userReg").val();
|
|
||||||
var password = $("#passwordReg").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
onLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var msg = "Is the home server running?";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson !== null) {
|
|
||||||
msg = errJson.error;
|
|
||||||
}
|
|
||||||
alert("Unable to register: "+msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ************** Creating a room ******************
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var data = {};
|
|
||||||
if (roomAlias.length > 0) {
|
|
||||||
data.room_alias_name = roomAlias;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(response) {
|
|
||||||
$("#roomAlias").val("");
|
|
||||||
response.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
response.latest_message = "";
|
|
||||||
|
|
||||||
roomInfo.push(response);
|
|
||||||
setRooms(roomInfo);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ************** Getting current state **************
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
if ("messages" in rooms[i]) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roomInfo = rooms;
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var loadRoomContent = function(roomId) {
|
|
||||||
console.log("loadRoomContent " + roomId);
|
|
||||||
viewingRoomId = roomId;
|
|
||||||
$("#roomName").text("Room: "+roomId);
|
|
||||||
$(".sendMessageForm").css({visibility: "visible"});
|
|
||||||
getMessages(roomId);
|
|
||||||
getMemberList(roomId);
|
|
||||||
};
|
|
||||||
|
|
||||||
var getMessages = function(roomId) {
|
|
||||||
$("#messages").empty();
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
|
||||||
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
for (var i=data.chunk.length-1; i>=0; --i) {
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var getMemberList = function(roomId) {
|
|
||||||
$("#members").empty();
|
|
||||||
memberInfo = [];
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
|
||||||
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
|
||||||
memberInfo.push(data.chunk[i]);
|
|
||||||
addMember(data.chunk[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Sending messages **************
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
if (viewingRoomId === undefined) {
|
|
||||||
alert("There is no room to send a message to!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var body = $("#body").val();
|
|
||||||
sendMessage(viewingRoomId, body);
|
|
||||||
});
|
|
||||||
|
|
||||||
var sendMessage = function(roomId, body) {
|
|
||||||
var msgId = $.now();
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#body").val("");
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Navigation and DOM manipulation **************
|
|
||||||
var setRooms = function(roomList) {
|
|
||||||
// wipe existing entries
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var rows = "";
|
|
||||||
for (var i=0; i<roomList.length; ++i) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+roomList[i].room_id+"</td>" +
|
|
||||||
"<td>"+roomList[i].membership+"</td>" +
|
|
||||||
"<td>"+roomList[i].latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
rows += row;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#rooms").append(rows);
|
|
||||||
|
|
||||||
$('#rooms').find("tr").click(function(){
|
|
||||||
var roomId = $(this).find('td:eq(0)').text();
|
|
||||||
var membership = $(this).find('td:eq(1)').text();
|
|
||||||
if (membership !== "join") {
|
|
||||||
console.log("Joining room " + roomId);
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({membership: "join"}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
loadRoomContent(roomId);
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
loadRoomContent(roomId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var addMessage = function(data) {
|
|
||||||
|
|
||||||
var msg = data.content.body;
|
|
||||||
if (data.type === "m.room.member") {
|
|
||||||
if (data.content.membership === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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 if (data.content.membership === "ban") {
|
|
||||||
msg = "<em>was banned from the room</em>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var row = "<tr>" +
|
|
||||||
"<td>"+data.user_id+"</td>" +
|
|
||||||
"<td>"+msg+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#messages").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addMember = function(data) {
|
|
||||||
var row = "<tr>" +
|
|
||||||
"<td>"+data.state_key+"</td>" +
|
|
||||||
"<td>"+data.content.membership+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#members").append(row);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="registrationForm">
|
|
||||||
<input type="text" id="user" placeholder="Username"></input>
|
|
||||||
<input type="password" id="password" placeholder="Password"></input>
|
|
||||||
<input type="button" class="register" value="Register"></input>
|
|
||||||
</form>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<p id="welcomeText"></p>
|
|
||||||
<input type="button" class="testToken" value="Test token"></input>
|
|
||||||
<input type="button" class="logout" value="Logout"></input>
|
|
||||||
<p id="imSyncText"></p>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
$("#welcomeText").text("Welcome " + accountInfo.user_id+". Your access token is: " +
|
|
||||||
accountInfo.access_token);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.register').live('click', function() {
|
|
||||||
var user = $("#user").val();
|
|
||||||
var password = $("#password").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var login = function(user, password) {
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
|
|
||||||
if (data.flows[0].type !== "m.login.password") {
|
|
||||||
alert("I don't know how to login with this type: " + data.type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
login(user, password);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.logout').live('click', function() {
|
|
||||||
accountInfo = {};
|
|
||||||
$("#imSyncText").text("");
|
|
||||||
$(".loggedin").css({visibility: "hidden"});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.testToken').live('click', function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
|
||||||
}).fail(function(err) {
|
|
||||||
$("#imSyncText").text(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This room membership demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<form class="changeMembershipForm">
|
|
||||||
<input type="text" id="roomId" placeholder="Room ID"></input>
|
|
||||||
<input type="text" id="targetUser" placeholder="Target User ID"></input>
|
|
||||||
<select id="membership">
|
|
||||||
<option value="invite">invite</option>
|
|
||||||
<option value="join">join</option>
|
|
||||||
<option value="leave">leave</option>
|
|
||||||
</select>
|
|
||||||
<input type="button" class="changeMembership" value="Change Membership"></input>
|
|
||||||
</form>
|
|
||||||
<form class="joinAliasForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room Alias (#name:domain)"></input>
|
|
||||||
<input type="button" class="joinAlias" value="Join via Alias"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Room Alias</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
$("#membership").change(function() {
|
|
||||||
if ($("#membership").val() === "invite") {
|
|
||||||
$("#targetUser").css({visibility: "visible"});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$("#targetUser").css({visibility: "hidden"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
$("#roomId").val("");
|
|
||||||
// wipe the table and reload it. Using the event stream would be the best
|
|
||||||
// solution but that is out of scope of this fiddle.
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
addRoom(rooms[i]);
|
|
||||||
}
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var data = {};
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
data.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
data.latest_message = "";
|
|
||||||
addRoom(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var addRoom = function(data) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+data.room_id+"</td>" +
|
|
||||||
"<td>"+data.membership+"</td>" +
|
|
||||||
"<td>"+data.room_alias+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#rooms").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.changeMembership').live('click', function() {
|
|
||||||
var roomId = $("#roomId").val();
|
|
||||||
var member = $("#targetUser").val();
|
|
||||||
var membership = $("#membership").val();
|
|
||||||
|
|
||||||
if (roomId.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
url = url.replace("$membership", membership);
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
if (membership === "invite") {
|
|
||||||
data = {
|
|
||||||
user_id: member
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
$.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() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -8,3 +8,11 @@ test = trial
|
||||||
|
|
||||||
[trial]
|
[trial]
|
||||||
test_suite = tests
|
test_suite = tests
|
||||||
|
|
||||||
|
[check-manifest]
|
||||||
|
ignore =
|
||||||
|
contrib
|
||||||
|
contrib/*
|
||||||
|
docs/*
|
||||||
|
pylint.cfg
|
||||||
|
tox.ini
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -33,7 +33,7 @@ setup(
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"syutil==0.0.2",
|
"syutil==0.0.2",
|
||||||
"matrix_angular_sdk==0.6.0",
|
"matrix_angular_sdk==0.6.0",
|
||||||
"Twisted>=14.0.0",
|
"Twisted==14.0.2",
|
||||||
"service_identity>=1.0.0",
|
"service_identity>=1.0.0",
|
||||||
"pyopenssl>=0.14",
|
"pyopenssl>=0.14",
|
||||||
"pyyaml",
|
"pyyaml",
|
||||||
|
|
|
@ -21,6 +21,7 @@ from synapse.api.constants import EventTypes, Membership, JoinRules
|
||||||
from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
|
from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
|
from synapse.types import UserID, ClientInfo
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ class Auth(object):
|
||||||
for event in curr_state:
|
for event in curr_state:
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
try:
|
try:
|
||||||
if self.hs.parse_userid(event.state_key).domain != host:
|
if UserID.from_string(event.state_key).domain != host:
|
||||||
continue
|
continue
|
||||||
except:
|
except:
|
||||||
logger.warn("state_key not user_id: %s", event.state_key)
|
logger.warn("state_key not user_id: %s", event.state_key)
|
||||||
|
@ -289,7 +290,9 @@ class Auth(object):
|
||||||
Args:
|
Args:
|
||||||
request - An HTTP request with an access_token query parameter.
|
request - An HTTP request with an access_token query parameter.
|
||||||
Returns:
|
Returns:
|
||||||
UserID : User ID object of the user making the request
|
tuple : of UserID and device string:
|
||||||
|
User ID object of the user making the request
|
||||||
|
Client ID object of the client instance the user is using
|
||||||
Raises:
|
Raises:
|
||||||
AuthError if no user by that token exists or the token is invalid.
|
AuthError if no user by that token exists or the token is invalid.
|
||||||
"""
|
"""
|
||||||
|
@ -298,6 +301,8 @@ class Auth(object):
|
||||||
access_token = request.args["access_token"][0]
|
access_token = request.args["access_token"][0]
|
||||||
user_info = yield self.get_user_by_token(access_token)
|
user_info = yield self.get_user_by_token(access_token)
|
||||||
user = user_info["user"]
|
user = user_info["user"]
|
||||||
|
device_id = user_info["device_id"]
|
||||||
|
token_id = user_info["token_id"]
|
||||||
|
|
||||||
ip_addr = self.hs.get_ip_from_request(request)
|
ip_addr = self.hs.get_ip_from_request(request)
|
||||||
user_agent = request.requestHeaders.getRawHeaders(
|
user_agent = request.requestHeaders.getRawHeaders(
|
||||||
|
@ -313,7 +318,7 @@ class Auth(object):
|
||||||
user_agent=user_agent
|
user_agent=user_agent
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue(user)
|
defer.returnValue((user, ClientInfo(device_id, token_id)))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AuthError(403, "Missing access token.")
|
raise AuthError(403, "Missing access token.")
|
||||||
|
|
||||||
|
@ -337,7 +342,8 @@ class Auth(object):
|
||||||
user_info = {
|
user_info = {
|
||||||
"admin": bool(ret.get("admin", False)),
|
"admin": bool(ret.get("admin", False)),
|
||||||
"device_id": ret.get("device_id"),
|
"device_id": ret.get("device_id"),
|
||||||
"user": self.hs.parse_userid(ret.get("name")),
|
"user": UserID.from_string(ret.get("name")),
|
||||||
|
"token_id": ret.get("token_id", None),
|
||||||
}
|
}
|
||||||
|
|
||||||
defer.returnValue(user_info)
|
defer.returnValue(user_info)
|
||||||
|
@ -461,7 +467,7 @@ class Auth(object):
|
||||||
"You are not allowed to set others state"
|
"You are not allowed to set others state"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sender_domain = self.hs.parse_userid(
|
sender_domain = UserID.from_string(
|
||||||
event.user_id
|
event.user_id
|
||||||
).domain
|
).domain
|
||||||
|
|
||||||
|
@ -496,7 +502,7 @@ class Auth(object):
|
||||||
# Validate users
|
# Validate users
|
||||||
for k, v in user_list.items():
|
for k, v in user_list.items():
|
||||||
try:
|
try:
|
||||||
self.hs.parse_userid(k)
|
UserID.from_string(k)
|
||||||
except:
|
except:
|
||||||
raise SynapseError(400, "Not a valid user_id: %s" % (k,))
|
raise SynapseError(400, "Not a valid user_id: %s" % (k,))
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Codes(object):
|
class Codes(object):
|
||||||
|
UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||||
UNAUTHORIZED = "M_UNAUTHORIZED"
|
UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||||
FORBIDDEN = "M_FORBIDDEN"
|
FORBIDDEN = "M_FORBIDDEN"
|
||||||
BAD_JSON = "M_BAD_JSON"
|
BAD_JSON = "M_BAD_JSON"
|
||||||
|
@ -34,6 +35,7 @@ class Codes(object):
|
||||||
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||||
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||||
|
MISSING_PARAM = "M_MISSING_PARAM",
|
||||||
TOO_LARGE = "M_TOO_LARGE"
|
TOO_LARGE = "M_TOO_LARGE"
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +83,35 @@ class RegistrationError(SynapseError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnrecognizedRequestError(SynapseError):
|
||||||
|
"""An error indicating we don't understand the request you're trying to make"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if "errcode" not in kwargs:
|
||||||
|
kwargs["errcode"] = Codes.UNRECOGNIZED
|
||||||
|
message = None
|
||||||
|
if len(args) == 0:
|
||||||
|
message = "Unrecognized request"
|
||||||
|
else:
|
||||||
|
message = args[0]
|
||||||
|
super(UnrecognizedRequestError, self).__init__(
|
||||||
|
400,
|
||||||
|
message,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundError(SynapseError):
|
||||||
|
"""An error indicating we can't find the thing you asked for"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if "errcode" not in kwargs:
|
||||||
|
kwargs["errcode"] = Codes.NOT_FOUND
|
||||||
|
super(NotFoundError, self).__init__(
|
||||||
|
404,
|
||||||
|
"Not found",
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthError(SynapseError):
|
class AuthError(SynapseError):
|
||||||
"""An error raised when there was a problem authorising an event."""
|
"""An error raised when there was a problem authorising an event."""
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,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"
|
||||||
|
CLIENT_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha"
|
||||||
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"
|
||||||
|
|
|
@ -26,17 +26,19 @@ from twisted.web.resource import Resource
|
||||||
from twisted.web.static import File
|
from twisted.web.static import File
|
||||||
from twisted.web.server import Site
|
from twisted.web.server import Site
|
||||||
from synapse.http.server import JsonResource, RootRedirect
|
from synapse.http.server import JsonResource, RootRedirect
|
||||||
from synapse.media.v0.content_repository import ContentRepoResource
|
from synapse.rest.media.v0.content_repository import ContentRepoResource
|
||||||
from synapse.media.v1.media_repository import MediaRepositoryResource
|
from synapse.rest.media.v1.media_repository import MediaRepositoryResource
|
||||||
from synapse.http.server_key_resource import LocalKey
|
from synapse.http.server_key_resource import LocalKey
|
||||||
from synapse.http.matrixfederationclient import MatrixFederationHttpClient
|
from synapse.http.matrixfederationclient import MatrixFederationHttpClient
|
||||||
from synapse.api.urls import (
|
from synapse.api.urls import (
|
||||||
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
|
||||||
SERVER_KEY_PREFIX, MEDIA_PREFIX
|
SERVER_KEY_PREFIX, MEDIA_PREFIX, CLIENT_V2_ALPHA_PREFIX,
|
||||||
)
|
)
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.util.logcontext import LoggingContext
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
from synapse.rest.client.v1 import ClientV1RestResource
|
||||||
|
from synapse.rest.client.v2_alpha import ClientV2AlphaRestResource
|
||||||
|
|
||||||
from daemonize import Daemonize
|
from daemonize import Daemonize
|
||||||
import twisted.manhole.telnet
|
import twisted.manhole.telnet
|
||||||
|
@ -59,7 +61,10 @@ class SynapseHomeServer(HomeServer):
|
||||||
return MatrixFederationHttpClient(self)
|
return MatrixFederationHttpClient(self)
|
||||||
|
|
||||||
def build_resource_for_client(self):
|
def build_resource_for_client(self):
|
||||||
return JsonResource()
|
return ClientV1RestResource(self)
|
||||||
|
|
||||||
|
def build_resource_for_client_v2_alpha(self):
|
||||||
|
return ClientV2AlphaRestResource(self)
|
||||||
|
|
||||||
def build_resource_for_federation(self):
|
def build_resource_for_federation(self):
|
||||||
return JsonResource()
|
return JsonResource()
|
||||||
|
@ -104,6 +109,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
# [ ("/aaa/bbb/cc", Resource1), ("/aaa/dummy", Resource2) ]
|
# [ ("/aaa/bbb/cc", Resource1), ("/aaa/dummy", Resource2) ]
|
||||||
desired_tree = [
|
desired_tree = [
|
||||||
(CLIENT_PREFIX, self.get_resource_for_client()),
|
(CLIENT_PREFIX, self.get_resource_for_client()),
|
||||||
|
(CLIENT_V2_ALPHA_PREFIX, self.get_resource_for_client_v2_alpha()),
|
||||||
(FEDERATION_PREFIX, self.get_resource_for_federation()),
|
(FEDERATION_PREFIX, self.get_resource_for_federation()),
|
||||||
(CONTENT_REPO_PREFIX, self.get_resource_for_content_repo()),
|
(CONTENT_REPO_PREFIX, self.get_resource_for_content_repo()),
|
||||||
(SERVER_KEY_PREFIX, self.get_resource_for_server_key()),
|
(SERVER_KEY_PREFIX, self.get_resource_for_server_key()),
|
||||||
|
@ -224,8 +230,6 @@ def setup():
|
||||||
content_addr=config.content_addr,
|
content_addr=config.content_addr,
|
||||||
)
|
)
|
||||||
|
|
||||||
hs.register_servlets()
|
|
||||||
|
|
||||||
hs.create_resource_tree(
|
hs.create_resource_tree(
|
||||||
web_client=config.webclient,
|
web_client=config.webclient,
|
||||||
redirect_root_to_web_client=True,
|
redirect_root_to_web_client=True,
|
||||||
|
@ -268,6 +272,8 @@ def setup():
|
||||||
bind_port = None
|
bind_port = None
|
||||||
hs.start_listening(bind_port, config.unsecure_port)
|
hs.start_listening(bind_port, config.unsecure_port)
|
||||||
|
|
||||||
|
hs.get_pusherpool().start()
|
||||||
|
|
||||||
if config.daemonize:
|
if config.daemonize:
|
||||||
print config.pid_file
|
print config.pid_file
|
||||||
daemon = Daemonize(
|
daemon = Daemonize(
|
||||||
|
|
|
@ -89,31 +89,31 @@ def prune_event(event):
|
||||||
return type(event)(allowed_fields)
|
return type(event)(allowed_fields)
|
||||||
|
|
||||||
|
|
||||||
def serialize_event(hs, e, client_event=True):
|
def serialize_event(e, time_now_ms, client_event=True):
|
||||||
# FIXME(erikj): To handle the case of presence events and the like
|
# FIXME(erikj): To handle the case of presence events and the like
|
||||||
if not isinstance(e, EventBase):
|
if not isinstance(e, EventBase):
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
time_now_ms = int(time_now_ms)
|
||||||
|
|
||||||
# Should this strip out None's?
|
# Should this strip out None's?
|
||||||
d = {k: v for k, v in e.get_dict().items()}
|
d = {k: v for k, v in e.get_dict().items()}
|
||||||
|
|
||||||
if not client_event:
|
if not client_event:
|
||||||
# set the age and keep all other keys
|
# set the age and keep all other keys
|
||||||
if "age_ts" in d["unsigned"]:
|
if "age_ts" in d["unsigned"]:
|
||||||
now = int(hs.get_clock().time_msec())
|
d["unsigned"]["age"] = time_now_ms - d["unsigned"]["age_ts"]
|
||||||
d["unsigned"]["age"] = now - d["unsigned"]["age_ts"]
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
if "age_ts" in d["unsigned"]:
|
if "age_ts" in d["unsigned"]:
|
||||||
now = int(hs.get_clock().time_msec())
|
d["age"] = time_now_ms - d["unsigned"]["age_ts"]
|
||||||
d["age"] = now - d["unsigned"]["age_ts"]
|
|
||||||
del d["unsigned"]["age_ts"]
|
del d["unsigned"]["age_ts"]
|
||||||
|
|
||||||
d["user_id"] = d.pop("sender", None)
|
d["user_id"] = d.pop("sender", None)
|
||||||
|
|
||||||
if "redacted_because" in e.unsigned:
|
if "redacted_because" in e.unsigned:
|
||||||
d["redacted_because"] = serialize_event(
|
d["redacted_because"] = serialize_event(
|
||||||
hs, e.unsigned["redacted_because"]
|
e.unsigned["redacted_because"], time_now_ms
|
||||||
)
|
)
|
||||||
|
|
||||||
del d["unsigned"]["redacted_because"]
|
del d["unsigned"]["redacted_because"]
|
||||||
|
|
|
@ -19,6 +19,7 @@ from synapse.api.errors import LimitExceededError, SynapseError
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
from synapse.crypto.event_signing import add_hashes_and_signatures
|
from synapse.crypto.event_signing import add_hashes_and_signatures
|
||||||
from synapse.api.constants import Membership, EventTypes
|
from synapse.api.constants import Membership, EventTypes
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -113,7 +114,7 @@ class BaseHandler(object):
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
if event.content["membership"] == Membership.INVITE:
|
if event.content["membership"] == Membership.INVITE:
|
||||||
invitee = self.hs.parse_userid(event.state_key)
|
invitee = UserID.from_string(event.state_key)
|
||||||
if not self.hs.is_mine(invitee):
|
if not self.hs.is_mine(invitee):
|
||||||
# TODO: Can we add signature from remote server in a nicer
|
# TODO: Can we add signature from remote server in a nicer
|
||||||
# way? If we have been invited by a remote server, we need
|
# way? If we have been invited by a remote server, we need
|
||||||
|
@ -134,7 +135,7 @@ class BaseHandler(object):
|
||||||
if k[0] == EventTypes.Member:
|
if k[0] == EventTypes.Member:
|
||||||
if s.content["membership"] == Membership.JOIN:
|
if s.content["membership"] == Membership.JOIN:
|
||||||
destinations.add(
|
destinations.add(
|
||||||
self.hs.parse_userid(s.state_key).domain
|
UserID.from_string(s.state_key).domain
|
||||||
)
|
)
|
||||||
except SynapseError:
|
except SynapseError:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|
|
@ -19,6 +19,7 @@ from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError, Codes, CodeMessageException
|
from synapse.api.errors import SynapseError, Codes, CodeMessageException
|
||||||
from synapse.api.constants import EventTypes
|
from synapse.api.constants import EventTypes
|
||||||
|
from synapse.types import RoomAlias
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ class DirectoryHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_directory_query(self, args):
|
def on_directory_query(self, args):
|
||||||
room_alias = self.hs.parse_roomalias(args["room_alias"])
|
room_alias = RoomAlias.from_string(args["room_alias"])
|
||||||
if not self.hs.is_mine(room_alias):
|
if not self.hs.is_mine(room_alias):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "Room Alias is not hosted on this Home Server"
|
400, "Room Alias is not hosted on this Home Server"
|
||||||
|
|
|
@ -17,6 +17,8 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.util.logcontext import PreserveLoggingContext
|
from synapse.util.logcontext import PreserveLoggingContext
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
|
from synapse.types import UserID
|
||||||
|
from synapse.events.utils import serialize_event
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
@ -47,24 +49,25 @@ class EventStreamHandler(BaseHandler):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def get_stream(self, auth_user_id, pagin_config, timeout=0,
|
def get_stream(self, auth_user_id, pagin_config, timeout=0,
|
||||||
as_client_event=True):
|
as_client_event=True, affect_presence=True):
|
||||||
auth_user = self.hs.parse_userid(auth_user_id)
|
auth_user = UserID.from_string(auth_user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if auth_user not in self._streams_per_user:
|
if affect_presence:
|
||||||
self._streams_per_user[auth_user] = 0
|
if auth_user not in self._streams_per_user:
|
||||||
if auth_user in self._stop_timer_per_user:
|
self._streams_per_user[auth_user] = 0
|
||||||
try:
|
if auth_user in self._stop_timer_per_user:
|
||||||
self.clock.cancel_call_later(
|
try:
|
||||||
self._stop_timer_per_user.pop(auth_user)
|
self.clock.cancel_call_later(
|
||||||
|
self._stop_timer_per_user.pop(auth_user)
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
logger.exception("Failed to cancel event timer")
|
||||||
|
else:
|
||||||
|
yield self.distributor.fire(
|
||||||
|
"started_user_eventstream", auth_user
|
||||||
)
|
)
|
||||||
except:
|
self._streams_per_user[auth_user] += 1
|
||||||
logger.exception("Failed to cancel event timer")
|
|
||||||
else:
|
|
||||||
yield self.distributor.fire(
|
|
||||||
"started_user_eventstream", auth_user
|
|
||||||
)
|
|
||||||
self._streams_per_user[auth_user] += 1
|
|
||||||
|
|
||||||
if pagin_config.from_token is None:
|
if pagin_config.from_token is None:
|
||||||
pagin_config.from_token = None
|
pagin_config.from_token = None
|
||||||
|
@ -77,8 +80,10 @@ class EventStreamHandler(BaseHandler):
|
||||||
auth_user, room_ids, pagin_config, timeout
|
auth_user, room_ids, pagin_config, timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
chunks = [
|
chunks = [
|
||||||
self.hs.serialize_event(e, as_client_event) for e in events
|
serialize_event(e, time_now, as_client_event) for e in events
|
||||||
]
|
]
|
||||||
|
|
||||||
chunk = {
|
chunk = {
|
||||||
|
@ -90,28 +95,29 @@ class EventStreamHandler(BaseHandler):
|
||||||
defer.returnValue(chunk)
|
defer.returnValue(chunk)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._streams_per_user[auth_user] -= 1
|
if affect_presence:
|
||||||
if not self._streams_per_user[auth_user]:
|
self._streams_per_user[auth_user] -= 1
|
||||||
del self._streams_per_user[auth_user]
|
if not self._streams_per_user[auth_user]:
|
||||||
|
del self._streams_per_user[auth_user]
|
||||||
|
|
||||||
# 10 seconds of grace to allow the client to reconnect again
|
# 10 seconds of grace to allow the client to reconnect again
|
||||||
# before we think they're gone
|
# before we think they're gone
|
||||||
def _later():
|
def _later():
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"_later stopped_user_eventstream %s", auth_user
|
"_later stopped_user_eventstream %s", auth_user
|
||||||
|
)
|
||||||
|
|
||||||
|
self._stop_timer_per_user.pop(auth_user, None)
|
||||||
|
|
||||||
|
return self.distributor.fire(
|
||||||
|
"stopped_user_eventstream", auth_user
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug("Scheduling _later: for %s", auth_user)
|
||||||
|
self._stop_timer_per_user[auth_user] = (
|
||||||
|
self.clock.call_later(30, _later)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._stop_timer_per_user.pop(auth_user, None)
|
|
||||||
|
|
||||||
yield self.distributor.fire(
|
|
||||||
"stopped_user_eventstream", auth_user
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Scheduling _later: for %s", auth_user)
|
|
||||||
self._stop_timer_per_user[auth_user] = (
|
|
||||||
self.clock.call_later(30, _later)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EventHandler(BaseHandler):
|
class EventHandler(BaseHandler):
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ from synapse.crypto.event_signing import (
|
||||||
compute_event_signature, check_event_content_hash,
|
compute_event_signature, check_event_content_hash,
|
||||||
add_hashes_and_signatures,
|
add_hashes_and_signatures,
|
||||||
)
|
)
|
||||||
|
from synapse.types import UserID
|
||||||
from syutil.jsonutil import encode_canonical_json
|
from syutil.jsonutil import encode_canonical_json
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
@ -227,7 +228,7 @@ class FederationHandler(BaseHandler):
|
||||||
extra_users = []
|
extra_users = []
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
target_user_id = event.state_key
|
target_user_id = event.state_key
|
||||||
target_user = self.hs.parse_userid(target_user_id)
|
target_user = UserID.from_string(target_user_id)
|
||||||
extra_users.append(target_user)
|
extra_users.append(target_user)
|
||||||
|
|
||||||
yield self.notifier.on_new_room_event(
|
yield self.notifier.on_new_room_event(
|
||||||
|
@ -236,7 +237,7 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
if event.membership == Membership.JOIN:
|
if event.membership == Membership.JOIN:
|
||||||
user = self.hs.parse_userid(event.state_key)
|
user = UserID.from_string(event.state_key)
|
||||||
yield self.distributor.fire(
|
yield self.distributor.fire(
|
||||||
"user_joined_room", user=user, room_id=event.room_id
|
"user_joined_room", user=user, room_id=event.room_id
|
||||||
)
|
)
|
||||||
|
@ -491,7 +492,7 @@ class FederationHandler(BaseHandler):
|
||||||
extra_users = []
|
extra_users = []
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
target_user_id = event.state_key
|
target_user_id = event.state_key
|
||||||
target_user = self.hs.parse_userid(target_user_id)
|
target_user = UserID.from_string(target_user_id)
|
||||||
extra_users.append(target_user)
|
extra_users.append(target_user)
|
||||||
|
|
||||||
yield self.notifier.on_new_room_event(
|
yield self.notifier.on_new_room_event(
|
||||||
|
@ -500,7 +501,7 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
if event.content["membership"] == Membership.JOIN:
|
if event.content["membership"] == Membership.JOIN:
|
||||||
user = self.hs.parse_userid(event.state_key)
|
user = UserID.from_string(event.state_key)
|
||||||
yield self.distributor.fire(
|
yield self.distributor.fire(
|
||||||
"user_joined_room", user=user, room_id=event.room_id
|
"user_joined_room", user=user, room_id=event.room_id
|
||||||
)
|
)
|
||||||
|
@ -514,7 +515,7 @@ class FederationHandler(BaseHandler):
|
||||||
if k[0] == EventTypes.Member:
|
if k[0] == EventTypes.Member:
|
||||||
if s.content["membership"] == Membership.JOIN:
|
if s.content["membership"] == Membership.JOIN:
|
||||||
destinations.add(
|
destinations.add(
|
||||||
self.hs.parse_userid(s.state_key).domain
|
UserID.from_string(s.state_key).domain
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
@ -565,7 +566,7 @@ class FederationHandler(BaseHandler):
|
||||||
backfilled=False,
|
backfilled=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
target_user = self.hs.parse_userid(event.state_key)
|
target_user = UserID.from_string(event.state_key)
|
||||||
yield self.notifier.on_new_room_event(
|
yield self.notifier.on_new_room_event(
|
||||||
event, extra_users=[target_user],
|
event, extra_users=[target_user],
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,8 +18,10 @@ from twisted.internet import defer
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
from synapse.api.errors import RoomError
|
from synapse.api.errors import RoomError
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
|
from synapse.events.utils import serialize_event
|
||||||
from synapse.events.validator import EventValidator
|
from synapse.events.validator import EventValidator
|
||||||
from synapse.util.logcontext import PreserveLoggingContext
|
from synapse.util.logcontext import PreserveLoggingContext
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
@ -89,7 +91,7 @@ class MessageHandler(BaseHandler):
|
||||||
yield self.hs.get_event_sources().get_current_token()
|
yield self.hs.get_event_sources().get_current_token()
|
||||||
)
|
)
|
||||||
|
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
events, next_key = yield data_source.get_pagination_rows(
|
events, next_key = yield data_source.get_pagination_rows(
|
||||||
user, pagin_config.get_source_config("room"), room_id
|
user, pagin_config.get_source_config("room"), room_id
|
||||||
|
@ -99,9 +101,11 @@ class MessageHandler(BaseHandler):
|
||||||
"room_key", next_key
|
"room_key", next_key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
chunk = {
|
chunk = {
|
||||||
"chunk": [
|
"chunk": [
|
||||||
self.hs.serialize_event(e, as_client_event) for e in events
|
serialize_event(e, time_now, as_client_event) for e in events
|
||||||
],
|
],
|
||||||
"start": pagin_config.from_token.to_string(),
|
"start": pagin_config.from_token.to_string(),
|
||||||
"end": next_token.to_string(),
|
"end": next_token.to_string(),
|
||||||
|
@ -110,7 +114,8 @@ class MessageHandler(BaseHandler):
|
||||||
defer.returnValue(chunk)
|
defer.returnValue(chunk)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_and_send_event(self, event_dict, ratelimit=True):
|
def create_and_send_event(self, event_dict, ratelimit=True,
|
||||||
|
client=None, txn_id=None):
|
||||||
""" Given a dict from a client, create and handle a new event.
|
""" Given a dict from a client, create and handle a new event.
|
||||||
|
|
||||||
Creates an FrozenEvent object, filling out auth_events, prev_events,
|
Creates an FrozenEvent object, filling out auth_events, prev_events,
|
||||||
|
@ -130,13 +135,13 @@ class MessageHandler(BaseHandler):
|
||||||
if ratelimit:
|
if ratelimit:
|
||||||
self.ratelimit(builder.user_id)
|
self.ratelimit(builder.user_id)
|
||||||
# TODO(paul): Why does 'event' not have a 'user' object?
|
# TODO(paul): Why does 'event' not have a 'user' object?
|
||||||
user = self.hs.parse_userid(builder.user_id)
|
user = UserID.from_string(builder.user_id)
|
||||||
assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
|
assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
|
||||||
|
|
||||||
if builder.type == EventTypes.Member:
|
if builder.type == EventTypes.Member:
|
||||||
membership = builder.content.get("membership", None)
|
membership = builder.content.get("membership", None)
|
||||||
if membership == Membership.JOIN:
|
if membership == Membership.JOIN:
|
||||||
joinee = self.hs.parse_userid(builder.state_key)
|
joinee = UserID.from_string(builder.state_key)
|
||||||
# If event doesn't include a display name, add one.
|
# If event doesn't include a display name, add one.
|
||||||
yield self.distributor.fire(
|
yield self.distributor.fire(
|
||||||
"collect_presencelike_data",
|
"collect_presencelike_data",
|
||||||
|
@ -144,6 +149,15 @@ class MessageHandler(BaseHandler):
|
||||||
builder.content
|
builder.content
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if client is not None:
|
||||||
|
if client.token_id is not None:
|
||||||
|
builder.internal_metadata.token_id = client.token_id
|
||||||
|
if client.device_id is not None:
|
||||||
|
builder.internal_metadata.device_id = client.device_id
|
||||||
|
|
||||||
|
if txn_id is not None:
|
||||||
|
builder.internal_metadata.txn_id = txn_id
|
||||||
|
|
||||||
event, context = yield self._create_new_client_event(
|
event, context = yield self._create_new_client_event(
|
||||||
builder=builder,
|
builder=builder,
|
||||||
)
|
)
|
||||||
|
@ -210,7 +224,8 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
# TODO: This is duplicating logic from snapshot_all_rooms
|
# TODO: This is duplicating logic from snapshot_all_rooms
|
||||||
current_state = yield self.state_handler.get_current_state(room_id)
|
current_state = yield self.state_handler.get_current_state(room_id)
|
||||||
defer.returnValue([self.hs.serialize_event(c) for c in current_state])
|
now = self.clock.time_msec()
|
||||||
|
defer.returnValue([serialize_event(c, now) for c in current_state])
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def snapshot_all_rooms(self, user_id=None, pagin_config=None,
|
def snapshot_all_rooms(self, user_id=None, pagin_config=None,
|
||||||
|
@ -237,7 +252,7 @@ class MessageHandler(BaseHandler):
|
||||||
membership_list=[Membership.INVITE, Membership.JOIN]
|
membership_list=[Membership.INVITE, Membership.JOIN]
|
||||||
)
|
)
|
||||||
|
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
rooms_ret = []
|
rooms_ret = []
|
||||||
|
|
||||||
|
@ -282,10 +297,11 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("room_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token[0])
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token.copy_and_replace("room_key", token[1])
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
d["messages"] = {
|
d["messages"] = {
|
||||||
"chunk": [
|
"chunk": [
|
||||||
self.hs.serialize_event(m, as_client_event)
|
serialize_event(m, time_now, as_client_event)
|
||||||
for m in messages
|
for m in messages
|
||||||
],
|
],
|
||||||
"start": start_token.to_string(),
|
"start": start_token.to_string(),
|
||||||
|
@ -296,7 +312,8 @@ class MessageHandler(BaseHandler):
|
||||||
event.room_id
|
event.room_id
|
||||||
)
|
)
|
||||||
d["state"] = [
|
d["state"] = [
|
||||||
self.hs.serialize_event(c) for c in current_state
|
serialize_event(c, time_now, as_client_event)
|
||||||
|
for c in current_state
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
logger.exception("Failed to get snapshot")
|
logger.exception("Failed to get snapshot")
|
||||||
|
@ -316,11 +333,12 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
# TODO(paul): I wish I was called with user objects not user_id
|
# TODO(paul): I wish I was called with user objects not user_id
|
||||||
# strings...
|
# strings...
|
||||||
auth_user = self.hs.parse_userid(user_id)
|
auth_user = UserID.from_string(user_id)
|
||||||
|
|
||||||
# TODO: These concurrently
|
# TODO: These concurrently
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
state_tuples = yield self.state_handler.get_current_state(room_id)
|
state_tuples = yield self.state_handler.get_current_state(room_id)
|
||||||
state = [self.hs.serialize_event(x) for x in state_tuples]
|
state = [serialize_event(x, time_now) for x in state_tuples]
|
||||||
|
|
||||||
member_event = (yield self.store.get_room_member(
|
member_event = (yield self.store.get_room_member(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
@ -349,7 +367,7 @@ class MessageHandler(BaseHandler):
|
||||||
for m in room_members:
|
for m in room_members:
|
||||||
try:
|
try:
|
||||||
member_presence = yield presence_handler.get_state(
|
member_presence = yield presence_handler.get_state(
|
||||||
target_user=self.hs.parse_userid(m.user_id),
|
target_user=UserID.from_string(m.user_id),
|
||||||
auth_user=auth_user,
|
auth_user=auth_user,
|
||||||
as_event=True,
|
as_event=True,
|
||||||
)
|
)
|
||||||
|
@ -359,11 +377,13 @@ class MessageHandler(BaseHandler):
|
||||||
"Failed to get member presence of %r", m.user_id
|
"Failed to get member presence of %r", m.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"membership": member_event.membership,
|
"membership": member_event.membership,
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"messages": {
|
"messages": {
|
||||||
"chunk": [self.hs.serialize_event(m) for m in messages],
|
"chunk": [serialize_event(m, time_now) for m in messages],
|
||||||
"start": start_token.to_string(),
|
"start": start_token.to_string(),
|
||||||
"end": end_token.to_string(),
|
"end": end_token.to_string(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ from synapse.api.constants import PresenceState
|
||||||
|
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.logcontext import PreserveLoggingContext
|
from synapse.util.logcontext import PreserveLoggingContext
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
@ -86,6 +87,10 @@ class PresenceHandler(BaseHandler):
|
||||||
"changed_presencelike_data", self.changed_presencelike_data
|
"changed_presencelike_data", self.changed_presencelike_data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# outbound signal from the presence module to advertise when a user's
|
||||||
|
# presence has changed
|
||||||
|
distributor.declare("user_presence_changed")
|
||||||
|
|
||||||
self.distributor = distributor
|
self.distributor = distributor
|
||||||
|
|
||||||
self.federation = hs.get_replication_layer()
|
self.federation = hs.get_replication_layer()
|
||||||
|
@ -96,22 +101,22 @@ class PresenceHandler(BaseHandler):
|
||||||
self.federation.register_edu_handler(
|
self.federation.register_edu_handler(
|
||||||
"m.presence_invite",
|
"m.presence_invite",
|
||||||
lambda origin, content: self.invite_presence(
|
lambda origin, content: self.invite_presence(
|
||||||
observed_user=hs.parse_userid(content["observed_user"]),
|
observed_user=UserID.from_string(content["observed_user"]),
|
||||||
observer_user=hs.parse_userid(content["observer_user"]),
|
observer_user=UserID.from_string(content["observer_user"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.federation.register_edu_handler(
|
self.federation.register_edu_handler(
|
||||||
"m.presence_accept",
|
"m.presence_accept",
|
||||||
lambda origin, content: self.accept_presence(
|
lambda origin, content: self.accept_presence(
|
||||||
observed_user=hs.parse_userid(content["observed_user"]),
|
observed_user=UserID.from_string(content["observed_user"]),
|
||||||
observer_user=hs.parse_userid(content["observer_user"]),
|
observer_user=UserID.from_string(content["observer_user"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.federation.register_edu_handler(
|
self.federation.register_edu_handler(
|
||||||
"m.presence_deny",
|
"m.presence_deny",
|
||||||
lambda origin, content: self.deny_presence(
|
lambda origin, content: self.deny_presence(
|
||||||
observed_user=hs.parse_userid(content["observed_user"]),
|
observed_user=UserID.from_string(content["observed_user"]),
|
||||||
observer_user=hs.parse_userid(content["observer_user"]),
|
observer_user=UserID.from_string(content["observer_user"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -418,7 +423,7 @@ class PresenceHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
for p in presence:
|
for p in presence:
|
||||||
observed_user = self.hs.parse_userid(p.pop("observed_user_id"))
|
observed_user = UserID.from_string(p.pop("observed_user_id"))
|
||||||
p["observed_user"] = observed_user
|
p["observed_user"] = observed_user
|
||||||
p.update(self._get_or_offline_usercache(observed_user).get_state())
|
p.update(self._get_or_offline_usercache(observed_user).get_state())
|
||||||
if "last_active" in p:
|
if "last_active" in p:
|
||||||
|
@ -441,7 +446,7 @@ class PresenceHandler(BaseHandler):
|
||||||
user.localpart, accepted=True
|
user.localpart, accepted=True
|
||||||
)
|
)
|
||||||
target_users = set([
|
target_users = set([
|
||||||
self.hs.parse_userid(x["observed_user_id"]) for x in presence
|
UserID.from_string(x["observed_user_id"]) for x in presence
|
||||||
])
|
])
|
||||||
|
|
||||||
# Also include people in all my rooms
|
# Also include people in all my rooms
|
||||||
|
@ -603,6 +608,7 @@ class PresenceHandler(BaseHandler):
|
||||||
room_ids=room_ids,
|
room_ids=room_ids,
|
||||||
statuscache=statuscache,
|
statuscache=statuscache,
|
||||||
)
|
)
|
||||||
|
yield self.distributor.fire("user_presence_changed", user, statuscache)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _push_presence_remote(self, user, destination, state=None):
|
def _push_presence_remote(self, user, destination, state=None):
|
||||||
|
@ -646,7 +652,7 @@ class PresenceHandler(BaseHandler):
|
||||||
deferreds = []
|
deferreds = []
|
||||||
|
|
||||||
for push in content.get("push", []):
|
for push in content.get("push", []):
|
||||||
user = self.hs.parse_userid(push["user_id"])
|
user = UserID.from_string(push["user_id"])
|
||||||
|
|
||||||
logger.debug("Incoming presence update from %s", user)
|
logger.debug("Incoming presence update from %s", user)
|
||||||
|
|
||||||
|
@ -694,7 +700,7 @@ class PresenceHandler(BaseHandler):
|
||||||
del self._user_cachemap[user]
|
del self._user_cachemap[user]
|
||||||
|
|
||||||
for poll in content.get("poll", []):
|
for poll in content.get("poll", []):
|
||||||
user = self.hs.parse_userid(poll)
|
user = UserID.from_string(poll)
|
||||||
|
|
||||||
if not self.hs.is_mine(user):
|
if not self.hs.is_mine(user):
|
||||||
continue
|
continue
|
||||||
|
@ -709,7 +715,7 @@ class PresenceHandler(BaseHandler):
|
||||||
deferreds.append(self._push_presence_remote(user, origin))
|
deferreds.append(self._push_presence_remote(user, origin))
|
||||||
|
|
||||||
for unpoll in content.get("unpoll", []):
|
for unpoll in content.get("unpoll", []):
|
||||||
user = self.hs.parse_userid(unpoll)
|
user = UserID.from_string(unpoll)
|
||||||
|
|
||||||
if not self.hs.is_mine(user):
|
if not self.hs.is_mine(user):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -18,6 +18,7 @@ from twisted.internet import defer
|
||||||
from synapse.api.errors import SynapseError, AuthError, CodeMessageException
|
from synapse.api.errors import SynapseError, AuthError, CodeMessageException
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
from synapse.util.logcontext import PreserveLoggingContext
|
from synapse.util.logcontext import PreserveLoggingContext
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
@ -169,7 +170,7 @@ class ProfileHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_profile_query(self, args):
|
def on_profile_query(self, args):
|
||||||
user = self.hs.parse_userid(args["user_id"])
|
user = UserID.from_string(args["user_id"])
|
||||||
if not self.hs.is_mine(user):
|
if not self.hs.is_mine(user):
|
||||||
raise SynapseError(400, "User is not hosted on this Home Server")
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
# each request
|
# each request
|
||||||
httpCli = SimpleHttpClient(self.hs)
|
httpCli = SimpleHttpClient(self.hs)
|
||||||
# XXX: make this configurable!
|
# XXX: make this configurable!
|
||||||
trustedIdServers = ['matrix.org:8090']
|
trustedIdServers = ['matrix.org:8090', 'matrix.org']
|
||||||
if not creds['idServer'] in trustedIdServers:
|
if not creds['idServer'] in trustedIdServers:
|
||||||
logger.warn('%s is not a trusted ID server: rejecting 3pid ' +
|
logger.warn('%s is not a trusted ID server: rejecting 3pid ' +
|
||||||
'credentials', creds['idServer'])
|
'credentials', creds['idServer'])
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
"""Contains functions for performing events on rooms."""
|
"""Contains functions for performing events on rooms."""
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.types import UserID, RoomAlias, RoomID
|
from synapse.types import UserID, RoomAlias, RoomID
|
||||||
from synapse.api.constants import EventTypes, Membership, JoinRules
|
from synapse.api.constants import EventTypes, Membership, JoinRules
|
||||||
from synapse.api.errors import StoreError, SynapseError
|
from synapse.api.errors import StoreError, SynapseError
|
||||||
from synapse.util import stringutils
|
from synapse.util import stringutils
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
from ._base import BaseHandler
|
from synapse.events.utils import serialize_event
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -64,7 +66,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
invite_list = config.get("invite", [])
|
invite_list = config.get("invite", [])
|
||||||
for i in invite_list:
|
for i in invite_list:
|
||||||
try:
|
try:
|
||||||
self.hs.parse_userid(i)
|
UserID.from_string(i)
|
||||||
except:
|
except:
|
||||||
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
servers=[self.hs.hostname],
|
servers=[self.hs.hostname],
|
||||||
)
|
)
|
||||||
|
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
creation_events = self._create_events_for_new_room(
|
creation_events = self._create_events_for_new_room(
|
||||||
user, room_id, is_public=is_public
|
user, room_id, is_public=is_public
|
||||||
)
|
)
|
||||||
|
@ -246,11 +248,9 @@ class RoomMemberHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_room_members(self, room_id):
|
def get_room_members(self, room_id):
|
||||||
hs = self.hs
|
|
||||||
|
|
||||||
users = yield self.store.get_users_in_room(room_id)
|
users = yield self.store.get_users_in_room(room_id)
|
||||||
|
|
||||||
defer.returnValue([hs.parse_userid(u) for u in users])
|
defer.returnValue([UserID.from_string(u) for u in users])
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def fetch_room_distributions_into(self, room_id, localusers=None,
|
def fetch_room_distributions_into(self, room_id, localusers=None,
|
||||||
|
@ -295,8 +295,9 @@ class RoomMemberHandler(BaseHandler):
|
||||||
yield self.auth.check_joined_room(room_id, user_id)
|
yield self.auth.check_joined_room(room_id, user_id)
|
||||||
|
|
||||||
member_list = yield self.store.get_room_members(room_id=room_id)
|
member_list = yield self.store.get_room_members(room_id=room_id)
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
event_list = [
|
event_list = [
|
||||||
self.hs.serialize_event(entry)
|
serialize_event(entry, time_now)
|
||||||
for entry in member_list
|
for entry in member_list
|
||||||
]
|
]
|
||||||
chunk_data = {
|
chunk_data = {
|
||||||
|
@ -368,7 +369,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
if prev_state and prev_state.membership == Membership.JOIN:
|
if prev_state and prev_state.membership == Membership.JOIN:
|
||||||
user = self.hs.parse_userid(event.user_id)
|
user = UserID.from_string(event.user_id)
|
||||||
self.distributor.fire(
|
self.distributor.fire(
|
||||||
"user_left_room", user=user, room_id=event.room_id
|
"user_left_room", user=user, room_id=event.room_id
|
||||||
)
|
)
|
||||||
|
@ -412,7 +413,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _do_join(self, event, context, room_host=None, do_auth=True):
|
def _do_join(self, event, context, room_host=None, do_auth=True):
|
||||||
joinee = self.hs.parse_userid(event.state_key)
|
joinee = UserID.from_string(event.state_key)
|
||||||
# room_id = RoomID.from_string(event.room_id, self.hs)
|
# room_id = RoomID.from_string(event.room_id, self.hs)
|
||||||
room_id = event.room_id
|
room_id = event.room_id
|
||||||
|
|
||||||
|
@ -476,7 +477,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
do_auth=do_auth,
|
do_auth=do_auth,
|
||||||
)
|
)
|
||||||
|
|
||||||
user = self.hs.parse_userid(event.user_id)
|
user = UserID.from_string(event.user_id)
|
||||||
yield self.distributor.fire(
|
yield self.distributor.fire(
|
||||||
"user_joined_room", user=user, room_id=room_id
|
"user_joined_room", user=user, room_id=room_id
|
||||||
)
|
)
|
||||||
|
@ -526,7 +527,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
do_auth):
|
do_auth):
|
||||||
yield run_on_reactor()
|
yield run_on_reactor()
|
||||||
|
|
||||||
target_user = self.hs.parse_userid(event.state_key)
|
target_user = UserID.from_string(event.state_key)
|
||||||
|
|
||||||
yield self.handle_new_client_event(
|
yield self.handle_new_client_event(
|
||||||
event,
|
event,
|
||||||
|
|
|
@ -18,6 +18,7 @@ from twisted.internet import defer
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError, AuthError
|
from synapse.api.errors import SynapseError, AuthError
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -185,7 +186,7 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _recv_edu(self, origin, content):
|
def _recv_edu(self, origin, content):
|
||||||
room_id = content["room_id"]
|
room_id = content["room_id"]
|
||||||
user = self.homeserver.parse_userid(content["user_id"])
|
user = UserID.from_string(content["user_id"])
|
||||||
|
|
||||||
localusers = set()
|
localusers = set()
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,25 @@ class SimpleHttpClient(object):
|
||||||
|
|
||||||
defer.returnValue(json.loads(body))
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def post_json_get_json(self, uri, post_json):
|
||||||
|
json_str = json.dumps(post_json)
|
||||||
|
|
||||||
|
logger.info("HTTP POST %s -> %s", json_str, uri)
|
||||||
|
|
||||||
|
response = yield self.agent.request(
|
||||||
|
"POST",
|
||||||
|
uri.encode("ascii"),
|
||||||
|
headers=Headers({
|
||||||
|
"Content-Type": ["application/json"]
|
||||||
|
}),
|
||||||
|
bodyProducer=FileBodyProducer(StringIO(json_str))
|
||||||
|
)
|
||||||
|
|
||||||
|
body = yield readBody(response)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(body))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_json(self, uri, args={}):
|
def get_json(self, uri, args={}):
|
||||||
""" Get's some json from the given host and path
|
""" Get's some json from the given host and path
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
from synapse.http.agent_name import AGENT_NAME
|
from synapse.http.agent_name import AGENT_NAME
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
cs_exception, SynapseError, CodeMessageException
|
cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError
|
||||||
)
|
)
|
||||||
from synapse.util.logcontext import LoggingContext
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
@ -139,11 +139,7 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Huh. No one wanted to handle that? Fiiiiiine. Send 400.
|
# Huh. No one wanted to handle that? Fiiiiiine. Send 400.
|
||||||
self._send_response(
|
raise UnrecognizedRequestError()
|
||||||
request,
|
|
||||||
400,
|
|
||||||
{"error": "Unrecognized request"}
|
|
||||||
)
|
|
||||||
except CodeMessageException as e:
|
except CodeMessageException as e:
|
||||||
if isinstance(e, SynapseError):
|
if isinstance(e, SynapseError):
|
||||||
logger.info("%s SynapseError: %s - %s", request, e.code, e.msg)
|
logger.info("%s SynapseError: %s - %s", request, e.code, e.msg)
|
||||||
|
|
113
synapse/http/servlet.py
Normal file
113
synapse/http/servlet.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
""" This module contains base REST classes for constructing REST servlets. """
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RestServlet(object):
|
||||||
|
|
||||||
|
""" A Synapse REST Servlet.
|
||||||
|
|
||||||
|
An implementing class can either provide its own custom 'register' method,
|
||||||
|
or use the automatic pattern handling provided by the base class.
|
||||||
|
|
||||||
|
To use this latter, the implementing class instead provides a `PATTERN`
|
||||||
|
class attribute containing a pre-compiled regular expression. The automatic
|
||||||
|
register method will then use this method to register any of the following
|
||||||
|
instance methods associated with the corresponding HTTP method:
|
||||||
|
|
||||||
|
on_GET
|
||||||
|
on_PUT
|
||||||
|
on_POST
|
||||||
|
on_DELETE
|
||||||
|
on_OPTIONS
|
||||||
|
|
||||||
|
Automatically handles turning CodeMessageExceptions thrown by these methods
|
||||||
|
into the appropriate HTTP response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register(self, http_server):
|
||||||
|
""" Register this servlet with the given HTTP server. """
|
||||||
|
if hasattr(self, "PATTERN"):
|
||||||
|
pattern = self.PATTERN
|
||||||
|
|
||||||
|
for method in ("GET", "PUT", "POST", "OPTIONS", "DELETE"):
|
||||||
|
if hasattr(self, "on_%s" % (method)):
|
||||||
|
method_handler = getattr(self, "on_%s" % (method))
|
||||||
|
http_server.register_path(method, pattern, method_handler)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("RestServlet must register something.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_integer(request, name, default=None, required=False):
|
||||||
|
if name in request.args:
|
||||||
|
try:
|
||||||
|
return int(request.args[name][0])
|
||||||
|
except:
|
||||||
|
message = "Query parameter %r must be an integer" % (name,)
|
||||||
|
raise SynapseError(400, message)
|
||||||
|
else:
|
||||||
|
if required:
|
||||||
|
message = "Missing integer query parameter %r" % (name,)
|
||||||
|
raise SynapseError(400, message)
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_boolean(request, name, default=None, required=False):
|
||||||
|
if name in request.args:
|
||||||
|
try:
|
||||||
|
return {
|
||||||
|
"true": True,
|
||||||
|
"false": False,
|
||||||
|
}[request.args[name][0]]
|
||||||
|
except:
|
||||||
|
message = (
|
||||||
|
"Boolean query parameter %r must be one of"
|
||||||
|
" ['true', 'false']"
|
||||||
|
) % (name,)
|
||||||
|
raise SynapseError(400, message)
|
||||||
|
else:
|
||||||
|
if required:
|
||||||
|
message = "Missing boolean query parameter %r" % (name,)
|
||||||
|
raise SynapseError(400, message)
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_string(request, name, default=None, required=False,
|
||||||
|
allowed_values=None, param_type="string"):
|
||||||
|
if name in request.args:
|
||||||
|
value = request.args[name][0]
|
||||||
|
if allowed_values is not None and value not in allowed_values:
|
||||||
|
message = "Query parameter %r must be one of [%s]" % (
|
||||||
|
name, ", ".join(repr(v) for v in allowed_values)
|
||||||
|
)
|
||||||
|
raise SynapseError(message)
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
if required:
|
||||||
|
message = "Missing %s query parameter %r" % (param_type, name)
|
||||||
|
raise SynapseError(400, message)
|
||||||
|
else:
|
||||||
|
return default
|
364
synapse/push/__init__.py
Normal file
364
synapse/push/__init__.py
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.streams.config import PaginationConfig
|
||||||
|
from synapse.types import StreamToken, UserID
|
||||||
|
|
||||||
|
import synapse.util.async
|
||||||
|
import baserules
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import fnmatch
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Pusher(object):
|
||||||
|
INITIAL_BACKOFF = 1000
|
||||||
|
MAX_BACKOFF = 60 * 60 * 1000
|
||||||
|
GIVE_UP_AFTER = 24 * 60 * 60 * 1000
|
||||||
|
DEFAULT_ACTIONS = ['notify']
|
||||||
|
|
||||||
|
def __init__(self, _hs, instance_handle, user_name, app_id,
|
||||||
|
app_display_name, device_display_name, pushkey, pushkey_ts,
|
||||||
|
data, last_token, last_success, failing_since):
|
||||||
|
self.hs = _hs
|
||||||
|
self.evStreamHandler = self.hs.get_handlers().event_stream_handler
|
||||||
|
self.store = self.hs.get_datastore()
|
||||||
|
self.clock = self.hs.get_clock()
|
||||||
|
self.instance_handle = instance_handle
|
||||||
|
self.user_name = user_name
|
||||||
|
self.app_id = app_id
|
||||||
|
self.app_display_name = app_display_name
|
||||||
|
self.device_display_name = device_display_name
|
||||||
|
self.pushkey = pushkey
|
||||||
|
self.pushkey_ts = pushkey_ts
|
||||||
|
self.data = data
|
||||||
|
self.last_token = last_token
|
||||||
|
self.last_success = last_success # not actually used
|
||||||
|
self.backoff_delay = Pusher.INITIAL_BACKOFF
|
||||||
|
self.failing_since = failing_since
|
||||||
|
self.alive = True
|
||||||
|
|
||||||
|
# The last value of last_active_time that we saw
|
||||||
|
self.last_last_active_time = 0
|
||||||
|
self.has_unread = True
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _actions_for_event(self, ev):
|
||||||
|
"""
|
||||||
|
This should take into account notification settings that the user
|
||||||
|
has configured both globally and per-room when we have the ability
|
||||||
|
to do such things.
|
||||||
|
"""
|
||||||
|
if ev['user_id'] == self.user_name:
|
||||||
|
# let's assume you probably know about messages you sent yourself
|
||||||
|
defer.returnValue(['dont_notify'])
|
||||||
|
|
||||||
|
if ev['type'] == 'm.room.member':
|
||||||
|
if ev['state_key'] != self.user_name:
|
||||||
|
defer.returnValue(['dont_notify'])
|
||||||
|
|
||||||
|
rules = yield self.store.get_push_rules_for_user_name(self.user_name)
|
||||||
|
|
||||||
|
for r in rules:
|
||||||
|
r['conditions'] = json.loads(r['conditions'])
|
||||||
|
r['actions'] = json.loads(r['actions'])
|
||||||
|
|
||||||
|
user_name_localpart = UserID.from_string(self.user_name).localpart
|
||||||
|
|
||||||
|
rules.extend(baserules.make_base_rules(user_name_localpart))
|
||||||
|
|
||||||
|
# get *our* member event for display name matching
|
||||||
|
member_events_for_room = yield self.store.get_current_state(
|
||||||
|
room_id=ev['room_id'],
|
||||||
|
event_type='m.room.member',
|
||||||
|
state_key=self.user_name
|
||||||
|
)
|
||||||
|
my_display_name = None
|
||||||
|
if len(member_events_for_room) > 0:
|
||||||
|
my_display_name = member_events_for_room[0].content['displayname']
|
||||||
|
|
||||||
|
for r in rules:
|
||||||
|
matches = True
|
||||||
|
|
||||||
|
conditions = r['conditions']
|
||||||
|
actions = r['actions']
|
||||||
|
|
||||||
|
for c in conditions:
|
||||||
|
matches &= self._event_fulfills_condition(
|
||||||
|
ev, c, display_name=my_display_name
|
||||||
|
)
|
||||||
|
# ignore rules with no actions (we have an explict 'dont_notify'
|
||||||
|
if len(actions) == 0:
|
||||||
|
logger.warn(
|
||||||
|
"Ignoring rule id %s with no actions for user %s" %
|
||||||
|
(r['rule_id'], r['user_name'])
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if matches:
|
||||||
|
defer.returnValue(actions)
|
||||||
|
|
||||||
|
defer.returnValue(Pusher.DEFAULT_ACTIONS)
|
||||||
|
|
||||||
|
def _event_fulfills_condition(self, ev, condition, display_name):
|
||||||
|
if condition['kind'] == 'event_match':
|
||||||
|
if 'pattern' not in condition:
|
||||||
|
logger.warn("event_match condition with no pattern")
|
||||||
|
return False
|
||||||
|
pat = condition['pattern']
|
||||||
|
|
||||||
|
val = _value_for_dotted_key(condition['key'], ev)
|
||||||
|
if val is None:
|
||||||
|
return False
|
||||||
|
return fnmatch.fnmatch(val.upper(), pat.upper())
|
||||||
|
elif condition['kind'] == 'device':
|
||||||
|
if 'instance_handle' not in condition:
|
||||||
|
return True
|
||||||
|
return condition['instance_handle'] == self.instance_handle
|
||||||
|
elif condition['kind'] == 'contains_display_name':
|
||||||
|
# This is special because display names can be different
|
||||||
|
# between rooms and so you can't really hard code it in a rule.
|
||||||
|
# Optimisation: we should cache these names and update them from
|
||||||
|
# the event stream.
|
||||||
|
if 'content' not in ev or 'body' not in ev['content']:
|
||||||
|
return False
|
||||||
|
return fnmatch.fnmatch(
|
||||||
|
ev['content']['body'].upper(), "*%s*" % (display_name.upper(),)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_context_for_event(self, ev):
|
||||||
|
name_aliases = yield self.store.get_room_name_and_aliases(
|
||||||
|
ev['room_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx = {'aliases': name_aliases[1]}
|
||||||
|
if name_aliases[0] is not None:
|
||||||
|
ctx['name'] = name_aliases[0]
|
||||||
|
|
||||||
|
their_member_events_for_room = yield self.store.get_current_state(
|
||||||
|
room_id=ev['room_id'],
|
||||||
|
event_type='m.room.member',
|
||||||
|
state_key=ev['user_id']
|
||||||
|
)
|
||||||
|
if len(their_member_events_for_room) > 0:
|
||||||
|
dn = their_member_events_for_room[0].content['displayname']
|
||||||
|
if dn is not None:
|
||||||
|
ctx['sender_display_name'] = dn
|
||||||
|
|
||||||
|
defer.returnValue(ctx)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def start(self):
|
||||||
|
if not self.last_token:
|
||||||
|
# First-time setup: get a token to start from (we can't
|
||||||
|
# just start from no token, ie. 'now'
|
||||||
|
# because we need the result to be reproduceable in case
|
||||||
|
# we fail to dispatch the push)
|
||||||
|
config = PaginationConfig(from_token=None, limit='1')
|
||||||
|
chunk = yield self.evStreamHandler.get_stream(
|
||||||
|
self.user_name, config, timeout=0)
|
||||||
|
self.last_token = chunk['end']
|
||||||
|
self.store.update_pusher_last_token(
|
||||||
|
self.user_name, self.pushkey, self.last_token)
|
||||||
|
logger.info("Pusher %s for user %s starting from token %s",
|
||||||
|
self.pushkey, self.user_name, self.last_token)
|
||||||
|
|
||||||
|
while self.alive:
|
||||||
|
from_tok = StreamToken.from_string(self.last_token)
|
||||||
|
config = PaginationConfig(from_token=from_tok, limit='1')
|
||||||
|
chunk = yield self.evStreamHandler.get_stream(
|
||||||
|
self.user_name, config,
|
||||||
|
timeout=100*365*24*60*60*1000, affect_presence=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# limiting to 1 may get 1 event plus 1 presence event, so
|
||||||
|
# pick out the actual event
|
||||||
|
single_event = None
|
||||||
|
for c in chunk['chunk']:
|
||||||
|
if 'event_id' in c: # Hmmm...
|
||||||
|
single_event = c
|
||||||
|
break
|
||||||
|
if not single_event:
|
||||||
|
self.last_token = chunk['end']
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not self.alive:
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed = False
|
||||||
|
actions = yield self._actions_for_event(single_event)
|
||||||
|
tweaks = _tweaks_for_actions(actions)
|
||||||
|
|
||||||
|
if len(actions) == 0:
|
||||||
|
logger.warn("Empty actions! Using default action.")
|
||||||
|
actions = Pusher.DEFAULT_ACTIONS
|
||||||
|
if 'notify' not in actions and 'dont_notify' not in actions:
|
||||||
|
logger.warn("Neither notify nor dont_notify in actions: adding default")
|
||||||
|
actions.extend(Pusher.DEFAULT_ACTIONS)
|
||||||
|
if 'dont_notify' in actions:
|
||||||
|
logger.debug(
|
||||||
|
"%s for %s: dont_notify",
|
||||||
|
single_event['event_id'], self.user_name
|
||||||
|
)
|
||||||
|
processed = True
|
||||||
|
else:
|
||||||
|
rejected = yield self.dispatch_push(single_event, tweaks)
|
||||||
|
self.has_unread = True
|
||||||
|
if isinstance(rejected, list) or isinstance(rejected, tuple):
|
||||||
|
processed = True
|
||||||
|
for pk in rejected:
|
||||||
|
if pk != self.pushkey:
|
||||||
|
# for sanity, we only remove the pushkey if it
|
||||||
|
# was the one we actually sent...
|
||||||
|
logger.warn(
|
||||||
|
("Ignoring rejected pushkey %s because we"
|
||||||
|
" didn't send it"), pk
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
"Pushkey %s was rejected: removing",
|
||||||
|
pk
|
||||||
|
)
|
||||||
|
yield self.hs.get_pusherpool().remove_pusher(
|
||||||
|
self.app_id, pk
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.alive:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if processed:
|
||||||
|
self.backoff_delay = Pusher.INITIAL_BACKOFF
|
||||||
|
self.last_token = chunk['end']
|
||||||
|
self.store.update_pusher_last_token_and_success(
|
||||||
|
self.user_name,
|
||||||
|
self.pushkey,
|
||||||
|
self.last_token,
|
||||||
|
self.clock.time_msec()
|
||||||
|
)
|
||||||
|
if self.failing_since:
|
||||||
|
self.failing_since = None
|
||||||
|
self.store.update_pusher_failing_since(
|
||||||
|
self.user_name,
|
||||||
|
self.pushkey,
|
||||||
|
self.failing_since)
|
||||||
|
else:
|
||||||
|
if not self.failing_since:
|
||||||
|
self.failing_since = self.clock.time_msec()
|
||||||
|
self.store.update_pusher_failing_since(
|
||||||
|
self.user_name,
|
||||||
|
self.pushkey,
|
||||||
|
self.failing_since
|
||||||
|
)
|
||||||
|
|
||||||
|
if (self.failing_since and
|
||||||
|
self.failing_since <
|
||||||
|
self.clock.time_msec() - Pusher.GIVE_UP_AFTER):
|
||||||
|
# we really only give up so that if the URL gets
|
||||||
|
# fixed, we don't suddenly deliver a load
|
||||||
|
# of old notifications.
|
||||||
|
logger.warn("Giving up on a notification to user %s, "
|
||||||
|
"pushkey %s",
|
||||||
|
self.user_name, self.pushkey)
|
||||||
|
self.backoff_delay = Pusher.INITIAL_BACKOFF
|
||||||
|
self.last_token = chunk['end']
|
||||||
|
self.store.update_pusher_last_token(
|
||||||
|
self.user_name,
|
||||||
|
self.pushkey,
|
||||||
|
self.last_token
|
||||||
|
)
|
||||||
|
|
||||||
|
self.failing_since = None
|
||||||
|
self.store.update_pusher_failing_since(
|
||||||
|
self.user_name,
|
||||||
|
self.pushkey,
|
||||||
|
self.failing_since
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warn("Failed to dispatch push for user %s "
|
||||||
|
"(failing for %dms)."
|
||||||
|
"Trying again in %dms",
|
||||||
|
self.user_name,
|
||||||
|
self.clock.time_msec() - self.failing_since,
|
||||||
|
self.backoff_delay)
|
||||||
|
yield synapse.util.async.sleep(self.backoff_delay / 1000.0)
|
||||||
|
self.backoff_delay *= 2
|
||||||
|
if self.backoff_delay > Pusher.MAX_BACKOFF:
|
||||||
|
self.backoff_delay = Pusher.MAX_BACKOFF
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
def dispatch_push(self, p, tweaks):
|
||||||
|
"""
|
||||||
|
Overridden by implementing classes to actually deliver the notification
|
||||||
|
Args:
|
||||||
|
p: The event to notify for as a single event from the event stream
|
||||||
|
Returns: If the notification was delivered, an array containing any
|
||||||
|
pushkeys that were rejected by the push gateway.
|
||||||
|
False if the notification could not be delivered (ie.
|
||||||
|
should be retried).
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reset_badge_count(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def presence_changed(self, state):
|
||||||
|
"""
|
||||||
|
We clear badge counts whenever a user's last_active time is bumped
|
||||||
|
This is by no means perfect but I think it's the best we can do
|
||||||
|
without read receipts.
|
||||||
|
"""
|
||||||
|
if 'last_active' in state.state:
|
||||||
|
last_active = state.state['last_active']
|
||||||
|
if last_active > self.last_last_active_time:
|
||||||
|
self.last_last_active_time = last_active
|
||||||
|
if self.has_unread:
|
||||||
|
logger.info("Resetting badge count for %s", self.user_name)
|
||||||
|
self.reset_badge_count()
|
||||||
|
self.has_unread = False
|
||||||
|
|
||||||
|
|
||||||
|
def _value_for_dotted_key(dotted_key, event):
|
||||||
|
parts = dotted_key.split(".")
|
||||||
|
val = event
|
||||||
|
while len(parts) > 0:
|
||||||
|
if parts[0] not in val:
|
||||||
|
return None
|
||||||
|
val = val[parts[0]]
|
||||||
|
parts = parts[1:]
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def _tweaks_for_actions(actions):
|
||||||
|
tweaks = {}
|
||||||
|
for a in actions:
|
||||||
|
if not isinstance(a, dict):
|
||||||
|
continue
|
||||||
|
if 'set_sound' in a:
|
||||||
|
tweaks['sound'] = a['set_sound']
|
||||||
|
return tweaks
|
||||||
|
|
||||||
|
|
||||||
|
class PusherConfigException(Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
super(PusherConfigException, self).__init__(msg)
|
35
synapse/push/baserules.py
Normal file
35
synapse/push/baserules.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
def make_base_rules(user_name):
|
||||||
|
"""
|
||||||
|
Nominally we reserve priority class 0 for these rules, although
|
||||||
|
in practice we just append them to the end so we don't actually need it.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'conditions': [
|
||||||
|
{
|
||||||
|
'kind': 'event_match',
|
||||||
|
'key': 'content.body',
|
||||||
|
'pattern': '*%s*' % (user_name,), # Matrix ID match
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'actions': [
|
||||||
|
'notify',
|
||||||
|
{
|
||||||
|
'set_sound': 'default'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'conditions': [
|
||||||
|
{
|
||||||
|
'kind': 'contains_display_name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'actions': [
|
||||||
|
'notify',
|
||||||
|
{
|
||||||
|
'set_sound': 'default'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
146
synapse/push/httppusher.py
Normal file
146
synapse/push/httppusher.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from synapse.push import Pusher, PusherConfigException
|
||||||
|
from synapse.http.client import SimpleHttpClient
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpPusher(Pusher):
|
||||||
|
def __init__(self, _hs, instance_handle, user_name, app_id,
|
||||||
|
app_display_name, device_display_name, pushkey, pushkey_ts,
|
||||||
|
data, last_token, last_success, failing_since):
|
||||||
|
super(HttpPusher, self).__init__(
|
||||||
|
_hs,
|
||||||
|
instance_handle,
|
||||||
|
user_name,
|
||||||
|
app_id,
|
||||||
|
app_display_name,
|
||||||
|
device_display_name,
|
||||||
|
pushkey,
|
||||||
|
pushkey_ts,
|
||||||
|
data,
|
||||||
|
last_token,
|
||||||
|
last_success,
|
||||||
|
failing_since
|
||||||
|
)
|
||||||
|
if 'url' not in data:
|
||||||
|
raise PusherConfigException(
|
||||||
|
"'url' required in data for HTTP pusher"
|
||||||
|
)
|
||||||
|
self.url = data['url']
|
||||||
|
self.httpCli = SimpleHttpClient(self.hs)
|
||||||
|
self.data_minus_url = {}
|
||||||
|
self.data_minus_url.update(self.data)
|
||||||
|
del self.data_minus_url['url']
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _build_notification_dict(self, event, tweaks):
|
||||||
|
# we probably do not want to push for every presence update
|
||||||
|
# (we may want to be able to set up notifications when specific
|
||||||
|
# people sign in, but we'd want to only deliver the pertinent ones)
|
||||||
|
# Actually, presence events will not get this far now because we
|
||||||
|
# need to filter them out in the main Pusher code.
|
||||||
|
if 'event_id' not in event:
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
ctx = yield self.get_context_for_event(event)
|
||||||
|
|
||||||
|
d = {
|
||||||
|
'notification': {
|
||||||
|
'id': event['event_id'],
|
||||||
|
'type': event['type'],
|
||||||
|
'sender': event['user_id'],
|
||||||
|
'counts': { # -- we don't mark messages as read yet so
|
||||||
|
# we have no way of knowing
|
||||||
|
# Just set the badge to 1 until we have read receipts
|
||||||
|
'unread': 1,
|
||||||
|
# 'missed_calls': 2
|
||||||
|
},
|
||||||
|
'devices': [
|
||||||
|
{
|
||||||
|
'app_id': self.app_id,
|
||||||
|
'pushkey': self.pushkey,
|
||||||
|
'pushkey_ts': long(self.pushkey_ts / 1000),
|
||||||
|
'data': self.data_minus_url,
|
||||||
|
'tweaks': tweaks
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event['type'] == 'm.room.member':
|
||||||
|
d['notification']['membership'] = event['content']['membership']
|
||||||
|
if 'content' in event:
|
||||||
|
d['notification']['content'] = event['content']
|
||||||
|
|
||||||
|
if len(ctx['aliases']):
|
||||||
|
d['notification']['room_alias'] = ctx['aliases'][0]
|
||||||
|
if 'sender_display_name' in ctx:
|
||||||
|
d['notification']['sender_display_name'] = ctx['sender_display_name']
|
||||||
|
if 'name' in ctx:
|
||||||
|
d['notification']['room_name'] = ctx['name']
|
||||||
|
|
||||||
|
defer.returnValue(d)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def dispatch_push(self, event, tweaks):
|
||||||
|
notification_dict = yield self._build_notification_dict(event, tweaks)
|
||||||
|
if not notification_dict:
|
||||||
|
defer.returnValue([])
|
||||||
|
try:
|
||||||
|
resp = yield self.httpCli.post_json_get_json(self.url, notification_dict)
|
||||||
|
except:
|
||||||
|
logger.exception("Failed to push %s ", self.url)
|
||||||
|
defer.returnValue(False)
|
||||||
|
rejected = []
|
||||||
|
if 'rejected' in resp:
|
||||||
|
rejected = resp['rejected']
|
||||||
|
defer.returnValue(rejected)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def reset_badge_count(self):
|
||||||
|
d = {
|
||||||
|
'notification': {
|
||||||
|
'id': '',
|
||||||
|
'type': None,
|
||||||
|
'sender': '',
|
||||||
|
'counts': {
|
||||||
|
'unread': 0,
|
||||||
|
'missed_calls': 0
|
||||||
|
},
|
||||||
|
'devices': [
|
||||||
|
{
|
||||||
|
'app_id': self.app_id,
|
||||||
|
'pushkey': self.pushkey,
|
||||||
|
'pushkey_ts': long(self.pushkey_ts / 1000),
|
||||||
|
'data': self.data_minus_url,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp = yield self.httpCli.post_json_get_json(self.url, d)
|
||||||
|
except:
|
||||||
|
logger.exception("Failed to push %s ", self.url)
|
||||||
|
defer.returnValue(False)
|
||||||
|
rejected = []
|
||||||
|
if 'rejected' in resp:
|
||||||
|
rejected = resp['rejected']
|
||||||
|
defer.returnValue(rejected)
|
152
synapse/push/pusherpool.py
Normal file
152
synapse/push/pusherpool.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from httppusher import HttpPusher
|
||||||
|
from synapse.push import PusherConfigException
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PusherPool:
|
||||||
|
def __init__(self, _hs):
|
||||||
|
self.hs = _hs
|
||||||
|
self.store = self.hs.get_datastore()
|
||||||
|
self.pushers = {}
|
||||||
|
self.last_pusher_started = -1
|
||||||
|
|
||||||
|
distributor = self.hs.get_distributor()
|
||||||
|
distributor.observe(
|
||||||
|
"user_presence_changed", self.user_presence_changed
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def user_presence_changed(self, user, state):
|
||||||
|
user_name = user.to_string()
|
||||||
|
|
||||||
|
# until we have read receipts, pushers use this to reset a user's
|
||||||
|
# badge counters to zero
|
||||||
|
for p in self.pushers.values():
|
||||||
|
if p.user_name == user_name:
|
||||||
|
yield p.presence_changed(state)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def start(self):
|
||||||
|
pushers = yield self.store.get_all_pushers()
|
||||||
|
for p in pushers:
|
||||||
|
p['data'] = json.loads(p['data'])
|
||||||
|
self._start_pushers(pushers)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def add_pusher(self, user_name, instance_handle, kind, app_id,
|
||||||
|
app_display_name, device_display_name, pushkey, lang, data):
|
||||||
|
# we try to create the pusher just to validate the config: it
|
||||||
|
# will then get pulled out of the database,
|
||||||
|
# recreated, added and started: this means we have only one
|
||||||
|
# code path adding pushers.
|
||||||
|
self._create_pusher({
|
||||||
|
"user_name": user_name,
|
||||||
|
"kind": kind,
|
||||||
|
"instance_handle": instance_handle,
|
||||||
|
"app_id": app_id,
|
||||||
|
"app_display_name": app_display_name,
|
||||||
|
"device_display_name": device_display_name,
|
||||||
|
"pushkey": pushkey,
|
||||||
|
"pushkey_ts": self.hs.get_clock().time_msec(),
|
||||||
|
"lang": lang,
|
||||||
|
"data": data,
|
||||||
|
"last_token": None,
|
||||||
|
"last_success": None,
|
||||||
|
"failing_since": None
|
||||||
|
})
|
||||||
|
yield self._add_pusher_to_store(
|
||||||
|
user_name, instance_handle, kind, app_id,
|
||||||
|
app_display_name, device_display_name,
|
||||||
|
pushkey, lang, data
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _add_pusher_to_store(self, user_name, instance_handle, kind, app_id,
|
||||||
|
app_display_name, device_display_name,
|
||||||
|
pushkey, lang, data):
|
||||||
|
yield self.store.add_pusher(
|
||||||
|
user_name=user_name,
|
||||||
|
instance_handle=instance_handle,
|
||||||
|
kind=kind,
|
||||||
|
app_id=app_id,
|
||||||
|
app_display_name=app_display_name,
|
||||||
|
device_display_name=device_display_name,
|
||||||
|
pushkey=pushkey,
|
||||||
|
pushkey_ts=self.hs.get_clock().time_msec(),
|
||||||
|
lang=lang,
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
self._refresh_pusher((app_id, pushkey))
|
||||||
|
|
||||||
|
def _create_pusher(self, pusherdict):
|
||||||
|
if pusherdict['kind'] == 'http':
|
||||||
|
return HttpPusher(
|
||||||
|
self.hs,
|
||||||
|
instance_handle=pusherdict['instance_handle'],
|
||||||
|
user_name=pusherdict['user_name'],
|
||||||
|
app_id=pusherdict['app_id'],
|
||||||
|
app_display_name=pusherdict['app_display_name'],
|
||||||
|
device_display_name=pusherdict['device_display_name'],
|
||||||
|
pushkey=pusherdict['pushkey'],
|
||||||
|
pushkey_ts=pusherdict['pushkey_ts'],
|
||||||
|
data=pusherdict['data'],
|
||||||
|
last_token=pusherdict['last_token'],
|
||||||
|
last_success=pusherdict['last_success'],
|
||||||
|
failing_since=pusherdict['failing_since']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise PusherConfigException(
|
||||||
|
"Unknown pusher type '%s' for user %s" %
|
||||||
|
(pusherdict['kind'], pusherdict['user_name'])
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _refresh_pusher(self, app_id_pushkey):
|
||||||
|
p = yield self.store.get_pushers_by_app_id_and_pushkey(
|
||||||
|
app_id_pushkey
|
||||||
|
)
|
||||||
|
p['data'] = json.loads(p['data'])
|
||||||
|
|
||||||
|
self._start_pushers([p])
|
||||||
|
|
||||||
|
def _start_pushers(self, pushers):
|
||||||
|
logger.info("Starting %d pushers", len(pushers))
|
||||||
|
for pusherdict in pushers:
|
||||||
|
p = self._create_pusher(pusherdict)
|
||||||
|
if p:
|
||||||
|
fullid = "%s:%s" % (pusherdict['app_id'], pusherdict['pushkey'])
|
||||||
|
if fullid in self.pushers:
|
||||||
|
self.pushers[fullid].stop()
|
||||||
|
self.pushers[fullid] = p
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def remove_pusher(self, app_id, pushkey):
|
||||||
|
fullid = "%s:%s" % (app_id, pushkey)
|
||||||
|
if fullid in self.pushers:
|
||||||
|
logger.info("Stopping pusher %s", fullid)
|
||||||
|
self.pushers[fullid].stop()
|
||||||
|
del self.pushers[fullid]
|
||||||
|
yield self.store.delete_pusher_by_app_id_pushkey(app_id, pushkey)
|
|
@ -5,8 +5,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUIREMENTS = {
|
REQUIREMENTS = {
|
||||||
"syutil==0.0.2": ["syutil"],
|
"syutil==0.0.2": ["syutil"],
|
||||||
"matrix_angular_sdk==0.6.0": ["syweb==0.6.0"],
|
"matrix_angular_sdk==0.6.0": ["syweb>=0.6.0"],
|
||||||
"Twisted>=14.0.0": ["twisted>=14.0.0"],
|
"Twisted==14.0.2": ["twisted==14.0.2"],
|
||||||
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
||||||
"pyopenssl>=0.14": ["OpenSSL>=0.14"],
|
"pyopenssl>=0.14": ["OpenSSL>=0.14"],
|
||||||
"pyyaml": ["yaml"],
|
"pyyaml": ["yaml"],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014, 2015 OpenMarket Ltd
|
# Copyright 2015 OpenMarket Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,36 +12,3 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
room, events, register, login, profile, presence, initial_sync, directory,
|
|
||||||
voip, admin,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RestServletFactory(object):
|
|
||||||
|
|
||||||
""" A factory for creating REST servlets.
|
|
||||||
|
|
||||||
These REST servlets represent the entire client-server REST API. Generally
|
|
||||||
speaking, they serve as wrappers around events and the handlers that
|
|
||||||
process them.
|
|
||||||
|
|
||||||
See synapse.events for information on synapse events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
|
||||||
client_resource = hs.get_resource_for_client()
|
|
||||||
|
|
||||||
# TODO(erikj): There *must* be a better way of doing this.
|
|
||||||
room.register_servlets(hs, client_resource)
|
|
||||||
events.register_servlets(hs, client_resource)
|
|
||||||
register.register_servlets(hs, client_resource)
|
|
||||||
login.register_servlets(hs, client_resource)
|
|
||||||
profile.register_servlets(hs, client_resource)
|
|
||||||
presence.register_servlets(hs, client_resource)
|
|
||||||
initial_sync.register_servlets(hs, client_resource)
|
|
||||||
directory.register_servlets(hs, client_resource)
|
|
||||||
voip.register_servlets(hs, client_resource)
|
|
||||||
admin.register_servlets(hs, client_resource)
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2014, 2015 OpenMarket Ltd
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
""" This module contains base REST classes for constructing REST servlets. """
|
|
||||||
from synapse.api.urls import CLIENT_PREFIX
|
|
||||||
from synapse.rest.transactions import HttpTransactionStore
|
|
||||||
import re
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def client_path_pattern(path_regex):
|
|
||||||
"""Creates a regex compiled client path with the correct client path
|
|
||||||
prefix.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path_regex (str): The regex string to match. This should NOT have a ^
|
|
||||||
as this will be prefixed.
|
|
||||||
Returns:
|
|
||||||
SRE_Pattern
|
|
||||||
"""
|
|
||||||
return re.compile("^" + CLIENT_PREFIX + path_regex)
|
|
||||||
|
|
||||||
|
|
||||||
class RestServlet(object):
|
|
||||||
|
|
||||||
""" A Synapse REST Servlet.
|
|
||||||
|
|
||||||
An implementing class can either provide its own custom 'register' method,
|
|
||||||
or use the automatic pattern handling provided by the base class.
|
|
||||||
|
|
||||||
To use this latter, the implementing class instead provides a `PATTERN`
|
|
||||||
class attribute containing a pre-compiled regular expression. The automatic
|
|
||||||
register method will then use this method to register any of the following
|
|
||||||
instance methods associated with the corresponding HTTP method:
|
|
||||||
|
|
||||||
on_GET
|
|
||||||
on_PUT
|
|
||||||
on_POST
|
|
||||||
on_DELETE
|
|
||||||
on_OPTIONS
|
|
||||||
|
|
||||||
Automatically handles turning CodeMessageExceptions thrown by these methods
|
|
||||||
into the appropriate HTTP response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
|
||||||
self.hs = hs
|
|
||||||
|
|
||||||
self.handlers = hs.get_handlers()
|
|
||||||
self.builder_factory = hs.get_event_builder_factory()
|
|
||||||
self.auth = hs.get_auth()
|
|
||||||
self.txns = HttpTransactionStore()
|
|
||||||
|
|
||||||
def register(self, http_server):
|
|
||||||
""" Register this servlet with the given HTTP server. """
|
|
||||||
if hasattr(self, "PATTERN"):
|
|
||||||
pattern = self.PATTERN
|
|
||||||
|
|
||||||
for method in ("GET", "PUT", "POST", "OPTIONS", "DELETE"):
|
|
||||||
if hasattr(self, "on_%s" % (method)):
|
|
||||||
method_handler = getattr(self, "on_%s" % (method))
|
|
||||||
http_server.register_path(method, pattern, method_handler)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError("RestServlet must register something.")
|
|
14
synapse/rest/client/__init__.py
Normal file
14
synapse/rest/client/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
44
synapse/rest/client/v1/__init__.py
Normal file
44
synapse/rest/client/v1/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
room, events, register, login, profile, presence, initial_sync, directory,
|
||||||
|
voip, admin, pusher, push_rule
|
||||||
|
)
|
||||||
|
|
||||||
|
from synapse.http.server import JsonResource
|
||||||
|
|
||||||
|
|
||||||
|
class ClientV1RestResource(JsonResource):
|
||||||
|
"""A resource for version 1 of the matrix client API."""
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
JsonResource.__init__(self)
|
||||||
|
self.register_servlets(self, hs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register_servlets(client_resource, hs):
|
||||||
|
room.register_servlets(hs, client_resource)
|
||||||
|
events.register_servlets(hs, client_resource)
|
||||||
|
register.register_servlets(hs, client_resource)
|
||||||
|
login.register_servlets(hs, client_resource)
|
||||||
|
profile.register_servlets(hs, client_resource)
|
||||||
|
presence.register_servlets(hs, client_resource)
|
||||||
|
initial_sync.register_servlets(hs, client_resource)
|
||||||
|
directory.register_servlets(hs, client_resource)
|
||||||
|
voip.register_servlets(hs, client_resource)
|
||||||
|
admin.register_servlets(hs, client_resource)
|
||||||
|
pusher.register_servlets(hs, client_resource)
|
||||||
|
push_rule.register_servlets(hs, client_resource)
|
|
@ -16,20 +16,22 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import AuthError, SynapseError
|
from synapse.api.errors import AuthError, SynapseError
|
||||||
from base import RestServlet, client_path_pattern
|
from synapse.types import UserID
|
||||||
|
|
||||||
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WhoisRestServlet(RestServlet):
|
class WhoisRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/admin/whois/(?P<user_id>[^/]*)")
|
PATTERN = client_path_pattern("/admin/whois/(?P<user_id>[^/]*)")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
target_user = self.hs.parse_userid(user_id)
|
target_user = UserID.from_string(user_id)
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
is_admin = yield self.auth.is_server_admin(auth_user)
|
is_admin = yield self.auth.is_server_admin(auth_user)
|
||||||
|
|
||||||
if not is_admin and target_user != auth_user:
|
if not is_admin and target_user != auth_user:
|
52
synapse/rest/client/v1/base.py
Normal file
52
synapse/rest/client/v1/base.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""This module contains base REST classes for constructing client v1 servlets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from synapse.http.servlet import RestServlet
|
||||||
|
from synapse.api.urls import CLIENT_PREFIX
|
||||||
|
from .transactions import HttpTransactionStore
|
||||||
|
import re
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def client_path_pattern(path_regex):
|
||||||
|
"""Creates a regex compiled client path with the correct client path
|
||||||
|
prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path_regex (str): The regex string to match. This should NOT have a ^
|
||||||
|
as this will be prefixed.
|
||||||
|
Returns:
|
||||||
|
SRE_Pattern
|
||||||
|
"""
|
||||||
|
return re.compile("^" + CLIENT_PREFIX + path_regex)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientV1RestServlet(RestServlet):
|
||||||
|
"""A base Synapse REST Servlet for the client version 1 API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.hs = hs
|
||||||
|
self.handlers = hs.get_handlers()
|
||||||
|
self.builder_factory = hs.get_event_builder_factory()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
self.txns = HttpTransactionStore()
|
|
@ -17,7 +17,8 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import AuthError, SynapseError, Codes
|
from synapse.api.errors import AuthError, SynapseError, Codes
|
||||||
from base import RestServlet, client_path_pattern
|
from synapse.types import RoomAlias
|
||||||
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -30,12 +31,12 @@ def register_servlets(hs, http_server):
|
||||||
ClientDirectoryServer(hs).register(http_server)
|
ClientDirectoryServer(hs).register(http_server)
|
||||||
|
|
||||||
|
|
||||||
class ClientDirectoryServer(RestServlet):
|
class ClientDirectoryServer(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/directory/room/(?P<room_alias>[^/]*)$")
|
PATTERN = client_path_pattern("/directory/room/(?P<room_alias>[^/]*)$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_alias):
|
def on_GET(self, request, room_alias):
|
||||||
room_alias = self.hs.parse_roomalias(room_alias)
|
room_alias = RoomAlias.from_string(room_alias)
|
||||||
|
|
||||||
dir_handler = self.handlers.directory_handler
|
dir_handler = self.handlers.directory_handler
|
||||||
res = yield dir_handler.get_association(room_alias)
|
res = yield dir_handler.get_association(room_alias)
|
||||||
|
@ -44,7 +45,7 @@ class ClientDirectoryServer(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_alias):
|
def on_PUT(self, request, room_alias):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
if not "room_id" in content:
|
if not "room_id" in content:
|
||||||
|
@ -53,7 +54,7 @@ class ClientDirectoryServer(RestServlet):
|
||||||
|
|
||||||
logger.debug("Got content: %s", content)
|
logger.debug("Got content: %s", content)
|
||||||
|
|
||||||
room_alias = self.hs.parse_roomalias(room_alias)
|
room_alias = RoomAlias.from_string(room_alias)
|
||||||
|
|
||||||
logger.debug("Got room name: %s", room_alias.to_string())
|
logger.debug("Got room name: %s", room_alias.to_string())
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ class ClientDirectoryServer(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_DELETE(self, request, room_alias):
|
def on_DELETE(self, request, room_alias):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
is_admin = yield self.auth.is_server_admin(user)
|
is_admin = yield self.auth.is_server_admin(user)
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
|
@ -92,7 +93,7 @@ class ClientDirectoryServer(RestServlet):
|
||||||
|
|
||||||
dir_handler = self.handlers.directory_handler
|
dir_handler = self.handlers.directory_handler
|
||||||
|
|
||||||
room_alias = self.hs.parse_roomalias(room_alias)
|
room_alias = RoomAlias.from_string(room_alias)
|
||||||
|
|
||||||
yield dir_handler.delete_association(
|
yield dir_handler.delete_association(
|
||||||
user.to_string(), room_alias
|
user.to_string(), room_alias
|
|
@ -18,7 +18,8 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from synapse.rest.base import RestServlet, client_path_pattern
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
from synapse.events.utils import serialize_event
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -26,14 +27,14 @@ import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EventStreamRestServlet(RestServlet):
|
class EventStreamRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/events$")
|
PATTERN = client_path_pattern("/events$")
|
||||||
|
|
||||||
DEFAULT_LONGPOLL_TIME_MS = 30000
|
DEFAULT_LONGPOLL_TIME_MS = 30000
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
try:
|
try:
|
||||||
handler = self.handlers.event_stream_handler
|
handler = self.handlers.event_stream_handler
|
||||||
pagin_config = PaginationConfig.from_request(request)
|
pagin_config = PaginationConfig.from_request(request)
|
||||||
|
@ -61,17 +62,22 @@ class EventStreamRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Unit test gets, with and without auth, with different kinds of events.
|
# TODO: Unit test gets, with and without auth, with different kinds of events.
|
||||||
class EventRestServlet(RestServlet):
|
class EventRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/events/(?P<event_id>[^/]*)$")
|
PATTERN = client_path_pattern("/events/(?P<event_id>[^/]*)$")
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(EventRestServlet, self).__init__(hs)
|
||||||
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, event_id):
|
def on_GET(self, request, event_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
handler = self.handlers.event_handler
|
handler = self.handlers.event_handler
|
||||||
event = yield handler.get_event(auth_user, event_id)
|
event = yield handler.get_event(auth_user, event_id)
|
||||||
|
|
||||||
|
time_now = self.clock.time_msec()
|
||||||
if event:
|
if event:
|
||||||
defer.returnValue((200, self.hs.serialize_event(event)))
|
defer.returnValue((200, serialize_event(event, time_now)))
|
||||||
else:
|
else:
|
||||||
defer.returnValue((404, "Event not found."))
|
defer.returnValue((404, "Event not found."))
|
||||||
|
|
|
@ -16,16 +16,16 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from base import RestServlet, client_path_pattern
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class InitialSyncRestServlet(RestServlet):
|
class InitialSyncRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/initialSync$")
|
PATTERN = client_path_pattern("/initialSync$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
with_feedback = "feedback" in request.args
|
with_feedback = "feedback" in request.args
|
||||||
as_client_event = "raw" not in request.args
|
as_client_event = "raw" not in request.args
|
||||||
pagination_config = PaginationConfig.from_request(request)
|
pagination_config = PaginationConfig.from_request(request)
|
|
@ -17,12 +17,12 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
from base import RestServlet, client_path_pattern
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class LoginRestServlet(RestServlet):
|
class LoginRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/login$")
|
PATTERN = client_path_pattern("/login$")
|
||||||
PASS_TYPE = "m.login.password"
|
PASS_TYPE = "m.login.password"
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ class LoginRestServlet(RestServlet):
|
||||||
defer.returnValue((200, result))
|
defer.returnValue((200, result))
|
||||||
|
|
||||||
|
|
||||||
class LoginFallbackRestServlet(RestServlet):
|
class LoginFallbackRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/login/fallback$")
|
PATTERN = client_path_pattern("/login/fallback$")
|
||||||
|
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
|
@ -73,7 +73,7 @@ class LoginFallbackRestServlet(RestServlet):
|
||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetRestServlet(RestServlet):
|
class PasswordResetRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/login/reset")
|
PATTERN = client_path_pattern("/login/reset")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
|
@ -18,7 +18,8 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from base import RestServlet, client_path_pattern
|
from synapse.types import UserID
|
||||||
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -26,13 +27,13 @@ import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PresenceStatusRestServlet(RestServlet):
|
class PresenceStatusRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/presence/(?P<user_id>[^/]*)/status")
|
PATTERN = client_path_pattern("/presence/(?P<user_id>[^/]*)/status")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
state = yield self.handlers.presence_handler.get_state(
|
state = yield self.handlers.presence_handler.get_state(
|
||||||
target_user=user, auth_user=auth_user)
|
target_user=user, auth_user=auth_user)
|
||||||
|
@ -41,8 +42,8 @@ class PresenceStatusRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
state = {}
|
state = {}
|
||||||
try:
|
try:
|
||||||
|
@ -71,13 +72,13 @@ class PresenceStatusRestServlet(RestServlet):
|
||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class PresenceListRestServlet(RestServlet):
|
class PresenceListRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/presence/list/(?P<user_id>[^/]*)")
|
PATTERN = client_path_pattern("/presence/list/(?P<user_id>[^/]*)")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
if not self.hs.is_mine(user):
|
if not self.hs.is_mine(user):
|
||||||
raise SynapseError(400, "User not hosted on this Home Server")
|
raise SynapseError(400, "User not hosted on this Home Server")
|
||||||
|
@ -96,8 +97,8 @@ class PresenceListRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, user_id):
|
def on_POST(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
if not self.hs.is_mine(user):
|
if not self.hs.is_mine(user):
|
||||||
raise SynapseError(400, "User not hosted on this Home Server")
|
raise SynapseError(400, "User not hosted on this Home Server")
|
||||||
|
@ -118,7 +119,7 @@ class PresenceListRestServlet(RestServlet):
|
||||||
raise SynapseError(400, "Bad invite value.")
|
raise SynapseError(400, "Bad invite value.")
|
||||||
if len(u) == 0:
|
if len(u) == 0:
|
||||||
continue
|
continue
|
||||||
invited_user = self.hs.parse_userid(u)
|
invited_user = UserID.from_string(u)
|
||||||
yield self.handlers.presence_handler.send_invite(
|
yield self.handlers.presence_handler.send_invite(
|
||||||
observer_user=user, observed_user=invited_user
|
observer_user=user, observed_user=invited_user
|
||||||
)
|
)
|
||||||
|
@ -129,7 +130,7 @@ class PresenceListRestServlet(RestServlet):
|
||||||
raise SynapseError(400, "Bad drop value.")
|
raise SynapseError(400, "Bad drop value.")
|
||||||
if len(u) == 0:
|
if len(u) == 0:
|
||||||
continue
|
continue
|
||||||
dropped_user = self.hs.parse_userid(u)
|
dropped_user = UserID.from_string(u)
|
||||||
yield self.handlers.presence_handler.drop(
|
yield self.handlers.presence_handler.drop(
|
||||||
observer_user=user, observed_user=dropped_user
|
observer_user=user, observed_user=dropped_user
|
||||||
)
|
)
|
|
@ -16,17 +16,18 @@
|
||||||
""" This module contains REST servlets to do with profile: /profile/<paths> """
|
""" This module contains REST servlets to do with profile: /profile/<paths> """
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from base import RestServlet, client_path_pattern
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ProfileDisplaynameRestServlet(RestServlet):
|
class ProfileDisplaynameRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/displayname")
|
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/displayname")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
displayname = yield self.handlers.profile_handler.get_displayname(
|
displayname = yield self.handlers.profile_handler.get_displayname(
|
||||||
user,
|
user,
|
||||||
|
@ -36,8 +37,8 @@ class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
|
@ -54,12 +55,12 @@ class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class ProfileAvatarURLRestServlet(RestServlet):
|
class ProfileAvatarURLRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/avatar_url")
|
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)/avatar_url")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
avatar_url = yield self.handlers.profile_handler.get_avatar_url(
|
avatar_url = yield self.handlers.profile_handler.get_avatar_url(
|
||||||
user,
|
user,
|
||||||
|
@ -69,8 +70,8 @@ class ProfileAvatarURLRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, user_id):
|
def on_PUT(self, request, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
|
@ -87,12 +88,12 @@ class ProfileAvatarURLRestServlet(RestServlet):
|
||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class ProfileRestServlet(RestServlet):
|
class ProfileRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)")
|
PATTERN = client_path_pattern("/profile/(?P<user_id>[^/]*)")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
user = self.hs.parse_userid(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
displayname = yield self.handlers.profile_handler.get_displayname(
|
displayname = yield self.handlers.profile_handler.get_displayname(
|
||||||
user,
|
user,
|
401
synapse/rest/client/v1/push_rule.py
Normal file
401
synapse/rest/client/v1/push_rule.py
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError, Codes, UnrecognizedRequestError, NotFoundError, \
|
||||||
|
StoreError
|
||||||
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
from synapse.storage.push_rule import InconsistentRuleException, RuleNotFoundException
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class PushRuleRestServlet(ClientV1RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/pushrules/.*$")
|
||||||
|
PRIORITY_CLASS_MAP = {
|
||||||
|
'underride': 1,
|
||||||
|
'sender': 2,
|
||||||
|
'room': 3,
|
||||||
|
'content': 4,
|
||||||
|
'override': 5,
|
||||||
|
}
|
||||||
|
PRIORITY_CLASS_INVERSE_MAP = {v: k for k, v in PRIORITY_CLASS_MAP.items()}
|
||||||
|
SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR = (
|
||||||
|
"Unrecognised request: You probably wanted a trailing slash")
|
||||||
|
|
||||||
|
def rule_spec_from_path(self, path):
|
||||||
|
if len(path) < 2:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
if path[0] != 'pushrules':
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
scope = path[1]
|
||||||
|
path = path[2:]
|
||||||
|
if scope not in ['global', 'device']:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
device = None
|
||||||
|
if scope == 'device':
|
||||||
|
if len(path) == 0:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
device = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
|
||||||
|
if len(path) == 0:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
template = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
|
||||||
|
if len(path) == 0:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
rule_id = path[0]
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
'scope': scope,
|
||||||
|
'template': template,
|
||||||
|
'rule_id': rule_id
|
||||||
|
}
|
||||||
|
if device:
|
||||||
|
spec['device'] = device
|
||||||
|
return spec
|
||||||
|
|
||||||
|
def rule_tuple_from_request_object(self, rule_template, rule_id, req_obj, device=None):
|
||||||
|
if rule_template in ['override', 'underride']:
|
||||||
|
if 'conditions' not in req_obj:
|
||||||
|
raise InvalidRuleException("Missing 'conditions'")
|
||||||
|
conditions = req_obj['conditions']
|
||||||
|
for c in conditions:
|
||||||
|
if 'kind' not in c:
|
||||||
|
raise InvalidRuleException("Condition without 'kind'")
|
||||||
|
elif rule_template == 'room':
|
||||||
|
conditions = [{
|
||||||
|
'kind': 'event_match',
|
||||||
|
'key': 'room_id',
|
||||||
|
'pattern': rule_id
|
||||||
|
}]
|
||||||
|
elif rule_template == 'sender':
|
||||||
|
conditions = [{
|
||||||
|
'kind': 'event_match',
|
||||||
|
'key': 'user_id',
|
||||||
|
'pattern': rule_id
|
||||||
|
}]
|
||||||
|
elif rule_template == 'content':
|
||||||
|
if 'pattern' not in req_obj:
|
||||||
|
raise InvalidRuleException("Content rule missing 'pattern'")
|
||||||
|
pat = req_obj['pattern']
|
||||||
|
if pat.strip("*?[]") == pat:
|
||||||
|
# no special glob characters so we assume the user means
|
||||||
|
# 'contains this string' rather than 'is this string'
|
||||||
|
pat = "*%s*" % (pat,)
|
||||||
|
conditions = [{
|
||||||
|
'kind': 'event_match',
|
||||||
|
'key': 'content.body',
|
||||||
|
'pattern': pat
|
||||||
|
}]
|
||||||
|
else:
|
||||||
|
raise InvalidRuleException("Unknown rule template: %s" % (rule_template,))
|
||||||
|
|
||||||
|
if device:
|
||||||
|
conditions.append({
|
||||||
|
'kind': 'device',
|
||||||
|
'instance_handle': device
|
||||||
|
})
|
||||||
|
|
||||||
|
if 'actions' not in req_obj:
|
||||||
|
raise InvalidRuleException("No actions found")
|
||||||
|
actions = req_obj['actions']
|
||||||
|
|
||||||
|
for a in actions:
|
||||||
|
if a in ['notify', 'dont_notify', 'coalesce']:
|
||||||
|
pass
|
||||||
|
elif isinstance(a, dict) and 'set_sound' in a:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise InvalidRuleException("Unrecognised action")
|
||||||
|
|
||||||
|
return conditions, actions
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_PUT(self, request):
|
||||||
|
spec = self.rule_spec_from_path(request.postpath)
|
||||||
|
try:
|
||||||
|
priority_class = _priority_class_from_spec(spec)
|
||||||
|
except InvalidRuleException as e:
|
||||||
|
raise SynapseError(400, e.message)
|
||||||
|
|
||||||
|
user, _ = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
try:
|
||||||
|
(conditions, actions) = self.rule_tuple_from_request_object(
|
||||||
|
spec['template'],
|
||||||
|
spec['rule_id'],
|
||||||
|
content,
|
||||||
|
device=spec['device'] if 'device' in spec else None
|
||||||
|
)
|
||||||
|
except InvalidRuleException as e:
|
||||||
|
raise SynapseError(400, e.message)
|
||||||
|
|
||||||
|
before = request.args.get("before", None)
|
||||||
|
if before and len(before):
|
||||||
|
before = before[0]
|
||||||
|
after = request.args.get("after", None)
|
||||||
|
if after and len(after):
|
||||||
|
after = after[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield self.hs.get_datastore().add_push_rule(
|
||||||
|
user_name=user.to_string(),
|
||||||
|
rule_id=spec['rule_id'],
|
||||||
|
priority_class=priority_class,
|
||||||
|
conditions=conditions,
|
||||||
|
actions=actions,
|
||||||
|
before=before,
|
||||||
|
after=after
|
||||||
|
)
|
||||||
|
except InconsistentRuleException as e:
|
||||||
|
raise SynapseError(400, e.message)
|
||||||
|
except RuleNotFoundException as e:
|
||||||
|
raise SynapseError(400, e.message)
|
||||||
|
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_DELETE(self, request):
|
||||||
|
spec = self.rule_spec_from_path(request.postpath)
|
||||||
|
try:
|
||||||
|
priority_class = _priority_class_from_spec(spec)
|
||||||
|
except InvalidRuleException as e:
|
||||||
|
raise SynapseError(400, e.message)
|
||||||
|
|
||||||
|
user, _ = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
if 'device' in spec:
|
||||||
|
rules = yield self.hs.get_datastore().get_push_rules_for_user_name(
|
||||||
|
user.to_string()
|
||||||
|
)
|
||||||
|
|
||||||
|
for r in rules:
|
||||||
|
conditions = json.loads(r['conditions'])
|
||||||
|
ih = _instance_handle_from_conditions(conditions)
|
||||||
|
if ih == spec['device'] and r['priority_class'] == priority_class:
|
||||||
|
yield self.hs.get_datastore().delete_push_rule(
|
||||||
|
user.to_string(), spec['rule_id']
|
||||||
|
)
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
raise NotFoundError()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
yield self.hs.get_datastore().delete_push_rule(
|
||||||
|
user.to_string(), spec['rule_id'],
|
||||||
|
priority_class=priority_class
|
||||||
|
)
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
except StoreError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
raise NotFoundError()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_GET(self, request):
|
||||||
|
user, _ = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
# we build up the full structure and then decide which bits of it
|
||||||
|
# to send which means doing unnecessary work sometimes but is
|
||||||
|
# is probably not going to make a whole lot of difference
|
||||||
|
rawrules = yield self.hs.get_datastore().get_push_rules_for_user_name(user.to_string())
|
||||||
|
|
||||||
|
rules = {'global': {}, 'device': {}}
|
||||||
|
|
||||||
|
rules['global'] = _add_empty_priority_class_arrays(rules['global'])
|
||||||
|
|
||||||
|
for r in rawrules:
|
||||||
|
rulearray = None
|
||||||
|
|
||||||
|
r["conditions"] = json.loads(r["conditions"])
|
||||||
|
r["actions"] = json.loads(r["actions"])
|
||||||
|
|
||||||
|
template_name = _priority_class_to_template_name(r['priority_class'])
|
||||||
|
|
||||||
|
if r['priority_class'] > PushRuleRestServlet.PRIORITY_CLASS_MAP['override']:
|
||||||
|
# per-device rule
|
||||||
|
instance_handle = _instance_handle_from_conditions(r["conditions"])
|
||||||
|
r = _strip_device_condition(r)
|
||||||
|
if not instance_handle:
|
||||||
|
continue
|
||||||
|
if instance_handle not in rules['device']:
|
||||||
|
rules['device'][instance_handle] = {}
|
||||||
|
rules['device'][instance_handle] = (
|
||||||
|
_add_empty_priority_class_arrays(
|
||||||
|
rules['device'][instance_handle]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
rulearray = rules['device'][instance_handle][template_name]
|
||||||
|
else:
|
||||||
|
rulearray = rules['global'][template_name]
|
||||||
|
|
||||||
|
template_rule = _rule_to_template(r)
|
||||||
|
if template_rule:
|
||||||
|
rulearray.append(template_rule)
|
||||||
|
|
||||||
|
path = request.postpath[1:]
|
||||||
|
|
||||||
|
if path == []:
|
||||||
|
# we're a reference impl: pedantry is our job.
|
||||||
|
raise UnrecognizedRequestError(
|
||||||
|
PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
if path[0] == '':
|
||||||
|
defer.returnValue((200, rules))
|
||||||
|
elif path[0] == 'global':
|
||||||
|
path = path[1:]
|
||||||
|
result = _filter_ruleset_with_path(rules['global'], path)
|
||||||
|
defer.returnValue((200, result))
|
||||||
|
elif path[0] == 'device':
|
||||||
|
path = path[1:]
|
||||||
|
if path == []:
|
||||||
|
raise UnrecognizedRequestError(
|
||||||
|
PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
|
||||||
|
)
|
||||||
|
if path[0] == '':
|
||||||
|
defer.returnValue((200, rules['device']))
|
||||||
|
|
||||||
|
instance_handle = path[0]
|
||||||
|
path = path[1:]
|
||||||
|
if instance_handle not in rules['device']:
|
||||||
|
ret = {}
|
||||||
|
ret = _add_empty_priority_class_arrays(ret)
|
||||||
|
defer.returnValue((200, ret))
|
||||||
|
ruleset = rules['device'][instance_handle]
|
||||||
|
result = _filter_ruleset_with_path(ruleset, path)
|
||||||
|
defer.returnValue((200, result))
|
||||||
|
else:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
def on_OPTIONS(self, _):
|
||||||
|
return 200, {}
|
||||||
|
|
||||||
|
|
||||||
|
def _add_empty_priority_class_arrays(d):
|
||||||
|
for pc in PushRuleRestServlet.PRIORITY_CLASS_MAP.keys():
|
||||||
|
d[pc] = []
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _instance_handle_from_conditions(conditions):
|
||||||
|
"""
|
||||||
|
Given a list of conditions, return the instance handle of the
|
||||||
|
device rule if there is one
|
||||||
|
"""
|
||||||
|
for c in conditions:
|
||||||
|
if c['kind'] == 'device':
|
||||||
|
return c['instance_handle']
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_ruleset_with_path(ruleset, path):
|
||||||
|
if path == []:
|
||||||
|
raise UnrecognizedRequestError(
|
||||||
|
PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
if path[0] == '':
|
||||||
|
return ruleset
|
||||||
|
template_kind = path[0]
|
||||||
|
if template_kind not in ruleset:
|
||||||
|
raise UnrecognizedRequestError()
|
||||||
|
path = path[1:]
|
||||||
|
if path == []:
|
||||||
|
raise UnrecognizedRequestError(
|
||||||
|
PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
|
||||||
|
)
|
||||||
|
if path[0] == '':
|
||||||
|
return ruleset[template_kind]
|
||||||
|
rule_id = path[0]
|
||||||
|
for r in ruleset[template_kind]:
|
||||||
|
if r['rule_id'] == rule_id:
|
||||||
|
return r
|
||||||
|
raise NotFoundError
|
||||||
|
|
||||||
|
|
||||||
|
def _priority_class_from_spec(spec):
|
||||||
|
if spec['template'] not in PushRuleRestServlet.PRIORITY_CLASS_MAP.keys():
|
||||||
|
raise InvalidRuleException("Unknown template: %s" % (spec['kind']))
|
||||||
|
pc = PushRuleRestServlet.PRIORITY_CLASS_MAP[spec['template']]
|
||||||
|
|
||||||
|
if spec['scope'] == 'device':
|
||||||
|
pc += len(PushRuleRestServlet.PRIORITY_CLASS_MAP)
|
||||||
|
|
||||||
|
return pc
|
||||||
|
|
||||||
|
|
||||||
|
def _priority_class_to_template_name(pc):
|
||||||
|
if pc > PushRuleRestServlet.PRIORITY_CLASS_MAP['override']:
|
||||||
|
# per-device
|
||||||
|
prio_class_index = pc - len(PushRuleRestServlet.PRIORITY_CLASS_MAP)
|
||||||
|
return PushRuleRestServlet.PRIORITY_CLASS_INVERSE_MAP[prio_class_index]
|
||||||
|
else:
|
||||||
|
return PushRuleRestServlet.PRIORITY_CLASS_INVERSE_MAP[pc]
|
||||||
|
|
||||||
|
|
||||||
|
def _rule_to_template(rule):
|
||||||
|
template_name = _priority_class_to_template_name(rule['priority_class'])
|
||||||
|
if template_name in ['override', 'underride']:
|
||||||
|
return {k: rule[k] for k in ["rule_id", "conditions", "actions"]}
|
||||||
|
elif template_name in ["sender", "room"]:
|
||||||
|
return {k: rule[k] for k in ["rule_id", "actions"]}
|
||||||
|
elif template_name == 'content':
|
||||||
|
if len(rule["conditions"]) != 1:
|
||||||
|
return None
|
||||||
|
thecond = rule["conditions"][0]
|
||||||
|
if "pattern" not in thecond:
|
||||||
|
return None
|
||||||
|
ret = {k: rule[k] for k in ["rule_id", "actions"]}
|
||||||
|
ret["pattern"] = thecond["pattern"]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_device_condition(rule):
|
||||||
|
for i, c in enumerate(rule['conditions']):
|
||||||
|
if c['kind'] == 'device':
|
||||||
|
del rule['conditions'][i]
|
||||||
|
return rule
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRuleException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# XXX: C+ped from rest/room.py - surely this should be common?
|
||||||
|
def _parse_json(request):
|
||||||
|
try:
|
||||||
|
content = json.loads(request.content.read())
|
||||||
|
if type(content) != dict:
|
||||||
|
raise SynapseError(400, "Content must be a JSON object.",
|
||||||
|
errcode=Codes.NOT_JSON)
|
||||||
|
return content
|
||||||
|
except ValueError:
|
||||||
|
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||||
|
|
||||||
|
|
||||||
|
def register_servlets(hs, http_server):
|
||||||
|
PushRuleRestServlet(hs).register(http_server)
|
89
synapse/rest/client/v1/pusher.py
Normal file
89
synapse/rest/client/v1/pusher.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError, Codes
|
||||||
|
from synapse.push import PusherConfigException
|
||||||
|
from .base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class PusherRestServlet(ClientV1RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/pushers/set$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_POST(self, request):
|
||||||
|
user, _ = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
pusher_pool = self.hs.get_pusherpool()
|
||||||
|
|
||||||
|
if ('pushkey' in content and 'app_id' in content
|
||||||
|
and 'kind' in content and
|
||||||
|
content['kind'] is None):
|
||||||
|
yield pusher_pool.remove_pusher(
|
||||||
|
content['app_id'], content['pushkey']
|
||||||
|
)
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
reqd = ['instance_handle', 'kind', 'app_id', 'app_display_name',
|
||||||
|
'device_display_name', 'pushkey', 'lang', 'data']
|
||||||
|
missing = []
|
||||||
|
for i in reqd:
|
||||||
|
if i not in content:
|
||||||
|
missing.append(i)
|
||||||
|
if len(missing):
|
||||||
|
raise SynapseError(400, "Missing parameters: "+','.join(missing),
|
||||||
|
errcode=Codes.MISSING_PARAM)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield pusher_pool.add_pusher(
|
||||||
|
user_name=user.to_string(),
|
||||||
|
instance_handle=content['instance_handle'],
|
||||||
|
kind=content['kind'],
|
||||||
|
app_id=content['app_id'],
|
||||||
|
app_display_name=content['app_display_name'],
|
||||||
|
device_display_name=content['device_display_name'],
|
||||||
|
pushkey=content['pushkey'],
|
||||||
|
lang=content['lang'],
|
||||||
|
data=content['data']
|
||||||
|
)
|
||||||
|
except PusherConfigException as pce:
|
||||||
|
raise SynapseError(400, "Config Error: "+pce.message,
|
||||||
|
errcode=Codes.MISSING_PARAM)
|
||||||
|
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
def on_OPTIONS(self, _):
|
||||||
|
return 200, {}
|
||||||
|
|
||||||
|
|
||||||
|
# XXX: C+ped from rest/room.py - surely this should be common?
|
||||||
|
def _parse_json(request):
|
||||||
|
try:
|
||||||
|
content = json.loads(request.content.read())
|
||||||
|
if type(content) != dict:
|
||||||
|
raise SynapseError(400, "Content must be a JSON object.",
|
||||||
|
errcode=Codes.NOT_JSON)
|
||||||
|
return content
|
||||||
|
except ValueError:
|
||||||
|
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||||
|
|
||||||
|
|
||||||
|
def register_servlets(hs, http_server):
|
||||||
|
PusherRestServlet(hs).register(http_server)
|
|
@ -18,7 +18,7 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError, Codes
|
from synapse.api.errors import SynapseError, Codes
|
||||||
from synapse.api.constants import LoginType
|
from synapse.api.constants import LoginType
|
||||||
from base import RestServlet, client_path_pattern
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
import synapse.util.stringutils as stringutils
|
import synapse.util.stringutils as stringutils
|
||||||
|
|
||||||
from synapse.util.async import run_on_reactor
|
from synapse.util.async import run_on_reactor
|
||||||
|
@ -42,7 +42,7 @@ else:
|
||||||
compare_digest = lambda a, b: a == b
|
compare_digest = lambda a, b: a == b
|
||||||
|
|
||||||
|
|
||||||
class RegisterRestServlet(RestServlet):
|
class RegisterRestServlet(ClientV1RestServlet):
|
||||||
"""Handles registration with the home server.
|
"""Handles registration with the home server.
|
||||||
|
|
||||||
This servlet is in control of the registration flow; the registration
|
This servlet is in control of the registration flow; the registration
|
|
@ -16,10 +16,12 @@
|
||||||
""" This module contains REST servlets to do with rooms: /rooms/<paths> """
|
""" This module contains REST servlets to do with rooms: /rooms/<paths> """
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from base import RestServlet, client_path_pattern
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
from synapse.api.errors import SynapseError, Codes
|
from synapse.api.errors import SynapseError, Codes
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
|
from synapse.types import UserID, RoomID, RoomAlias
|
||||||
|
from synapse.events.utils import serialize_event
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -29,7 +31,7 @@ import urllib
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RoomCreateRestServlet(RestServlet):
|
class RoomCreateRestServlet(ClientV1RestServlet):
|
||||||
# No PATTERN; we have custom dispatch rules here
|
# No PATTERN; we have custom dispatch rules here
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
|
@ -60,7 +62,7 @@ class RoomCreateRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request):
|
def on_POST(self, request):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
room_config = self.get_room_config(request)
|
room_config = self.get_room_config(request)
|
||||||
info = yield self.make_room(room_config, auth_user, None)
|
info = yield self.make_room(room_config, auth_user, None)
|
||||||
|
@ -93,7 +95,7 @@ class RoomCreateRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for generic events
|
# TODO: Needs unit testing for generic events
|
||||||
class RoomStateEventRestServlet(RestServlet):
|
class RoomStateEventRestServlet(ClientV1RestServlet):
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /room/$roomid/state/$eventtype
|
# /room/$roomid/state/$eventtype
|
||||||
no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$"
|
no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$"
|
||||||
|
@ -123,7 +125,7 @@ class RoomStateEventRestServlet(RestServlet):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id, event_type, state_key):
|
def on_GET(self, request, room_id, event_type, state_key):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
data = yield msg_handler.get_room_data(
|
data = yield msg_handler.get_room_data(
|
||||||
|
@ -140,8 +142,8 @@ class RoomStateEventRestServlet(RestServlet):
|
||||||
defer.returnValue((200, data.get_dict()["content"]))
|
defer.returnValue((200, data.get_dict()["content"]))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_id, event_type, state_key):
|
def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
@ -156,13 +158,15 @@ class RoomStateEventRestServlet(RestServlet):
|
||||||
event_dict["state_key"] = state_key
|
event_dict["state_key"] = state_key
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
yield msg_handler.create_and_send_event(event_dict)
|
yield msg_handler.create_and_send_event(
|
||||||
|
event_dict, client=client, txn_id=txn_id,
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for generic events + feedback
|
# TODO: Needs unit testing for generic events + feedback
|
||||||
class RoomSendEventRestServlet(RestServlet):
|
class RoomSendEventRestServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms/$roomid/send/$event_type[/$txn_id]
|
# /rooms/$roomid/send/$event_type[/$txn_id]
|
||||||
|
@ -170,8 +174,8 @@ class RoomSendEventRestServlet(RestServlet):
|
||||||
register_txn_path(self, PATTERN, http_server, with_get=True)
|
register_txn_path(self, PATTERN, http_server, with_get=True)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_id, event_type):
|
def on_POST(self, request, room_id, event_type, txn_id=None):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
|
@ -181,7 +185,9 @@ class RoomSendEventRestServlet(RestServlet):
|
||||||
"content": content,
|
"content": content,
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"sender": user.to_string(),
|
"sender": user.to_string(),
|
||||||
}
|
},
|
||||||
|
client=client,
|
||||||
|
txn_id=txn_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {"event_id": event.event_id}))
|
defer.returnValue((200, {"event_id": event.event_id}))
|
||||||
|
@ -198,14 +204,14 @@ class RoomSendEventRestServlet(RestServlet):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
response = yield self.on_POST(request, room_id, event_type)
|
response = yield self.on_POST(request, room_id, event_type, txn_id)
|
||||||
|
|
||||||
self.txns.store_client_transaction(request, txn_id, response)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for room ID + alias joins
|
# TODO: Needs unit testing for room ID + alias joins
|
||||||
class JoinRoomAliasServlet(RestServlet):
|
class JoinRoomAliasServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /join/$room_identifier[/$txn_id]
|
# /join/$room_identifier[/$txn_id]
|
||||||
|
@ -213,8 +219,8 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
register_txn_path(self, PATTERN, http_server)
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_identifier):
|
def on_POST(self, request, room_identifier, txn_id=None):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
# the identifier could be a room alias or a room id. Try one then the
|
# the identifier could be a room alias or a room id. Try one then the
|
||||||
# other if it fails to parse, without swallowing other valid
|
# other if it fails to parse, without swallowing other valid
|
||||||
|
@ -223,10 +229,10 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
identifier = None
|
identifier = None
|
||||||
is_room_alias = False
|
is_room_alias = False
|
||||||
try:
|
try:
|
||||||
identifier = self.hs.parse_roomalias(room_identifier)
|
identifier = RoomAlias.from_string(room_identifier)
|
||||||
is_room_alias = True
|
is_room_alias = True
|
||||||
except SynapseError:
|
except SynapseError:
|
||||||
identifier = self.hs.parse_roomid(room_identifier)
|
identifier = RoomID.from_string(room_identifier)
|
||||||
|
|
||||||
# TODO: Support for specifying the home server to join with?
|
# TODO: Support for specifying the home server to join with?
|
||||||
|
|
||||||
|
@ -243,7 +249,9 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
"room_id": identifier.to_string(),
|
"room_id": identifier.to_string(),
|
||||||
"sender": user.to_string(),
|
"sender": user.to_string(),
|
||||||
"state_key": user.to_string(),
|
"state_key": user.to_string(),
|
||||||
}
|
},
|
||||||
|
client=client,
|
||||||
|
txn_id=txn_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {"room_id": identifier.to_string()}))
|
defer.returnValue((200, {"room_id": identifier.to_string()}))
|
||||||
|
@ -257,14 +265,14 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
response = yield self.on_POST(request, room_identifier)
|
response = yield self.on_POST(request, room_identifier, txn_id)
|
||||||
|
|
||||||
self.txns.store_client_transaction(request, txn_id, response)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class PublicRoomListRestServlet(RestServlet):
|
class PublicRoomListRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/publicRooms$")
|
PATTERN = client_path_pattern("/publicRooms$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -275,13 +283,13 @@ class PublicRoomListRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMemberListRestServlet(RestServlet):
|
class RoomMemberListRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/members$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
# TODO support Pagination stream API (limit/tokens)
|
# TODO support Pagination stream API (limit/tokens)
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
handler = self.handlers.room_member_handler
|
handler = self.handlers.room_member_handler
|
||||||
members = yield handler.get_room_members_as_pagination_chunk(
|
members = yield handler.get_room_members_as_pagination_chunk(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
|
@ -289,7 +297,7 @@ class RoomMemberListRestServlet(RestServlet):
|
||||||
|
|
||||||
for event in members["chunk"]:
|
for event in members["chunk"]:
|
||||||
# FIXME: should probably be state_key here, not user_id
|
# FIXME: should probably be state_key here, not user_id
|
||||||
target_user = self.hs.parse_userid(event["user_id"])
|
target_user = UserID.from_string(event["user_id"])
|
||||||
# Presence is an optional cache; don't fail if we can't fetch it
|
# Presence is an optional cache; don't fail if we can't fetch it
|
||||||
try:
|
try:
|
||||||
presence_handler = self.handlers.presence_handler
|
presence_handler = self.handlers.presence_handler
|
||||||
|
@ -304,12 +312,12 @@ class RoomMemberListRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMessageListRestServlet(RestServlet):
|
class RoomMessageListRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/messages$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/messages$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
pagination_config = PaginationConfig.from_request(
|
pagination_config = PaginationConfig.from_request(
|
||||||
request, default_limit=10,
|
request, default_limit=10,
|
||||||
)
|
)
|
||||||
|
@ -328,12 +336,12 @@ class RoomMessageListRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomStateRestServlet(RestServlet):
|
class RoomStateRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/state$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/state$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
handler = self.handlers.message_handler
|
handler = self.handlers.message_handler
|
||||||
# Get all the current state for this room
|
# Get all the current state for this room
|
||||||
events = yield handler.get_state_events(
|
events = yield handler.get_state_events(
|
||||||
|
@ -344,12 +352,12 @@ class RoomStateRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomInitialSyncRestServlet(RestServlet):
|
class RoomInitialSyncRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/initialSync$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/initialSync$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
pagination_config = PaginationConfig.from_request(request)
|
pagination_config = PaginationConfig.from_request(request)
|
||||||
content = yield self.handlers.message_handler.room_initial_sync(
|
content = yield self.handlers.message_handler.room_initial_sync(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
|
@ -359,9 +367,13 @@ class RoomInitialSyncRestServlet(RestServlet):
|
||||||
defer.returnValue((200, content))
|
defer.returnValue((200, content))
|
||||||
|
|
||||||
|
|
||||||
class RoomTriggerBackfill(RestServlet):
|
class RoomTriggerBackfill(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(RoomTriggerBackfill, self).__init__(hs)
|
||||||
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
remote_server = urllib.unquote(
|
remote_server = urllib.unquote(
|
||||||
|
@ -373,12 +385,14 @@ class RoomTriggerBackfill(RestServlet):
|
||||||
handler = self.handlers.federation_handler
|
handler = self.handlers.federation_handler
|
||||||
events = yield handler.backfill(remote_server, room_id, limit)
|
events = yield handler.backfill(remote_server, room_id, limit)
|
||||||
|
|
||||||
res = [self.hs.serialize_event(event) for event in events]
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
|
res = [serialize_event(event, time_now) for event in events]
|
||||||
defer.returnValue((200, res))
|
defer.returnValue((200, res))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMembershipRestServlet(RestServlet):
|
class RoomMembershipRestServlet(ClientV1RestServlet):
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms/$roomid/[invite|join|leave]
|
# /rooms/$roomid/[invite|join|leave]
|
||||||
|
@ -387,8 +401,8 @@ class RoomMembershipRestServlet(RestServlet):
|
||||||
register_txn_path(self, PATTERN, http_server)
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_id, membership_action):
|
def on_POST(self, request, room_id, membership_action, txn_id=None):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
@ -410,7 +424,9 @@ class RoomMembershipRestServlet(RestServlet):
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"sender": user.to_string(),
|
"sender": user.to_string(),
|
||||||
"state_key": state_key,
|
"state_key": state_key,
|
||||||
}
|
},
|
||||||
|
client=client,
|
||||||
|
txn_id=txn_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
@ -424,20 +440,22 @@ class RoomMembershipRestServlet(RestServlet):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
response = yield self.on_POST(request, room_id, membership_action)
|
response = yield self.on_POST(
|
||||||
|
request, room_id, membership_action, txn_id
|
||||||
|
)
|
||||||
|
|
||||||
self.txns.store_client_transaction(request, txn_id, response)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
class RoomRedactEventRestServlet(RestServlet):
|
class RoomRedactEventRestServlet(ClientV1RestServlet):
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
PATTERN = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
|
PATTERN = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
|
||||||
register_txn_path(self, PATTERN, http_server)
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request, room_id, event_id):
|
def on_POST(self, request, room_id, event_id, txn_id=None):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user, client = yield self.auth.get_user_by_req(request)
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
||||||
msg_handler = self.handlers.message_handler
|
msg_handler = self.handlers.message_handler
|
||||||
|
@ -448,7 +466,9 @@ class RoomRedactEventRestServlet(RestServlet):
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"sender": user.to_string(),
|
"sender": user.to_string(),
|
||||||
"redacts": event_id,
|
"redacts": event_id,
|
||||||
}
|
},
|
||||||
|
client=client,
|
||||||
|
txn_id=txn_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {"event_id": event.event_id}))
|
defer.returnValue((200, {"event_id": event.event_id}))
|
||||||
|
@ -462,23 +482,23 @@ class RoomRedactEventRestServlet(RestServlet):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
response = yield self.on_POST(request, room_id, event_id)
|
response = yield self.on_POST(request, room_id, event_id, txn_id)
|
||||||
|
|
||||||
self.txns.store_client_transaction(request, txn_id, response)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
class RoomTypingRestServlet(RestServlet):
|
class RoomTypingRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern(
|
PATTERN = client_path_pattern(
|
||||||
"/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$"
|
"/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$"
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_id, user_id):
|
def on_PUT(self, request, room_id, user_id):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
room_id = urllib.unquote(room_id)
|
room_id = urllib.unquote(room_id)
|
||||||
target_user = self.hs.parse_userid(urllib.unquote(user_id))
|
target_user = UserID.from_string(urllib.unquote(user_id))
|
||||||
|
|
||||||
content = _parse_json(request)
|
content = _parse_json(request)
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from base import RestServlet, client_path_pattern
|
from base import ClientV1RestServlet, client_path_pattern
|
||||||
|
|
||||||
|
|
||||||
import hmac
|
import hmac
|
||||||
|
@ -23,12 +23,12 @@ import hashlib
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
class VoipRestServlet(RestServlet):
|
class VoipRestServlet(ClientV1RestServlet):
|
||||||
PATTERN = client_path_pattern("/voip/turnServer$")
|
PATTERN = client_path_pattern("/voip/turnServer$")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
turnUris = self.hs.config.turn_uris
|
turnUris = self.hs.config.turn_uris
|
||||||
turnSecret = self.hs.config.turn_shared_secret
|
turnSecret = self.hs.config.turn_shared_secret
|
29
synapse/rest/client/v2_alpha/__init__.py
Normal file
29
synapse/rest/client/v2_alpha/__init__.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from synapse.http.server import JsonResource
|
||||||
|
|
||||||
|
|
||||||
|
class ClientV2AlphaRestResource(JsonResource):
|
||||||
|
"""A resource for version 2 alpha of the matrix client API."""
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
JsonResource.__init__(self)
|
||||||
|
self.register_servlets(self, hs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register_servlets(client_resource, hs):
|
||||||
|
pass
|
38
synapse/rest/client/v2_alpha/_base.py
Normal file
38
synapse/rest/client/v2_alpha/_base.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""This module contains base REST classes for constructing client v1 servlets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from synapse.api.urls import CLIENT_V2_ALPHA_PREFIX
|
||||||
|
import re
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def client_v2_pattern(path_regex):
|
||||||
|
"""Creates a regex compiled client path with the correct client path
|
||||||
|
prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path_regex (str): The regex string to match. This should NOT have a ^
|
||||||
|
as this will be prefixed.
|
||||||
|
Returns:
|
||||||
|
SRE_Pattern
|
||||||
|
"""
|
||||||
|
return re.compile("^" + CLIENT_V2_ALPHA_PREFIX + path_regex)
|
|
@ -66,7 +66,7 @@ class ContentRepoResource(resource.Resource):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def map_request_to_name(self, request):
|
def map_request_to_name(self, request):
|
||||||
# auth the user
|
# auth the user
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
# namespace all file uploads on the user
|
# namespace all file uploads on the user
|
||||||
prefix = base64.urlsafe_b64encode(
|
prefix = base64.urlsafe_b64encode(
|
|
@ -42,7 +42,7 @@ class UploadResource(BaseMediaResource):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _async_render_POST(self, request):
|
def _async_render_POST(self, request):
|
||||||
try:
|
try:
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
auth_user, client = yield self.auth.get_user_by_req(request)
|
||||||
# TODO: The checks here are a bit late. The content will have
|
# TODO: The checks here are a bit late. The content will have
|
||||||
# already been uploaded to a tmp file at this point
|
# already been uploaded to a tmp file at this point
|
||||||
content_length = request.getHeader("Content-Length")
|
content_length = request.getHeader("Content-Length")
|
|
@ -20,20 +20,18 @@
|
||||||
|
|
||||||
# Imports required for the default HomeServer() implementation
|
# Imports required for the default HomeServer() implementation
|
||||||
from synapse.federation import initialize_http_replication
|
from synapse.federation import initialize_http_replication
|
||||||
from synapse.events.utils import serialize_event
|
|
||||||
from synapse.notifier import Notifier
|
from synapse.notifier import Notifier
|
||||||
from synapse.api.auth import Auth
|
from synapse.api.auth import Auth
|
||||||
from synapse.handlers import Handlers
|
from synapse.handlers import Handlers
|
||||||
from synapse.rest import RestServletFactory
|
|
||||||
from synapse.state import StateHandler
|
from synapse.state import StateHandler
|
||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
from synapse.types import UserID, RoomAlias, RoomID, EventID
|
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
from synapse.util.distributor import Distributor
|
from synapse.util.distributor import Distributor
|
||||||
from synapse.util.lockutils import LockManager
|
from synapse.util.lockutils import LockManager
|
||||||
from synapse.streams.events import EventSources
|
from synapse.streams.events import EventSources
|
||||||
from synapse.api.ratelimiting import Ratelimiter
|
from synapse.api.ratelimiting import Ratelimiter
|
||||||
from synapse.crypto.keyring import Keyring
|
from synapse.crypto.keyring import Keyring
|
||||||
|
from synapse.push.pusherpool import PusherPool
|
||||||
from synapse.events.builder import EventBuilderFactory
|
from synapse.events.builder import EventBuilderFactory
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,6 +70,7 @@ class BaseHomeServer(object):
|
||||||
'notifier',
|
'notifier',
|
||||||
'distributor',
|
'distributor',
|
||||||
'resource_for_client',
|
'resource_for_client',
|
||||||
|
'resource_for_client_v2_alpha',
|
||||||
'resource_for_federation',
|
'resource_for_federation',
|
||||||
'resource_for_web_client',
|
'resource_for_web_client',
|
||||||
'resource_for_content_repo',
|
'resource_for_content_repo',
|
||||||
|
@ -80,6 +79,7 @@ class BaseHomeServer(object):
|
||||||
'event_sources',
|
'event_sources',
|
||||||
'ratelimiter',
|
'ratelimiter',
|
||||||
'keyring',
|
'keyring',
|
||||||
|
'pusherpool',
|
||||||
'event_builder_factory',
|
'event_builder_factory',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -125,33 +125,6 @@ class BaseHomeServer(object):
|
||||||
|
|
||||||
setattr(BaseHomeServer, "get_%s" % (depname), _get)
|
setattr(BaseHomeServer, "get_%s" % (depname), _get)
|
||||||
|
|
||||||
# TODO: Why are these parse_ methods so high up along with other globals?
|
|
||||||
# Surely these should be in a util package or in the api package?
|
|
||||||
|
|
||||||
# Other utility methods
|
|
||||||
def parse_userid(self, s):
|
|
||||||
"""Parse the string given by 's' as a User ID and return a UserID
|
|
||||||
object."""
|
|
||||||
return UserID.from_string(s)
|
|
||||||
|
|
||||||
def parse_roomalias(self, s):
|
|
||||||
"""Parse the string given by 's' as a Room Alias and return a RoomAlias
|
|
||||||
object."""
|
|
||||||
return RoomAlias.from_string(s)
|
|
||||||
|
|
||||||
def parse_roomid(self, s):
|
|
||||||
"""Parse the string given by 's' as a Room ID and return a RoomID
|
|
||||||
object."""
|
|
||||||
return RoomID.from_string(s)
|
|
||||||
|
|
||||||
def parse_eventid(self, s):
|
|
||||||
"""Parse the string given by 's' as a Event ID and return a EventID
|
|
||||||
object."""
|
|
||||||
return EventID.from_string(s)
|
|
||||||
|
|
||||||
def serialize_event(self, e, as_client_event=True):
|
|
||||||
return serialize_event(self, e, as_client_event)
|
|
||||||
|
|
||||||
def get_ip_from_request(self, request):
|
def get_ip_from_request(self, request):
|
||||||
# May be an X-Forwarding-For header depending on config
|
# May be an X-Forwarding-For header depending on config
|
||||||
ip_addr = request.getClientIP()
|
ip_addr = request.getClientIP()
|
||||||
|
@ -203,9 +176,6 @@ class HomeServer(BaseHomeServer):
|
||||||
def build_auth(self):
|
def build_auth(self):
|
||||||
return Auth(self)
|
return Auth(self)
|
||||||
|
|
||||||
def build_rest_servlet_factory(self):
|
|
||||||
return RestServletFactory(self)
|
|
||||||
|
|
||||||
def build_state_handler(self):
|
def build_state_handler(self):
|
||||||
return StateHandler(self)
|
return StateHandler(self)
|
||||||
|
|
||||||
|
@ -230,8 +200,5 @@ class HomeServer(BaseHomeServer):
|
||||||
hostname=self.hostname,
|
hostname=self.hostname,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_servlets(self):
|
def build_pusherpool(self):
|
||||||
""" Register all servlets associated with this HomeServer.
|
return PusherPool(self)
|
||||||
"""
|
|
||||||
# Simply building the ServletFactory is sufficient to have it register
|
|
||||||
self.get_rest_servlet_factory()
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ from .stream import StreamStore
|
||||||
from .transactions import TransactionStore
|
from .transactions import TransactionStore
|
||||||
from .keys import KeyStore
|
from .keys import KeyStore
|
||||||
from .event_federation import EventFederationStore
|
from .event_federation import EventFederationStore
|
||||||
|
from .pusher import PusherStore
|
||||||
|
from .push_rule import PushRuleStore
|
||||||
from .media_repository import MediaRepositoryStore
|
from .media_repository import MediaRepositoryStore
|
||||||
from .rejections import RejectionsStore
|
from .rejections import RejectionsStore
|
||||||
|
|
||||||
|
@ -61,6 +63,7 @@ SCHEMAS = [
|
||||||
"state",
|
"state",
|
||||||
"event_edges",
|
"event_edges",
|
||||||
"event_signatures",
|
"event_signatures",
|
||||||
|
"pusher",
|
||||||
"media_repository",
|
"media_repository",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -84,6 +87,8 @@ class DataStore(RoomMemberStore, RoomStore,
|
||||||
EventFederationStore,
|
EventFederationStore,
|
||||||
MediaRepositoryStore,
|
MediaRepositoryStore,
|
||||||
RejectionsStore,
|
RejectionsStore,
|
||||||
|
PusherStore,
|
||||||
|
PushRuleStore
|
||||||
):
|
):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
@ -386,6 +391,41 @@ class DataStore(RoomMemberStore, RoomStore,
|
||||||
events = yield self._parse_events(results)
|
events = yield self._parse_events(results)
|
||||||
defer.returnValue(events)
|
defer.returnValue(events)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_room_name_and_aliases(self, room_id):
|
||||||
|
del_sql = (
|
||||||
|
"SELECT event_id FROM redactions WHERE redacts = e.event_id "
|
||||||
|
"LIMIT 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
sql = (
|
||||||
|
"SELECT e.*, (%(redacted)s) AS redacted FROM events as e "
|
||||||
|
"INNER JOIN current_state_events as c ON e.event_id = c.event_id "
|
||||||
|
"INNER JOIN state_events as s ON e.event_id = s.event_id "
|
||||||
|
"WHERE c.room_id = ? "
|
||||||
|
) % {
|
||||||
|
"redacted": del_sql,
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += " AND ((s.type = 'm.room.name' AND s.state_key = '')"
|
||||||
|
sql += " OR s.type = 'm.room.aliases')"
|
||||||
|
args = (room_id,)
|
||||||
|
|
||||||
|
results = yield self._execute_and_decode(sql, *args)
|
||||||
|
|
||||||
|
events = yield self._parse_events(results)
|
||||||
|
|
||||||
|
name = None
|
||||||
|
aliases = []
|
||||||
|
|
||||||
|
for e in events:
|
||||||
|
if e.type == 'm.room.name':
|
||||||
|
name = e.content['name']
|
||||||
|
elif e.type == 'm.room.aliases':
|
||||||
|
aliases.extend(e.content['aliases'])
|
||||||
|
|
||||||
|
defer.returnValue((name, aliases))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_min_token(self):
|
def _get_min_token(self):
|
||||||
row = yield self._execute(
|
row = yield self._execute(
|
||||||
|
|
|
@ -193,6 +193,50 @@ class SQLBaseStore(object):
|
||||||
txn.execute(sql, values.values())
|
txn.execute(sql, values.values())
|
||||||
return txn.lastrowid
|
return txn.lastrowid
|
||||||
|
|
||||||
|
def _simple_upsert(self, table, keyvalues, values):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
table (str): The table to upsert into
|
||||||
|
keyvalues (dict): The unique key tables and their new values
|
||||||
|
values (dict): The nonunique columns and their new values
|
||||||
|
Returns: A deferred
|
||||||
|
"""
|
||||||
|
return self.runInteraction(
|
||||||
|
"_simple_upsert",
|
||||||
|
self._simple_upsert_txn, table, keyvalues, values
|
||||||
|
)
|
||||||
|
|
||||||
|
def _simple_upsert_txn(self, txn, table, keyvalues, values):
|
||||||
|
# Try to update
|
||||||
|
sql = "UPDATE %s SET %s WHERE %s" % (
|
||||||
|
table,
|
||||||
|
", ".join("%s = ?" % (k,) for k in values),
|
||||||
|
" AND ".join("%s = ?" % (k,) for k in keyvalues)
|
||||||
|
)
|
||||||
|
sqlargs = values.values() + keyvalues.values()
|
||||||
|
logger.debug(
|
||||||
|
"[SQL] %s Args=%s",
|
||||||
|
sql, sqlargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
txn.execute(sql, sqlargs)
|
||||||
|
if txn.rowcount == 0:
|
||||||
|
# We didn't update and rows so insert a new one
|
||||||
|
allvalues = {}
|
||||||
|
allvalues.update(keyvalues)
|
||||||
|
allvalues.update(values)
|
||||||
|
|
||||||
|
sql = "INSERT INTO %s (%s) VALUES (%s)" % (
|
||||||
|
table,
|
||||||
|
", ".join(k for k in allvalues),
|
||||||
|
", ".join("?" for _ in allvalues)
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
"[SQL] %s Args=%s",
|
||||||
|
sql, keyvalues.values(),
|
||||||
|
)
|
||||||
|
txn.execute(sql, allvalues.values())
|
||||||
|
|
||||||
def _simple_select_one(self, table, keyvalues, retcols,
|
def _simple_select_one(self, table, keyvalues, retcols,
|
||||||
allow_none=False):
|
allow_none=False):
|
||||||
"""Executes a SELECT query on the named table, which is expected to
|
"""Executes a SELECT query on the named table, which is expected to
|
||||||
|
@ -344,8 +388,8 @@ class SQLBaseStore(object):
|
||||||
if updatevalues:
|
if updatevalues:
|
||||||
update_sql = "UPDATE %s SET %s WHERE %s" % (
|
update_sql = "UPDATE %s SET %s WHERE %s" % (
|
||||||
table,
|
table,
|
||||||
", ".join("%s = ?" % (k) for k in updatevalues),
|
", ".join("%s = ?" % (k,) for k in updatevalues),
|
||||||
" AND ".join("%s = ?" % (k) for k in keyvalues)
|
" AND ".join("%s = ?" % (k,) for k in keyvalues)
|
||||||
)
|
)
|
||||||
|
|
||||||
def func(txn):
|
def func(txn):
|
||||||
|
|
213
synapse/storage/push_rule.py
Normal file
213
synapse/storage/push_rule.py
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from ._base import SQLBaseStore, Table
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PushRuleStore(SQLBaseStore):
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_push_rules_for_user_name(self, user_name):
|
||||||
|
sql = (
|
||||||
|
"SELECT "+",".join(PushRuleTable.fields)+" "
|
||||||
|
"FROM "+PushRuleTable.table_name+" "
|
||||||
|
"WHERE user_name = ? "
|
||||||
|
"ORDER BY priority_class DESC, priority DESC"
|
||||||
|
)
|
||||||
|
rows = yield self._execute(None, sql, user_name)
|
||||||
|
|
||||||
|
dicts = []
|
||||||
|
for r in rows:
|
||||||
|
d = {}
|
||||||
|
for i, f in enumerate(PushRuleTable.fields):
|
||||||
|
d[f] = r[i]
|
||||||
|
dicts.append(d)
|
||||||
|
|
||||||
|
defer.returnValue(dicts)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def add_push_rule(self, before, after, **kwargs):
|
||||||
|
vals = copy.copy(kwargs)
|
||||||
|
if 'conditions' in vals:
|
||||||
|
vals['conditions'] = json.dumps(vals['conditions'])
|
||||||
|
if 'actions' in vals:
|
||||||
|
vals['actions'] = json.dumps(vals['actions'])
|
||||||
|
# we could check the rest of the keys are valid column names
|
||||||
|
# but sqlite will do that anyway so I think it's just pointless.
|
||||||
|
if 'id' in vals:
|
||||||
|
del vals['id']
|
||||||
|
|
||||||
|
if before or after:
|
||||||
|
ret = yield self.runInteraction(
|
||||||
|
"_add_push_rule_relative_txn",
|
||||||
|
self._add_push_rule_relative_txn,
|
||||||
|
before=before,
|
||||||
|
after=after,
|
||||||
|
**vals
|
||||||
|
)
|
||||||
|
defer.returnValue(ret)
|
||||||
|
else:
|
||||||
|
ret = yield self.runInteraction(
|
||||||
|
"_add_push_rule_highest_priority_txn",
|
||||||
|
self._add_push_rule_highest_priority_txn,
|
||||||
|
**vals
|
||||||
|
)
|
||||||
|
defer.returnValue(ret)
|
||||||
|
|
||||||
|
def _add_push_rule_relative_txn(self, txn, user_name, **kwargs):
|
||||||
|
after = None
|
||||||
|
relative_to_rule = None
|
||||||
|
if 'after' in kwargs and kwargs['after']:
|
||||||
|
after = kwargs['after']
|
||||||
|
relative_to_rule = after
|
||||||
|
if 'before' in kwargs and kwargs['before']:
|
||||||
|
relative_to_rule = kwargs['before']
|
||||||
|
|
||||||
|
# get the priority of the rule we're inserting after/before
|
||||||
|
sql = (
|
||||||
|
"SELECT priority_class, priority FROM ? "
|
||||||
|
"WHERE user_name = ? and rule_id = ?" % (PushRuleTable.table_name,)
|
||||||
|
)
|
||||||
|
txn.execute(sql, (user_name, relative_to_rule))
|
||||||
|
res = txn.fetchall()
|
||||||
|
if not res:
|
||||||
|
raise RuleNotFoundException("before/after rule not found: %s" % (relative_to_rule))
|
||||||
|
priority_class, base_rule_priority = res[0]
|
||||||
|
|
||||||
|
if 'priority_class' in kwargs and kwargs['priority_class'] != priority_class:
|
||||||
|
raise InconsistentRuleException(
|
||||||
|
"Given priority class does not match class of relative rule"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_rule = copy.copy(kwargs)
|
||||||
|
if 'before' in new_rule:
|
||||||
|
del new_rule['before']
|
||||||
|
if 'after' in new_rule:
|
||||||
|
del new_rule['after']
|
||||||
|
new_rule['priority_class'] = priority_class
|
||||||
|
new_rule['user_name'] = user_name
|
||||||
|
|
||||||
|
# check if the priority before/after is free
|
||||||
|
new_rule_priority = base_rule_priority
|
||||||
|
if after:
|
||||||
|
new_rule_priority -= 1
|
||||||
|
else:
|
||||||
|
new_rule_priority += 1
|
||||||
|
|
||||||
|
new_rule['priority'] = new_rule_priority
|
||||||
|
|
||||||
|
sql = (
|
||||||
|
"SELECT COUNT(*) FROM " + PushRuleTable.table_name +
|
||||||
|
" WHERE user_name = ? AND priority_class = ? AND priority = ?"
|
||||||
|
)
|
||||||
|
txn.execute(sql, (user_name, priority_class, new_rule_priority))
|
||||||
|
res = txn.fetchall()
|
||||||
|
num_conflicting = res[0][0]
|
||||||
|
|
||||||
|
# if there are conflicting rules, bump everything
|
||||||
|
if num_conflicting:
|
||||||
|
sql = "UPDATE "+PushRuleTable.table_name+" SET priority = priority "
|
||||||
|
if after:
|
||||||
|
sql += "-1"
|
||||||
|
else:
|
||||||
|
sql += "+1"
|
||||||
|
sql += " WHERE user_name = ? AND priority_class = ? AND priority "
|
||||||
|
if after:
|
||||||
|
sql += "<= ?"
|
||||||
|
else:
|
||||||
|
sql += ">= ?"
|
||||||
|
|
||||||
|
txn.execute(sql, (user_name, priority_class, new_rule_priority))
|
||||||
|
|
||||||
|
# now insert the new rule
|
||||||
|
sql = "INSERT OR REPLACE INTO "+PushRuleTable.table_name+" ("
|
||||||
|
sql += ",".join(new_rule.keys())+") VALUES ("
|
||||||
|
sql += ", ".join(["?" for _ in new_rule.keys()])+")"
|
||||||
|
|
||||||
|
txn.execute(sql, new_rule.values())
|
||||||
|
|
||||||
|
def _add_push_rule_highest_priority_txn(self, txn, user_name,
|
||||||
|
priority_class, **kwargs):
|
||||||
|
# find the highest priority rule in that class
|
||||||
|
sql = (
|
||||||
|
"SELECT COUNT(*), MAX(priority) FROM " + PushRuleTable.table_name +
|
||||||
|
" WHERE user_name = ? and priority_class = ?"
|
||||||
|
)
|
||||||
|
txn.execute(sql, (user_name, priority_class))
|
||||||
|
res = txn.fetchall()
|
||||||
|
(how_many, highest_prio) = res[0]
|
||||||
|
|
||||||
|
new_prio = 0
|
||||||
|
if how_many > 0:
|
||||||
|
new_prio = highest_prio + 1
|
||||||
|
|
||||||
|
# and insert the new rule
|
||||||
|
new_rule = copy.copy(kwargs)
|
||||||
|
if 'id' in new_rule:
|
||||||
|
del new_rule['id']
|
||||||
|
new_rule['user_name'] = user_name
|
||||||
|
new_rule['priority_class'] = priority_class
|
||||||
|
new_rule['priority'] = new_prio
|
||||||
|
|
||||||
|
sql = "INSERT OR REPLACE INTO "+PushRuleTable.table_name+" ("
|
||||||
|
sql += ",".join(new_rule.keys())+") VALUES ("
|
||||||
|
sql += ", ".join(["?" for _ in new_rule.keys()])+")"
|
||||||
|
|
||||||
|
txn.execute(sql, new_rule.values())
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def delete_push_rule(self, user_name, rule_id, **kwargs):
|
||||||
|
"""
|
||||||
|
Delete a push rule. Args specify the row to be deleted and can be
|
||||||
|
any of the columns in the push_rule table, but below are the
|
||||||
|
standard ones
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name (str): The matrix ID of the push rule owner
|
||||||
|
rule_id (str): The rule_id of the rule to be deleted
|
||||||
|
"""
|
||||||
|
yield self._simple_delete_one(PushRuleTable.table_name, kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RuleNotFoundException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InconsistentRuleException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PushRuleTable(Table):
|
||||||
|
table_name = "push_rules"
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"user_name",
|
||||||
|
"rule_id",
|
||||||
|
"priority_class",
|
||||||
|
"priority",
|
||||||
|
"conditions",
|
||||||
|
"actions",
|
||||||
|
]
|
||||||
|
|
||||||
|
EntryType = collections.namedtuple("PushRuleEntry", fields)
|
173
synapse/storage/pusher.py
Normal file
173
synapse/storage/pusher.py
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from ._base import SQLBaseStore, Table
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.errors import StoreError
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PusherStore(SQLBaseStore):
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_pushers_by_app_id_and_pushkey(self, app_id_and_pushkey):
|
||||||
|
sql = (
|
||||||
|
"SELECT id, user_name, kind, instance_handle, app_id,"
|
||||||
|
"app_display_name, device_display_name, pushkey, ts, data, "
|
||||||
|
"last_token, last_success, failing_since "
|
||||||
|
"FROM pushers "
|
||||||
|
"WHERE app_id = ? AND pushkey = ?"
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = yield self._execute(
|
||||||
|
None, sql, app_id_and_pushkey[0], app_id_and_pushkey[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
ret = [
|
||||||
|
{
|
||||||
|
"id": r[0],
|
||||||
|
"user_name": r[1],
|
||||||
|
"kind": r[2],
|
||||||
|
"instance_handle": r[3],
|
||||||
|
"app_id": r[4],
|
||||||
|
"app_display_name": r[5],
|
||||||
|
"device_display_name": r[6],
|
||||||
|
"pushkey": r[7],
|
||||||
|
"pushkey_ts": r[8],
|
||||||
|
"data": r[9],
|
||||||
|
"last_token": r[10],
|
||||||
|
"last_success": r[11],
|
||||||
|
"failing_since": r[12]
|
||||||
|
}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
defer.returnValue(ret[0])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_all_pushers(self):
|
||||||
|
sql = (
|
||||||
|
"SELECT id, user_name, kind, instance_handle, app_id,"
|
||||||
|
"app_display_name, device_display_name, pushkey, ts, data, "
|
||||||
|
"last_token, last_success, failing_since "
|
||||||
|
"FROM pushers"
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = yield self._execute(None, sql)
|
||||||
|
|
||||||
|
ret = [
|
||||||
|
{
|
||||||
|
"id": r[0],
|
||||||
|
"user_name": r[1],
|
||||||
|
"kind": r[2],
|
||||||
|
"instance_handle": r[3],
|
||||||
|
"app_id": r[4],
|
||||||
|
"app_display_name": r[5],
|
||||||
|
"device_display_name": r[6],
|
||||||
|
"pushkey": r[7],
|
||||||
|
"pushkey_ts": r[8],
|
||||||
|
"data": r[9],
|
||||||
|
"last_token": r[10],
|
||||||
|
"last_success": r[11],
|
||||||
|
"failing_since": r[12]
|
||||||
|
}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
defer.returnValue(ret)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def add_pusher(self, user_name, instance_handle, kind, app_id,
|
||||||
|
app_display_name, device_display_name,
|
||||||
|
pushkey, pushkey_ts, lang, data):
|
||||||
|
try:
|
||||||
|
yield self._simple_upsert(
|
||||||
|
PushersTable.table_name,
|
||||||
|
dict(
|
||||||
|
app_id=app_id,
|
||||||
|
pushkey=pushkey,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
user_name=user_name,
|
||||||
|
kind=kind,
|
||||||
|
instance_handle=instance_handle,
|
||||||
|
app_display_name=app_display_name,
|
||||||
|
device_display_name=device_display_name,
|
||||||
|
ts=pushkey_ts,
|
||||||
|
lang=lang,
|
||||||
|
data=data
|
||||||
|
))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("create_pusher with failed: %s", e)
|
||||||
|
raise StoreError(500, "Problem creating pusher.")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def delete_pusher_by_app_id_pushkey(self, app_id, pushkey):
|
||||||
|
yield self._simple_delete_one(
|
||||||
|
PushersTable.table_name,
|
||||||
|
dict(app_id=app_id, pushkey=pushkey)
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def update_pusher_last_token(self, user_name, pushkey, last_token):
|
||||||
|
yield self._simple_update_one(
|
||||||
|
PushersTable.table_name,
|
||||||
|
{'user_name': user_name, 'pushkey': pushkey},
|
||||||
|
{'last_token': last_token}
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def update_pusher_last_token_and_success(self, user_name, pushkey,
|
||||||
|
last_token, last_success):
|
||||||
|
yield self._simple_update_one(
|
||||||
|
PushersTable.table_name,
|
||||||
|
{'user_name': user_name, 'pushkey': pushkey},
|
||||||
|
{'last_token': last_token, 'last_success': last_success}
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def update_pusher_failing_since(self, user_name, pushkey, failing_since):
|
||||||
|
yield self._simple_update_one(
|
||||||
|
PushersTable.table_name,
|
||||||
|
{'user_name': user_name, 'pushkey': pushkey},
|
||||||
|
{'failing_since': failing_since}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PushersTable(Table):
|
||||||
|
table_name = "pushers"
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"user_name",
|
||||||
|
"kind",
|
||||||
|
"instance_handle",
|
||||||
|
"app_id",
|
||||||
|
"app_display_name",
|
||||||
|
"device_display_name",
|
||||||
|
"pushkey",
|
||||||
|
"pushkey_ts",
|
||||||
|
"data",
|
||||||
|
"last_token",
|
||||||
|
"last_success",
|
||||||
|
"failing_since"
|
||||||
|
]
|
||||||
|
|
||||||
|
EntryType = collections.namedtuple("PusherEntry", fields)
|
|
@ -122,7 +122,8 @@ class RegistrationStore(SQLBaseStore):
|
||||||
|
|
||||||
def _query_for_auth(self, txn, token):
|
def _query_for_auth(self, txn, token):
|
||||||
sql = (
|
sql = (
|
||||||
"SELECT users.name, users.admin, access_tokens.device_id"
|
"SELECT users.name, users.admin,"
|
||||||
|
" access_tokens.device_id, access_tokens.id as token_id"
|
||||||
" FROM users"
|
" FROM users"
|
||||||
" INNER JOIN access_tokens on users.id = access_tokens.user_id"
|
" INNER JOIN access_tokens on users.id = access_tokens.user_id"
|
||||||
" WHERE token = ?"
|
" WHERE token = ?"
|
||||||
|
|
|
@ -20,6 +20,7 @@ from collections import namedtuple
|
||||||
from ._base import SQLBaseStore
|
from ._base import SQLBaseStore
|
||||||
|
|
||||||
from synapse.api.constants import Membership
|
from synapse.api.constants import Membership
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ class RoomMemberStore(SQLBaseStore):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
target_user_id = event.state_key
|
target_user_id = event.state_key
|
||||||
domain = self.hs.parse_userid(target_user_id).domain
|
domain = UserID.from_string(target_user_id).domain
|
||||||
except:
|
except:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to parse target_user_id=%s", target_user_id
|
"Failed to parse target_user_id=%s", target_user_id
|
||||||
|
@ -84,7 +85,7 @@ class RoomMemberStore(SQLBaseStore):
|
||||||
for e in member_events:
|
for e in member_events:
|
||||||
try:
|
try:
|
||||||
joined_domains.add(
|
joined_domains.add(
|
||||||
self.hs.parse_userid(e.state_key).domain
|
UserID.from_string(e.state_key).domain
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
# FIXME: How do we deal with invalid user ids in the db?
|
# FIXME: How do we deal with invalid user ids in the db?
|
||||||
|
|
|
@ -19,3 +19,36 @@ CREATE TABLE IF NOT EXISTS rejections(
|
||||||
last_check TEXT NOT NULL,
|
last_check TEXT NOT NULL,
|
||||||
CONSTRAINT ev_id UNIQUE (event_id) ON CONFLICT REPLACE
|
CONSTRAINT ev_id UNIQUE (event_id) ON CONFLICT REPLACE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Push notification endpoints that users have configured
|
||||||
|
CREATE TABLE IF NOT EXISTS pushers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_name TEXT NOT NULL,
|
||||||
|
instance_handle varchar(32) NOT NULL,
|
||||||
|
kind varchar(8) NOT NULL,
|
||||||
|
app_id varchar(64) NOT NULL,
|
||||||
|
app_display_name varchar(64) NOT NULL,
|
||||||
|
device_display_name varchar(128) NOT NULL,
|
||||||
|
pushkey blob NOT NULL,
|
||||||
|
ts BIGINT NOT NULL,
|
||||||
|
lang varchar(8),
|
||||||
|
data blob,
|
||||||
|
last_token TEXT,
|
||||||
|
last_success BIGINT,
|
||||||
|
failing_since BIGINT,
|
||||||
|
FOREIGN KEY(user_name) REFERENCES users(name),
|
||||||
|
UNIQUE (app_id, pushkey)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS push_rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_name TEXT NOT NULL,
|
||||||
|
rule_id TEXT NOT NULL,
|
||||||
|
priority_class TINYINT NOT NULL,
|
||||||
|
priority INTEGER NOT NULL DEFAULT 0,
|
||||||
|
conditions TEXT NOT NULL,
|
||||||
|
actions TEXT NOT NULL,
|
||||||
|
UNIQUE(user_name, rule_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS push_rules_user_name on push_rules (user_name);
|
||||||
|
|
46
synapse/storage/schema/pusher.sql
Normal file
46
synapse/storage/schema/pusher.sql
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* Copyright 2014 OpenMarket Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-- Push notification endpoints that users have configured
|
||||||
|
CREATE TABLE IF NOT EXISTS pushers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_name TEXT NOT NULL,
|
||||||
|
instance_handle varchar(32) NOT NULL,
|
||||||
|
kind varchar(8) NOT NULL,
|
||||||
|
app_id varchar(64) NOT NULL,
|
||||||
|
app_display_name varchar(64) NOT NULL,
|
||||||
|
device_display_name varchar(128) NOT NULL,
|
||||||
|
pushkey blob NOT NULL,
|
||||||
|
ts BIGINT NOT NULL,
|
||||||
|
lang varchar(8),
|
||||||
|
data blob,
|
||||||
|
last_token TEXT,
|
||||||
|
last_success BIGINT,
|
||||||
|
failing_since BIGINT,
|
||||||
|
FOREIGN KEY(user_name) REFERENCES users(name),
|
||||||
|
UNIQUE (app_id, pushkey)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS push_rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_name TEXT NOT NULL,
|
||||||
|
rule_id TEXT NOT NULL,
|
||||||
|
priority_class TINYINT NOT NULL,
|
||||||
|
priority INTEGER NOT NULL DEFAULT 0,
|
||||||
|
conditions TEXT NOT NULL,
|
||||||
|
actions TEXT NOT NULL,
|
||||||
|
UNIQUE(user_name, rule_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS push_rules_user_name on push_rules (user_name);
|
|
@ -82,10 +82,10 @@ class _StreamToken(namedtuple("_StreamToken", "topological stream")):
|
||||||
def parse(cls, string):
|
def parse(cls, string):
|
||||||
try:
|
try:
|
||||||
if string[0] == 's':
|
if string[0] == 's':
|
||||||
return cls(None, int(string[1:]))
|
return cls(topological=None, stream=int(string[1:]))
|
||||||
if string[0] == 't':
|
if string[0] == 't':
|
||||||
parts = string[1:].split('-', 1)
|
parts = string[1:].split('-', 1)
|
||||||
return cls(int(parts[1]), int(parts[0]))
|
return cls(topological=int(parts[0]), stream=int(parts[1]))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise SynapseError(400, "Invalid token %r" % (string,))
|
raise SynapseError(400, "Invalid token %r" % (string,))
|
||||||
|
@ -94,7 +94,7 @@ class _StreamToken(namedtuple("_StreamToken", "topological stream")):
|
||||||
def parse_stream_token(cls, string):
|
def parse_stream_token(cls, string):
|
||||||
try:
|
try:
|
||||||
if string[0] == 's':
|
if string[0] == 's':
|
||||||
return cls(None, int(string[1:]))
|
return cls(topological=None, stream=int(string[1:]))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise SynapseError(400, "Invalid token %r" % (string,))
|
raise SynapseError(400, "Invalid token %r" % (string,))
|
||||||
|
|
|
@ -119,3 +119,6 @@ class StreamToken(
|
||||||
d = self._asdict()
|
d = self._asdict()
|
||||||
d[key] = new_value
|
d[key] = new_value
|
||||||
return StreamToken(**d)
|
return StreamToken(**d)
|
||||||
|
|
||||||
|
|
||||||
|
ClientInfo = namedtuple("ClientInfo", ("device_id", "token_id"))
|
||||||
|
|
|
@ -21,6 +21,7 @@ from mock import Mock
|
||||||
|
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.handlers.directory import DirectoryHandler
|
from synapse.handlers.directory import DirectoryHandler
|
||||||
|
from synapse.types import RoomAlias
|
||||||
|
|
||||||
from tests.utils import SQLiteMemoryDbPool, MockKey
|
from tests.utils import SQLiteMemoryDbPool, MockKey
|
||||||
|
|
||||||
|
@ -65,9 +66,9 @@ class DirectoryTestCase(unittest.TestCase):
|
||||||
|
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
|
|
||||||
self.my_room = hs.parse_roomalias("#my-room:test")
|
self.my_room = RoomAlias.from_string("#my-room:test")
|
||||||
self.your_room = hs.parse_roomalias("#your-room:test")
|
self.your_room = RoomAlias.from_string("#your-room:test")
|
||||||
self.remote_room = hs.parse_roomalias("#another:remote")
|
self.remote_room = RoomAlias.from_string("#another:remote")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_local_association(self):
|
def test_get_local_association(self):
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue