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.