Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop to master #15

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Own files to ignore
post_events.py
gen_tokens.py
env_vars.env

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
2 changes: 2 additions & 0 deletions .slugignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
websocketimg.png
README.md
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: gunicorn -b 0.0.0.0:$PORT -k gevent run_app:app
web: python -m asyncio -m websockets -m websocket_service
2 changes: 0 additions & 2 deletions env_vars.env

This file was deleted.

2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PyJWT==2.6.0
PyJWT==2.7.0
requests==2.28.2
websockets==11.0.2
python-dotenv==1.0.0
Expand Down
79 changes: 57 additions & 22 deletions websocket_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@
except ImportError as err_imp:
print(f"The following import error occurred: {err_imp}")


class WebSocketHandler:
def __init__(self, websocket):
self.websocket = websocket
self.username = None
self.heartbeat_task = None

async def send_heartbeat(self):
async def send_heartbeat(self) -> None:
"""
Method to send a heartbeat to the client.

Returns:
None
"""

try:
await self.websocket.send(json.dumps({
"action": "heartbeat"
Expand All @@ -27,13 +35,16 @@ async def send_heartbeat(self):
await self.websocket.close()

# Función para enviar mensajes a otros usuarios
async def send_message(self, recipient:str, message:dict[str, str]) -> None:
async def send_message(self, recipient: str, message: dict[str, str]) -> None:
"""
Method to send a message to a recipient.

:param recipient: The recipient username.
:param message: The message to send in dict format.
:return: None
Args:
recipient (str): The recipient username.
message (dict[str, str]): The message to be sent.

Returns:
None
"""
if recipient in CONNECTED_USERS:
recipient_socket = CONNECTED_USERS[recipient]
Expand All @@ -56,27 +67,38 @@ async def authenticate(self, token: str) -> bool:
"""
Method to authenticate the user.

:param token: The user token.
:return: True if the token is valid, False otherwise.
Args:
token (str): The user token.

Returns:
bool: True if the user is authenticated, False otherwise.
"""
try:
# Verify the user token.
payload = jwt.decode(token, "B7PwGjhYohg", algorithms=["HS256"])
self.username = payload["sub"]
secret_jwt = os.getenv("SECRET_JWT")
if secret_jwt is None:
raise Exception("The JWT secret is not defined.")
payload = jwt.decode(token, secret_jwt, algorithms=["HS256"])
self.username = payload["username"]
CONNECTED_USERS[self.username] = self.websocket
print(f"Connected user: {self.username}.")
return True
except:
# Si el token no es válido.
print(traceback.format_exc())
print("Invalid token")
return False

async def subscribe(self, topic_name:str,user:str|None=None) -> bool:
async def subscribe(self, topic_name: str, user: str|None=None) -> bool:
"""
Method to subscribe the user to a public topic.

:param topic_name: The topic name that user wants to subscribe.
:return: True if the user was subscribed, False otherwise.
Args:
topic_name (str): The topic name to subscribe.
user (str, optional): The user to subscribe to a private topic. Defaults to None.

Returns:
bool: True if the subscription was successful, False otherwise.
"""
URL = os.getenv("URL_LOCAL_TOPICS")
if user is not None:
Expand All @@ -99,12 +121,15 @@ async def subscribe(self, topic_name:str,user:str|None=None) -> bool:
print(f"Error in subscribe: {traceback.format_exc()}")
return False

async def get_topic(self, topic_name:str) -> dict[str, str|bool]|None:
async def get_topic(self, topic_name: str) -> dict[str, str|bool]|None:
"""
Method to get the topic information.

:param topic_name: The topic name.
:return: A dictionary with the topic information.
Args:
topic_name (str): The topic name to get the information.

Returns:
dict[str, str|bool]|None: The topic information or None if the topic doesn't exist.
"""
URL = os.getenv("URL_LOCAL_TOPICS")
PARAMS = {"topic_name": topic_name}
Expand All @@ -117,12 +142,15 @@ async def get_topic(self, topic_name:str) -> dict[str, str|bool]|None:
print(f"Error in get_topic: {traceback.format_exc()}")
return None

async def handle_message(self, info_message:dict|None=None) -> None:
async def handle_message(self, info_message: dict|None=None) -> None:
"""
Method to handle incoming messages and be sent to the correct destinataries.

:param info_message: The message to be sent.
:return: None
Args:
info_message (dict, optional): The message to be sent. Defaults to None.

Returns:
None
"""
try:
if info_message is not None:
Expand Down Expand Up @@ -186,7 +214,9 @@ async def handle_message(self, info_message:dict|None=None) -> None:
async def send_updates(self) -> None:
"""
Method to send updates of pending messages per user.
:return: None

Returns:
None
"""
URL = os.getenv("URL_LOCAL_MESSAGES")
PARAMS = {"user": self.username}
Expand All @@ -212,7 +242,8 @@ async def run(self) -> None:
"""
Method to run the WebSocketHandler.

:return: None
Returns:
None
"""
self.heartbeat_task = asyncio.create_task(self.send_heartbeat())
while True:
Expand Down Expand Up @@ -317,8 +348,12 @@ async def websocket_server(websocket, path: str) -> None:
Function to accept incoming WebSocket connections and validate
credentials if are correct.

:param websocket: The WebSocket connection.
:param path: The path of the WebSocket connection like url params.
Args:
websocket: WebSocket connection.
path(str): Path of the WebSocket connection.

Returns:
None
"""
websocket_handler = WebSocketHandler(websocket)
access = await websocket_handler.authenticate(token=path[1:])
Expand Down