2
2
3
3
import asyncio
4
4
import json
5
+ import uuid
5
6
from typing import Any , List , Dict , TYPE_CHECKING , NamedTuple
6
7
from realtime .message import *
7
8
14
15
class CallbackListener (NamedTuple ):
15
16
"""A tuple with `event` and `callback` """
16
17
event : str
18
+ ref : str
17
19
callback : Callback
18
20
19
21
20
22
class Channel :
21
23
"""
22
24
`Channel` is an abstraction for a topic listener for an existing socket connection.
23
25
Each Channel has its own topic and a list of event-callbacks that responds to messages.
26
+ A client can also send messages to a channel and register callback when expecting replies.
24
27
Should only be instantiated through `connection.Socket().set_channel(topic)`
25
28
Topic-Channel has a 1-many relationship.
26
29
"""
@@ -36,6 +39,8 @@ def __init__(self, socket: Socket, topic: str, params: Dict[str, Any] = {}) -> N
36
39
self .topic = topic
37
40
self .listeners : List [CallbackListener ] = []
38
41
self .joined = False
42
+ self .join_ref = str (uuid .uuid4 ())
43
+ self .join_msg_ref = str (uuid .uuid4 ())
39
44
40
45
def join (self ) -> Channel :
41
46
"""
@@ -56,7 +61,8 @@ async def _join(self) -> None:
56
61
join_req = dict (topic = self .topic , event = ChannelEvents .join ,
57
62
payload = {}, ref = None )
58
63
elif self .socket .version == 2 :
59
- join_req = [None , None , self .topic , ChannelEvents .join , {}]
64
+ #[join_reference, message_reference, topic_name, event_name, payload]
65
+ join_req = [self .join_ref , self .join_msg_ref , self .topic , ChannelEvents .join , {}]
60
66
61
67
try :
62
68
await self .socket .ws_connection .send (json .dumps (join_req ))
@@ -83,60 +89,64 @@ async def _leave(self) -> None:
83
89
join_req = dict (topic = self .topic , event = ChannelEvents .leave ,
84
90
payload = {}, ref = None )
85
91
elif self .socket .version == 2 :
86
- join_req = [None , None , self .topic , ChannelEvents .leave , {}]
92
+ join_req = [self . join_ref , None , self .topic , ChannelEvents .leave , {}]
87
93
88
94
try :
89
95
await self .socket .ws_connection .send (json .dumps (join_req ))
90
96
except Exception as e :
91
97
print (str (e )) # TODO: better error propagation
92
98
return
93
99
94
- def on (self , event : str , callback : Callback ) -> Channel :
100
+ def on (self , event : str , ref : str , callback : Callback ) -> Channel :
95
101
"""
96
102
:param event: A specific event will have a specific callback
103
+ :param ref: A specific reference that will have a specific callback
97
104
:param callback: Callback that takes msg payload as its first argument
98
105
:return: Channel
99
106
"""
100
- cl = CallbackListener (event = event , callback = callback )
107
+ cl = CallbackListener (event = event , ref = ref , callback = callback )
101
108
self .listeners .append (cl )
102
109
return self
103
110
104
- def off (self , event : str ) -> None :
111
+ def off (self , event : str , ref : str ) -> None :
105
112
"""
106
113
:param event: Stop responding to a certain event
114
+ :param event: Stop responding to a certain reference
107
115
:return: None
108
116
"""
109
117
self .listeners = [
110
- callback for callback in self .listeners if callback .event != event ]
118
+ callback for callback in self .listeners if ( callback .event != event and callback . ref != ref ) ]
111
119
112
- def send (self , event_name : str , payload :str ) -> None :
120
+ def send (self , event_name : str , payload :str , ref : uuid = str ( uuid . uuid4 ()) ) -> None :
113
121
"""
114
122
Wrapper for async def _send() to expose a non-async interface
115
123
Essentially gets the only event loop and attempt sending a payload
116
124
to a topic
117
125
:param event_name: The event_name: it must match the first argument of a handle_in function on the server channel module.
118
126
:param payload: The payload to be sent to the phoenix server
127
+ :param ref: The message reference that the server will use for replying - if none is set, generates the string repr of a uuidv4
119
128
:return: None
120
129
"""
121
130
loop = asyncio .get_event_loop () # TODO: replace with get_running_loop
122
- loop .run_until_complete (self ._send (event_name , payload ))
131
+ loop .run_until_complete (self ._send (event_name , payload , ref ))
123
132
return self
124
133
125
- async def _send (self , event_name : str , payload :str ) -> None :
134
+ async def _send (self , event_name : str , payload : str , ref : str ) -> None :
126
135
"""
127
136
Coroutine that attempts to join Phoenix Realtime server via a certain topic
128
137
:param event_name: The event_name: it must match the first argument of a handle_in function on the server channel module.
129
138
:param payload: The payload to be sent to the phoenix server
139
+ :param ref: The message reference that the server will use for replying
130
140
:return: None
131
141
"""
132
142
if self .socket .version == 1 :
133
143
msg = dict (topic = self .topic , event = event_name ,
134
144
payload = payload , ref = None )
135
145
elif self .socket .version == 2 :
136
- msg = [3 , 3 , self .topic , event_name , payload ]
146
+ msg = [None , ref , self .topic , event_name , payload ]
137
147
138
148
try :
139
149
await self .socket .ws_connection .send (json .dumps (msg ))
140
150
except Exception as e :
141
151
print (str (e )) # TODO: better error propagation
142
- return
152
+ return
0 commit comments