From ff3b33443612b0b3b775f78325430d5752a9c01f Mon Sep 17 00:00:00 2001 From: Lucas Messenger <1335960+layertwo@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:57:44 -0800 Subject: [PATCH] fix: use lowercase channelid in WebSocket response and add logging The fxa-pairing-channel library expects lowercase "channelid" in the initial WebSocket message (matching Mozilla's channelserver protocol), but we were sending "channelId" (camelCase). This caused PairingChannel .create() to hang indefinitely because the channel ID was undefined. Also adds structured logging to all channel service operations. --- lambda/src/services/channel_service.py | 10 +++++++++- lambda/tests/services/test_channel_service.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lambda/src/services/channel_service.py b/lambda/src/services/channel_service.py index ca298f5..72e4258 100644 --- a/lambda/src/services/channel_service.py +++ b/lambda/src/services/channel_service.py @@ -1,11 +1,14 @@ """Channel Service — WebSocket message relay for device pairing.""" import json +import logging import time import uuid from botocore.exceptions import ClientError +logger = logging.getLogger(__name__) + MAX_CONNECTIONS_PER_CHANNEL = 3 MAX_MESSAGES_PER_CHANNEL = 10 CHANNEL_TTL_SECONDS = 300 @@ -28,6 +31,7 @@ def handle(self, event, context): """Dispatch on WebSocket route key.""" route_key = event["requestContext"]["routeKey"] connection_id = event["requestContext"]["connectionId"] + logger.info("route=%s connection=%s", route_key, connection_id) if route_key == "$connect": return self._handle_connect(event, connection_id) @@ -53,6 +57,7 @@ def _handle_connect(self, event, connection_id): def _create_channel(self, event, connection_id, expiry): """Create a new channel with this connection as the first member.""" channel_id = str(uuid.uuid4()) + logger.info("Creating channel=%s for connection=%s", channel_id, connection_id) # Put channel metadata self._table.put_item( @@ -77,13 +82,14 @@ def _create_channel(self, event, connection_id, expiry): self._post_to_connection( event, connection_id, - json.dumps({"channelId": channel_id}), + json.dumps({"channelid": channel_id}), ) return {"statusCode": 200} def _join_channel(self, channel_id, connection_id, expiry): """Join an existing channel atomically.""" + logger.info("Joining channel=%s connection=%s", channel_id, connection_id) try: self._table.update_item( Key={"PK": f"CHANNEL#{channel_id}"}, @@ -140,6 +146,7 @@ def _handle_disconnect(self, connection_id): def _handle_message(self, event, connection_id): """Handle incoming message — relay to other connections.""" + logger.info("Message from connection=%s", connection_id) # Look up channel for this connection result = self._table.get_item(Key={"PK": f"CONN#{connection_id}"}) if "Item" not in result: @@ -201,6 +208,7 @@ def _post_to_connection(self, event, connection_id, data): Data=data.encode("utf-8") if isinstance(data, str) else data, ) except client.exceptions.GoneException: + logger.warning("Connection %s is gone, cleaning up", connection_id) self._handle_disconnect(connection_id) def _get_apigw_client(self, event): diff --git a/lambda/tests/services/test_channel_service.py b/lambda/tests/services/test_channel_service.py index 6f2b248..a3c12dd 100644 --- a/lambda/tests/services/test_channel_service.py +++ b/lambda/tests/services/test_channel_service.py @@ -112,7 +112,7 @@ def test_create_channel( {}, { "ConnectionId": "conn-1", - "Data": json.dumps({"channelId": FIXED_UUID}).encode("utf-8"), + "Data": json.dumps({"channelid": FIXED_UUID}).encode("utf-8"), }, )