Skip to content

codev-sdk: message-bus WebSocket client absent from v1 (tracking; mobile chat + gate-event consumers need it) #1350

Open

Description

@amrmelsayed

Finding (from the #1189 RN/Metro async review of PR #1346)

Scope note rather than defect: #1189's design listed a "WS/message-bus client" among the sdk's contents, and v1 ships without one. The sdk covers REST (tower-client) and SSE decoding (sse), but subscribing to /ws/messages (MessageFrame stream) still requires each consumer to hand-roll a WebSocket client, reconnect policy wiring, and frame parsing.

Consumers that will need it: the mobile chat + feed (first RN spike consumer), the web dashboard's eventual migration onto the sdk, and every consumer of #1194's typed gate-event frames (which ride the message bus as BusEventMeta).

Ask

Confirm this was a deliberate v1 cut (likely: no consumer exists yet, matching the extract-on-evidence principle), and keep this issue as the tracking point for adding it when the first consumer lands. Shape suggestion when it happens, mirroring the existing seams: injected WebSocket constructor (RN provides a global WebSocket; Node consumers inject ws), reconnect-policy for backoff, typed MessageFrame from codev-types, and lifecycle hooks compatible with RN AppState foreground reconnects (verified necessary on-device in the LAN spike, codev/research/mobile/spikes/expo-lan-reachability-2026-07.md).

Activity

  1. added
    area/coreArea: shared core library / forge abstraction (packages/core, packages/codev/src/lib)
    on Aug 5, 2026
  2. amrmelsayed commented on Aug 5, 2026

    @amrmelsayed
    CollaboratorAuthor

    Confirming from the merged plan (codev/plans/1189-introduce-packages-codev-sdk-c.md): the cut was deliberate in effect but under-documented — SSE was scoped in as the v1 event channel; the message-bus WS client was never in the v1 build list at all. The plan's only explicit event-channel decision (Design Decisions, ~line 306) scopes subscribeEvents (SSE) for the controller/streamdeck/mobile lanes and defers even in-repo SSE consolidation; a /ws/messages bus client has no consumer among the in-repo trio the PR migrated (dashboard/vscode consume terminals + SSE via their own paths), so under the plan's no-facades/single-source discipline it had nothing to migrate FROM and was implicitly out.

    So: deliberate descope, correctly so for v1 — but the issue body's original design ("Tower API client, WS/message-bus client") promised it, and this issue is the right tracker for delivering it. Since mobile's feed/chat is the first consumer, I'd suggest mobile drafts the client's contract needs (frame types from #1194's BusEventMeta envelope, reconnect semantics vs the sdk's reconnect-policy, auth handshake) as input, and the implementation lands as an sdk addition (./message-bus subpath) with the same import-boundary discipline — WebSocket arrives as an injected constructor, same pattern as fetchFn, so RN's WebSocket global and Node's ws both slot in. Sequencing-wise it can ride ahead of or alongside the mobile feed spike; it does not block #1347 (streamdeck rides SSE + command relay).

  3. amrmelsayed commented on Aug 5, 2026

    @amrmelsayed
    CollaboratorAuthor

    Consumer contract from the first consumer (mobile feed/chat), per main's suggestion — what the ./message-bus subpath needs to satisfy, shaped to the sdk's existing seam idioms:

    1. Injected socket, minimal interface. wsFactory?: (url: string) => MinimalWebSocket where the interface is only what the client uses (onopen/onmessage/onerror/onclose, close(), readyState). Defaults to globalThis.WebSocket (exists on RN and browsers); Node consumers inject ws. Mirrors fetchFn's default-global-inject-to-override pattern.

    2. URL building must compose with #1349. The ws(s):// URL derives from the client's base (scheme flipped from http(s), path prefix preserved), so /ws/messages works behind the tunnel proxy (wss://<cloud>/t/<towerId>/ws/messages). If #1349's baseUrl lands first, this comes free; if not, this subpath becomes the second place host+port hardcoding hurts.

    3. Typed frames, open-enum dispatch. Parse MessageFrame from codev-types; expose one onFrame callback plus a discriminated meta (BusEventMeta) so #1194 gate-event frames and future members route through a single dispatcher. Unknown kind values are delivered as opaque frames, never dropped or thrown (the #1194 open-enum discipline).

    4. projectFilter passthrough (the ?project= query the route already supports).

    5. Reconnect semantics. reconnect-policy backoff; explicit start()/stop() lifecycle so hosts wire their own foreground signal (RN AppState foreground-reconnect is verified necessary on-device — spike note, codev/research/mobile/spikes/expo-lan-reachability-2026-07.md); reconnect is resubscribe-only with no replay expectation — the bus is fire-and-forget, and consumers refetch REST snapshots on reconnect per decisions/q3-offline-behavior.md. The client should surface a droppedConnection signal so consumers know to refetch.

    6. Connection-state surface. 'idle' | 'connecting' | 'open' | 'backoff' | 'closed' exposed as a readable state + change callback — feeds the presence tiers in decisions/q6-session-presence.md directly.

    7. Auth seam now, enforcement later. /ws/messages is unauthenticated today, but the constructor should take getAuthKey from day one (applied as query param or first-frame auth, whichever Tower lands) so adding WS auth is not a breaking change for consumers.

    Happy to review the subpath's shape against this when it lands; the RN feed spike will exercise whatever exists at that point (and doubles as #1348's expo/fetch streaming verification).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreArea: shared core library / forge abstraction (packages/core, packages/codev/src/lib)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions