Skip to content

feat(api): add WebSocket endpoint with hub/client architecture#10

Merged
omattsson merged 3 commits intomainfrom
feature/add-websocket-support
Mar 14, 2026
Merged

feat(api): add WebSocket endpoint with hub/client architecture#10
omattsson merged 3 commits intomainfrom
feature/add-websocket-support

Conversation

@omattsson
Copy link
Owner

Summary

Adds a /ws WebSocket endpoint for real-time bidirectional communication between the server and connected clients.

Changes

Backend – WebSocket feature

  • internal/websocket — Hub + Client types with goroutine-safe broadcast, register/unregister, and graceful hub.Shutdown() for clean teardown
  • handlers/websocket.goWebSocketHandler struct (separate from Handler) that upgrades HTTP connections to WebSocket; validates origin against CORS_ALLOWED_ORIGINS
  • handlers/websocket_test.go — unit tests for the WebSocket handler
  • routes.go/ws route registered outside the rate limiter (connections are long-lived); hub injected via updated SetupRoutes signature
  • main.go — hub created and started before router setup; hub.Shutdown() called during graceful shutdown
  • docs/ — Swagger docs regenerated to include the /ws- docs/ — Swagger docs regenerated to include the * — new SCM Engineer agent for Git/GitHub SCM operations
  • .github/agents/orchestrator.md — workflow updated to include scm-engineer step; added GitHub issue management instructions
  • Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor u- Minor uo test ./... -v -short

Add new scm-engineer agent for Git/GitHub SCM operations.
Update orchestrator workflow to include scm-engineer step before
code-reviewer. Extend orchestrator with GitHub issue management
instructions and additional tools (terminal, agent, todo).
Minor additions to code-reviewer, devops-engineer, frontend-developer,
go-api-developer, and qa-engineer agent definitions.
Add a /ws WebSocket endpoint for real-time bidirectional communication.
A Hub manages all active client connections and broadcasts messages.
WebSocketHandler is a separate struct (not Handler) injected with *Hub.

- internal/websocket: Hub + Client types with goroutine-safe broadcast,
  register/unregister, and graceful shutdown via hub.Shutdown()
- handlers/websocket.go: upgrades HTTP to WebSocket with origin validation
  from CORS_ALLOWED_ORIGINS config
- routes.go: /ws route registered outside rate limiter (connections are
  long-lived); hub injected via SetupRoutes signature
- main.go: hub created and started before router setup; hub.Shutdown()
  called during graceful shutdown
- docs regenerated to include /ws endpoint
Copilot AI review requested due to automatic review settings March 14, 2026 18:05
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a top-level /ws WebSocket endpoint to the Go (Gin) backend using a hub/client architecture, wiring it into routing and graceful shutdown, and regenerates Swagger docs accordingly. Also updates the repo’s “agent” markdown configurations to include model/tool metadata and adds a new SCM Engineer agent.

Changes:

  • Register GET /ws in route setup (outside the rate limiter) and inject a *websocket.Hub through SetupRoutes.
  • Start/stop the WebSocket hub from api/main.go and add handler + unit tests for WebSocket upgrades/origin checks.
  • Regenerate Swagger artifacts and update .github/agents/* configurations (new SCM Engineer agent + tooling/model fields).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backend/internal/api/routes/routes.go Extends SetupRoutes to accept a hub and registers GET /ws outside rate limiting.
backend/internal/api/routes/routes_test.go Updates route setup test to construct/run/shutdown a hub and pass it into SetupRoutes.
backend/internal/api/handlers/websocket.go Adds WebSocketHandler to upgrade connections and validate Origin against allowed origins.
backend/internal/api/handlers/websocket_test.go Adds unit/integration-style tests for origin validation and basic hub/client interaction via /ws.
backend/api/main.go Creates/starts hub before route setup and shuts hub down during graceful shutdown.
backend/docs/swagger.yaml Adds /ws endpoint and updates version description text.
backend/docs/swagger.json Adds /ws endpoint and top-level produces/schemes; updates version description text.
backend/docs/docs.go Updates generated template/spec fields (produces, schemes) and adds /ws path.
.github/agents/scm-engineer.md Adds new SCM Engineer agent definition and workflow guidance.
.github/agents/orchestrator.md Updates orchestrator workflow to include SCM engineer step and issue-management instructions.
.github/agents/qa-engineer.md Adds model metadata to agent definition.
.github/agents/go-api-developer.md Adds model metadata and expands tools list formatting.
.github/agents/frontend-developer.md Adds model metadata and includes problems tool.
.github/agents/devops-engineer.md Adds model metadata and includes problems tool.
.github/agents/code-reviewer.md Adds model metadata and expands tools list formatting.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +39 to +52
upgrader := gorilla.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: h.checkOrigin,
}

conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
slog.Error("WebSocket upgrade failed", "error", err)
return
}

if _, err := websocket.NewClient(h.hub, conn); err != nil {
slog.Error("WebSocket client creation failed", "error", err)
Comment on lines +278 to +287
// The upgrade itself succeeds (HTTP → WS), but NewClient fails
// because the hub is closed. The server closes the connection.
conn, _, err := gorilla.DefaultDialer.Dial(wsURL, nil)
if err != nil {
// Connection refused or failed — acceptable when hub is closed
return
}
defer conn.Close()

// If dial succeeded, the connection should be immediately closed by server
Cover all six functions in client.go with direct unit tests:

- NewClient: success path (client registered, pumps started) and hub-already-
  shut-down path (ErrHubClosed returned, conn closed by NewClient)
- writeMessage: successful text frame delivery to peer; NextWriter error on
  closed conn
- writePing: successful ping frame (peer PingHandler fires); write error on
  closed conn
- handleSend: ok=false sends close frame + returns errChanClosed; ok=true
  single message; ok=true with queued drain (3 messages in order); closed conn
  returns write error
- readPump: normal close frame (CloseNormalClosure) triggers unregister;
  unexpected TCP drop triggers unregister; CloseProtocolError triggers the
  slog.Warn path (IsUnexpectedCloseError=true) then unregister
- writePump: single message forwarded to peer; multiple messages in order;
  closed send channel sends WS close frame and exits cleanly

Remaining uncovered statements are gorilla/websocket library-internal paths
(SetWriteDeadline returns nil lazily s(SetWriteDeadline returns nil lazily s(SetWriteDeadline retunt(SetWriteDeadline returns nil lazily s(SetWriteDeadline returns nil lazily s��(SetWriteDeadline returns nil lazily s(SetWriteDeadline retage: 45.5% → 85.7%
@omattsson omattsson merged commit abf1e40 into main Mar 14, 2026
@omattsson omattsson deleted the feature/add-websocket-support branch March 14, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants