crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

Engagements.md (2660B)


      1 # Engagements Design Note
      2 
      3 ## Context
      4 
      5 CloudKit push delivers updates in roughly 1-3 seconds. That is fine for durable
      6 game state, but it is too laggy for two live-play interactions:
      7 
      8 1. Collaborator cursor/selection movement.
      9 2. In-progress letter taps before the authoritative `Move` records land.
     10 
     11 An **engagement** is Crossmate's opportunistic low-latency channel for those
     12 live interactions. It is distinct from a play session and is never the source of
     13 truth. CloudKit remains authoritative.
     14 
     15 ## Architecture
     16 
     17 - **Transport:** native iOS WebSockets using `URLSessionWebSocketTask`.
     18 - **Relay:** Cloudflare Worker + one Durable Object per room.
     19 - **Bootstrap:** `.hail` pings in the shared game zone carry room credentials.
     20 - **Payload:** existing `EngagementMessage` JSON envelopes: debug text, realtime
     21   cell edits, and selection updates.
     22 - **Fallback:** any socket failure silently returns to CloudKit-only behavior.
     23 
     24 ## Hail Payload
     25 
     26 `.hail` now carries a v2 room bootstrap payload:
     27 
     28 ```json
     29 {
     30   "ver": 2,
     31   "roomID": "<uuid>",
     32   "secret": "<base64url 256-bit secret>",
     33   "createdAt": "<date>",
     34   "expiresAt": "<date>"
     35 }
     36 ```
     37 
     38 Only CloudKit share participants can read the hail. The secret is a room secret,
     39 not a durable account credential.
     40 
     41 ## Room Auth
     42 
     43 The client connects to:
     44 
     45 ```text
     46 /rooms/:roomID/socket
     47 ```
     48 
     49 with:
     50 
     51 - `authorID`
     52 - `deviceID`
     53 - `timestamp`
     54 - `nonce`
     55 - `secret`
     56 - `signature = HMAC-SHA256(secret, roomID|authorID|deviceID|timestamp|nonce)`
     57 
     58 The Durable Object verifies the timestamp, rejects nonce replay, verifies the
     59 HMAC, and stores only a hash of the room secret. This first implementation sends
     60 the room secret during the WSS upgrade so the object can verify the HMAC; if that
     61 becomes uncomfortable, move auth into a short-lived token minting step.
     62 
     63 ## Lifecycle
     64 
     65 1. Existing peer-presence logic notices another player in the puzzle.
     66 2. Deterministic initiator creates `roomID + secret`, writes a directed `.hail`,
     67    and connects.
     68 3. Recipient receives the hail, validates addressee/expiry, connects, and
     69    deletes the hail.
     70 4. `EngagementCoordinator` marks the game live only after socket open.
     71 5. Socket close clears transient engagement selection state and may retry via
     72    the normal presence path.
     73 
     74 Coordinator states are intentionally small:
     75 
     76 - `idle`
     77 - `connecting`
     78 - `live`
     79 
     80 ## Worker Duties
     81 
     82 - Authenticate socket upgrades.
     83 - Bind one Durable Object to each room ID.
     84 - Broadcast incoming messages to every other socket in the room.
     85 - Keep nonce replay state with a short TTL.
     86 - Expire rooms and close sockets after the room TTL.
     87 
     88 The Worker does not know puzzle contents, CloudKit tokens, or durable game
     89 state.