Skip to content

feat: implement real-time command handling #92

Description

@r0liveir

Context

Current backend and frontend communicates via raw events / raw snapshots sends, which works, but introduces complications when tackling errors. For example, if an event is malformed and raises an Error, the backend closes the WS connection, which is not ideal.

What to do

  1. Define an atomic upgrade

Both backend and frontend needs to change the way they communicate. A proposed plan is to start on the backend and integrate frontend later on. A proposed protocol:

  {
    "type": "authenticate",
    "access_token": "<Supabase JWT>"
  }

  {
    "type": "command",
    "request_id": "4f01c65c-4f0b-4cf7-aa3d-61d4a35b4292",
    "event": {
      "type": "JoinQueueEvent",
      "payload": {}
    }
  }

  {
    "type": "state_snapshot",
    "state": {
      "...": "SessionLiveState"
    }
  }

  {
    "type": "command_result",
    "request_id": "4f01c65c-4f0b-4cf7-aa3d-61d4a35b4292",
    "event_type": "JoinQueueEvent",
    "ok": false,
    "error": {
      "code": "conflict.already_queued",
      "message": "This representation is already in the speakers list."
    }
  }

By upgrading to something like this, we enable better error handling. For a successful event command, the sender receives a command_result and a state_snapshot.

You may need to implement input/output schemas for this.

  1. Separate errors

Create a session-level base exception. Example hierarchy:

SessionCommandError
- InvalidMoveError
- PermissionDeniedError
- InvalidSchemaError
...

Each error may expose a code string and a message for additional details (as you see on the expected json above). Initial ones can be:

  • invalid_schema
  • permission_denied
  • invalid_move
  • internal_error

These may be condensed into an enum ErrorCode that the transport layer may use to match stuff. Base exception then becomes:

  class SessionCommandError(Exception):
      def __init__(self, code: CommandErrorCode, message: str):
          self.code = code
          self.message = message
          super().__init__(message)

And later exceptions may inherit / compose of this class.

  1. Catch these errors on transport/view layer (in particular, in the websocket layer)

After raising an error, send an error type message to the socket, but leave the connection open. Sockets should only be disconnected on their own or if an admin kicks/bans them

  1. Test behaviour.

This will, surely, break existing behavior. We may need to change tests to account for this. I recommend using an AI agent for this.

If possible, add e2e tests to test if malformed/error requests keep ws open but return an error message.

Expected Behaviour

Main one is persisted WebSockets

Metadata

Metadata

Labels

BackendBack of the endFrontendFront of the end

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions