From 564e449e3300a976daadc963cbeac5cce5e48e8e Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Sun, 20 Apr 2025 14:13:29 +0200 Subject: [PATCH] Web exchange seems to work now --- WebServer/static/exchange_client.py | 13 ++-- WebServer/static/exchange_worker.js | 61 +++++++++++---- WebServer/static/style.css | 1 + WebServer/templates/exchange.html | 74 +++++++++++++------ src/SantaExchange/MessageHandler.py | 14 ++-- .../MessageTypes/Announcement.py | 2 +- .../MessageTypes/Introduction.py | 2 +- src/SantaExchange/MessageTypes/Ready.py | 2 +- src/SantaExchange/MessageTypes/Shuffle.py | 2 +- src/SantaExchange/SantasBrain.py | 47 ++++++++---- src/SantaExchange/UserInterface.py | 6 +- 11 files changed, 155 insertions(+), 69 deletions(-) diff --git a/WebServer/static/exchange_client.py b/WebServer/static/exchange_client.py index 8f6adbe..2f4ab84 100644 --- a/WebServer/static/exchange_client.py +++ b/WebServer/static/exchange_client.py @@ -1,10 +1,11 @@ +from typing import Optional, Callable + from Crypto.PublicKey.ECC import EccKey -from src.SantaExchange.Message import Message -from src.SantaExchange.MessageHandler import MessageHandler -from src.SantaExchange.SantasBrain import Brain -from src.SantaExchange.UserInterface import UserInterface - +from SantaExchange.Message import Message +from SantaExchange.MessageHandler import MessageHandler +from SantaExchange.SantasBrain import Brain +from SantaExchange.UserInterface import UserInterface class ExchangeClient(MessageHandler, UserInterface): def __init__(self, send_message_function, receive_user_function, announce_recipient_function): @@ -25,4 +26,4 @@ class ExchangeClient(MessageHandler, UserInterface): self.receive_user_function(name) def announce_recipient(self, name: str, other_info: str): - self.announce_recipient_function(name, other_info) \ No newline at end of file + self.announce_recipient_function(name, other_info) diff --git a/WebServer/static/exchange_worker.js b/WebServer/static/exchange_worker.js index 208bc2f..0be023f 100644 --- a/WebServer/static/exchange_worker.js +++ b/WebServer/static/exchange_worker.js @@ -1,23 +1,56 @@ import "./pyodide.js"; async function prepare() { + self.postMessage({type: 'status', stage: 'initialize', text: 'Initializing...'}); let pyodide = await loadPyodide(); await pyodide.loadPackage("pycryptodome"); - await pyodide.loadPackage("./santaexchange-0.1-py3-none-any.whl") - pyodide.runPython(` - class Test: - def calculate(self): - return 15 + await pyodide.loadPackage("./santaexchange-0.1-py3-none-any.whl"); + let response = await fetch('./exchange_client.py'); + pyodide.runPython(await response.text()) - a = Test() + function sendMessage(messageString) { + self.postMessage({type: 'message', message: messageString}) + } + + function receiveUser(username) { + self.postMessage({type: 'new_user', username: username}) + } + + function announceRecipient(recipient_name, recipient_info) { + self.postMessage({type: 'status', stage: 'finished', text: `Your secret santa receiver is \n${recipient_name}\n${recipient_info}`}) + } + + function printLogs(record) { + console.log(record); + } + + pyodide.globals.set('send_message', sendMessage); + pyodide.globals.set('receive_user', receiveUser); + pyodide.globals.set('announce_recipient', announceRecipient); + pyodide.globals.set('print_logs_function', printLogs); + + pyodide.runPython(` + exchange_worker = ExchangeClient(send_message, receive_user, announce_recipient) `); - console.log('Running python function'); - console.log(pyodide.globals.get('a').calculate()); -} -prepare(); + addEventListener('message', e => { + if (e.data.type == 'message') { + pyodide.globals.get('exchange_worker').decode_received_message(e.data.message); + } else if (e.data.type == 'set_user') { + let username = e.data.username; + let userinfo = e.data.userinfo; + pyodide.globals.get('exchange_worker').set_user_info(username, userinfo); + postMessage({type: 'status', stage: 'wait_for_start', text: 'Waiting for other participants'}); + } else if (e.data.type == 'start') { + postMessage({ + type: 'status', + stage: 'exchanging', + text: 'Performing exchange when all users have pressed start. This may take a minute or so.' + }); + pyodide.globals.get('exchange_worker').start_exchange(e.data.users); + } + }); -addEventListener('message', e => { - self.postMessage('Loading pyodide'); - self.postMessage('Loaded pyodide'); -}); \ No newline at end of file + self.postMessage({type: 'status', stage: 'wait_for_user', text: 'Initialized'}) +} +prepare(); \ No newline at end of file diff --git a/WebServer/static/style.css b/WebServer/static/style.css index bd42c2e..05a4f67 100644 --- a/WebServer/static/style.css +++ b/WebServer/static/style.css @@ -35,4 +35,5 @@ input { textarea { width: 30vw; height: 20vh; + font-size: 20px; } \ No newline at end of file diff --git a/WebServer/templates/exchange.html b/WebServer/templates/exchange.html index 9e40e07..c077d19 100644 --- a/WebServer/templates/exchange.html +++ b/WebServer/templates/exchange.html @@ -12,38 +12,70 @@


- +


-

- +

+	
 	

Current participants:

-
- + const other_users = [] + + let submitButton = document.getElementById('submit_info_button'); + let startButton = document.getElementById('start_exchange_button'); + + submitButton.disabled = true; + startButton.disabled = true; - \ No newline at end of file diff --git a/src/SantaExchange/MessageHandler.py b/src/SantaExchange/MessageHandler.py index 098641f..de5659e 100644 --- a/src/SantaExchange/MessageHandler.py +++ b/src/SantaExchange/MessageHandler.py @@ -8,11 +8,11 @@ from json import JSONDecodeError from Crypto.PublicKey import ECC from Crypto.PublicKey.ECC import EccKey -from src.SantaExchange.Message import Message -from src.SantaExchange.MessageTypes.Announcement import AnnouncementMessage -from src.SantaExchange.MessageTypes.Introduction import IntroductionMessage -from src.SantaExchange.MessageTypes.Ready import ReadyMessage -from src.SantaExchange.MessageTypes.Shuffle import ShuffleMessage +from SantaExchange.Message import Message +from SantaExchange.MessageTypes.Announcement import AnnouncementMessage +from SantaExchange.MessageTypes.Introduction import IntroductionMessage +from SantaExchange.MessageTypes.Ready import ReadyMessage +from SantaExchange.MessageTypes.Shuffle import ShuffleMessage logger = logging.getLogger(__name__) @@ -21,9 +21,9 @@ class MessageHandler: def __init__(self): self.receivers: list[Callable[[Message], None]] = [] + # Must be implemented by child class def send_message(self, message: Message, signing_key: EccKey): - # Must be implemented by child SantaExchange - pass + raise NotImplementedError def add_message_receiver(self, message_receiver: Callable[[Message], None]): self.receivers.append(message_receiver) diff --git a/src/SantaExchange/MessageTypes/Announcement.py b/src/SantaExchange/MessageTypes/Announcement.py index 38adda3..4d46cea 100644 --- a/src/SantaExchange/MessageTypes/Announcement.py +++ b/src/SantaExchange/MessageTypes/Announcement.py @@ -1,6 +1,6 @@ import base64 -from src.SantaExchange.Message import Message +from SantaExchange.Message import Message class AnnouncementMessage(Message): diff --git a/src/SantaExchange/MessageTypes/Introduction.py b/src/SantaExchange/MessageTypes/Introduction.py index 6faa26a..51a1cca 100644 --- a/src/SantaExchange/MessageTypes/Introduction.py +++ b/src/SantaExchange/MessageTypes/Introduction.py @@ -3,7 +3,7 @@ import base64 from Crypto.PublicKey import ECC from Crypto.PublicKey.ECC import EccKey -from src.SantaExchange.Message import Message +from SantaExchange.Message import Message class IntroductionMessage(Message): diff --git a/src/SantaExchange/MessageTypes/Ready.py b/src/SantaExchange/MessageTypes/Ready.py index a8f9eea..f0ace25 100644 --- a/src/SantaExchange/MessageTypes/Ready.py +++ b/src/SantaExchange/MessageTypes/Ready.py @@ -3,7 +3,7 @@ import base64 from Crypto.PublicKey import ECC from Crypto.PublicKey.ECC import EccKey -from src.SantaExchange.Message import Message +from SantaExchange.Message import Message class ReadyMessage(Message): diff --git a/src/SantaExchange/MessageTypes/Shuffle.py b/src/SantaExchange/MessageTypes/Shuffle.py index 982c2ea..12815d4 100644 --- a/src/SantaExchange/MessageTypes/Shuffle.py +++ b/src/SantaExchange/MessageTypes/Shuffle.py @@ -1,6 +1,6 @@ import base64 -from src.SantaExchange.Message import Message +from SantaExchange.Message import Message class ShuffleMessage(Message): diff --git a/src/SantaExchange/SantasBrain.py b/src/SantaExchange/SantasBrain.py index 08550c6..5d069a6 100644 --- a/src/SantaExchange/SantasBrain.py +++ b/src/SantaExchange/SantasBrain.py @@ -14,15 +14,15 @@ from Crypto.Protocol.DH import key_agreement from Crypto.PublicKey import ECC from Crypto.PublicKey.ECC import EccKey -from src.SantaExchange import Message -from src.SantaExchange.Crypto.CSPRNG import CSPRNG -from src.SantaExchange.Crypto.CommutativeCipher import CommutativeCipher -from src.SantaExchange.MessageHandler import MessageHandler -from src.SantaExchange.MessageTypes.Announcement import AnnouncementMessage -from src.SantaExchange.MessageTypes.Introduction import IntroductionMessage -from src.SantaExchange.MessageTypes.Ready import ReadyMessage -from src.SantaExchange.MessageTypes.Shuffle import ShuffleMessage -from src.SantaExchange.UserInterface import UserInterface +from SantaExchange import Message +from SantaExchange.Crypto.CSPRNG import CSPRNG +from SantaExchange.Crypto.CommutativeCipher import CommutativeCipher +from SantaExchange.MessageHandler import MessageHandler +from SantaExchange.MessageTypes.Announcement import AnnouncementMessage +from SantaExchange.MessageTypes.Introduction import IntroductionMessage +from SantaExchange.MessageTypes.Ready import ReadyMessage +from SantaExchange.MessageTypes.Shuffle import ShuffleMessage +from SantaExchange.UserInterface import UserInterface logger = logging.getLogger(__name__) @@ -129,6 +129,10 @@ class Brain: self.chosen_participants = sorted([name for name, key in confirmed_existing]) self.message_handler.send_message(ready_message, self.signing_key) + # Might as well start building ciphers and preparing immediately. Also, if the first user in the list happens + # to be the last person to send the start message, something needs to trigger the santa loop + self.santa_loop_and_send() + def receive_message(self, message: Message): # If this is an introduction message, it needs special handling if isinstance(message, IntroductionMessage): @@ -149,14 +153,13 @@ class Brain: self.received_messages[name].append(message) # Each received message triggers the main function of this class - messages_to_send = self.santa_loop() - for message in messages_to_send: - self.message_handler.send_message(message, self.signing_key) + self.santa_loop_and_send() def receive_introduction_message(self, message: IntroductionMessage): discovered_new_participant = False name = message.get_name() key = message.get_key() + logger.debug(f'Receiving introduction from user {name}') with self.thread_lock: if name in self.known_participants and self.known_participants[name][0] != key: @@ -174,11 +177,21 @@ class Brain: # Send an introduction message, if we are ready for that if ( discovered_new_participant - and self.introduction_message is not None - and self.signing_key is not None ): + logger.debug(f'Received new user {name}') self.user_interface.receive_user(name) - self.message_handler.send_message(self.introduction_message, self.signing_key) + if ( + self.introduction_message is not None + and self.signing_key is not None + ): + logger.debug('Sending own introduction message') + self.message_handler.send_message(self.introduction_message, self.signing_key) + + + def santa_loop_and_send(self): + messages_to_send = self.santa_loop() + for message in messages_to_send: + self.message_handler.send_message(message, self.signing_key) # Santa's brain will be driven by receiving messages. We'll call this main method each time we receive a message. # This is probably inefficient, but it makes it easier to follow what the code is doing @@ -203,6 +216,7 @@ class Brain: or self.announcement_build_cipher is None or self.announcement_shuffle_cipher is None ): + logger.debug('Building ciphers') should_continue = self.build_ciphers() if not should_continue: return messages_to_send @@ -212,6 +226,7 @@ class Brain: # Shuffle cards if not self.sent_card_shuffling: + logger.debug('Shuffling vards') shuffle_message = self.build_shuffle_message(all_participants) if shuffle_message is None: return messages_to_send @@ -220,6 +235,7 @@ class Brain: # Decrypt shuffled cards if not self.sent_card_decryption: + logger.debug('Decrypting shuffled cards') decrypt_cards_message = self.build_decrypt_cards_message(all_participants) if decrypt_cards_message is None: return messages_to_send @@ -432,6 +448,7 @@ class Brain: return ShuffleMessage(self.own_name, decrypted_cards, DECRYPT_CARDS_STAGE) def decrypt_card_value(self, card: bytes): + logger.debug('Decrypting own drawn card') decrypted_card_bytes = self.card_exchange_cipher.decode(card) if decrypted_card_bytes not in self.card_values: logging.critical(f'Received an invalid card after shuffling. Secret santa exchange failed!') diff --git a/src/SantaExchange/UserInterface.py b/src/SantaExchange/UserInterface.py index fb24e76..56095bb 100644 --- a/src/SantaExchange/UserInterface.py +++ b/src/SantaExchange/UserInterface.py @@ -6,11 +6,13 @@ class UserInterface: self.user_info_listeners: list[Callable[[str, str], None]] = [] self.start_listeners: list[Callable[[list[str]], None]] = [] + # Must be implemented by child class def receive_user(self, name: str): - pass + raise NotImplementedError + # Must be implemented by child class def announce_recipient(self, name: str, other_info: str): - pass + raise NotImplementedError def set_user_info(self, name: str, info_for_santa: str): for listener in self.user_info_listeners: