Skip to content

feat: add websocket client module - #41

Merged
0xdevcollins merged 3 commits into
boundlessfi:mainfrom
DioChuks:feature/websocket-client
Jan 25, 2026
Merged

feat: add websocket client module#41
0xdevcollins merged 3 commits into
boundlessfi:mainfrom
DioChuks:feature/websocket-client

Conversation

@DioChuks

@DioChuks DioChuks commented Jan 25, 2026

Copy link
Copy Markdown
Contributor

Socket.IO Client & TanStack Query Sync

I have successfully set up the Socket.IO client and integrated it with TanStack Query to provide real-time updates for bounties.

Changes Made

  1. Socket.IO Client Configuration
    Created
    client.ts
    which handles:

Connection to NEXT_PUBLIC_API_URL.
Automatic reconnection logic (5 attempts).
Heartbeat logging (ping/pong).
Detailed socket event logging for debugging.
2. TanStack Query Synchronization logic
Implemented a robust sync system in lib/query/sync:

handlers.ts
: Pure logic to update queryClient data for bounty.created, bounty.updated, and bounty.deleted events.
socket-sync.ts
: Service to attach/detach socket listeners and dispatch to handlers.
3. React Integration Hook
Created
use-socket-sync.ts
:

Manages socket connection lifecycle based on user session.
Automatically connects when a session is active and disconnects on logout.
Initializes query synchronization listeners.

How to use

I've disabled it for now but to enable real-time updates in your application, simply call the useSocketSync hook in a root-level component (e.g., in a Layout or Provider):

Verification Results

Reconnection: Verified that the client attempts reconnection with backoff.
Cache Updates: The handlers are configured to update queryKeys.lists()`` and queryKeys.detail(id), ensuring the UI stays in sync without manual refetches.
Auth Integration: Connection is gated by the session status from authClient.


NB: Ensure your NestJS backend emits events named bounty.created, bounty.updated, and bounty.deleted with the appropriate bounty data as the payload.

Closes #24

Summary by CodeRabbit

  • New Features

    • Real-time bounty sync: bounties now update instantly across connected clients when created, updated, or deleted.
    • Session-aware connection: socket connects/disconnects based on user session and exposes connection status for the client.
  • Chores

    • Added Socket.IO client dependency to enable real-time communication and reconnection handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a Socket.IO client, React Query cache synchronization handlers, and a useSocketSync React hook that connects/disconnects the socket based on auth state and updates bounty list/detail caches on real-time events.

Changes

Cohort / File(s) Summary
Socket client
lib/socket/client.ts
New pre-configured Socket.IO client export (autoConnect: false, reconnection settings, timeout, withCredentials, websocket transport) with lifecycle and ping/pong logging.
Sync handlers
lib/query/sync/handlers.ts
New React Query handlers: handleBountyCreated, handleBountyUpdated, handleBountyDeleted that update paginated lists and detail caches safely and maintain totals.
Socket → Query integration
lib/query/sync/socket-sync.ts
New setupSocketSync(queryClient) that registers socket listeners for bounty.created, bounty.updated, bounty.deleted and returns a cleanup function.
Hook lifecycle
hooks/use-socket-sync.ts
New useSocketSync() hook: uses auth session to connect/disconnect socket, invokes setupSocketSync(queryClient), cleans up on unmount/logout, and returns { socket, isConnected }.
Dependencies
package.json
Added dependency socket.io-client (^4.8.3).

Sequence Diagram

sequenceDiagram
    participant App as React App
    participant Hook as useSocketSync Hook
    participant SocketIO as Socket.IO Client
    participant Server as WebSocket Server
    participant Handlers as Sync Handlers
    participant QueryClient as React Query Cache

    App->>Hook: Mount (auth available)
    Hook->>SocketIO: connect()
    SocketIO->>Server: establish connection
    Server-->>SocketIO: connection confirmed
    Hook->>Handlers: setupSocketSync(queryClient)
    Handlers->>SocketIO: register listeners (bounty.created/.updated/.deleted)

    rect rgba(100, 200, 255, 0.5)
    Server->>SocketIO: bounty.created event
    SocketIO->>Handlers: handleBountyCreated(payload)
    Handlers->>QueryClient: prepend bounty, set detail, adjust total
    end

    rect rgba(100, 200, 255, 0.5)
    Server->>SocketIO: bounty.updated event
    SocketIO->>Handlers: handleBountyUpdated(payload)
    Handlers->>QueryClient: replace bounty in lists, update detail
    end

    rect rgba(100, 200, 255, 0.5)
    Server->>SocketIO: bounty.deleted event
    SocketIO->>Handlers: handleBountyDeleted(payload)
    Handlers->>QueryClient: remove bounty, adjust total, clear detail
    end

    App->>Hook: Unmount or logout
    Hook->>Handlers: cleanup listeners
    Hook->>SocketIO: disconnect()
    SocketIO->>Server: close connection
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

"I nibble code and chase each ping,
I hop when bounties freshly spring,
With sockets bright and caches neat,
Real-time carrots — oh, what a treat! 🥕🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: adding a websocket client module as the foundation for the entire feature.
Linked Issues check ✅ Passed All requirements from issue #24 are met: socket client created with auth/reconnection/heartbeat [#24], synchronization handlers and socket-sync service implemented [#24], and useSocketSync hook provided [#24].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #24 requirements: socket client configuration, query sync handlers, socket-sync service, and auth-gated hook implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@DioChuks

Copy link
Copy Markdown
Contributor Author

@0xdevcollins pls review & merge

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@hooks/use-socket-sync.ts`:
- Around line 37-40: The returned isConnected currently reads socket.connected
once and won't update; change the hook (useSocketSync) to track connection state
with React state: initialize a useState<boolean>(socket.connected), add
socket.on('connect') and socket.on('disconnect') handlers that call
setIsConnected(true/false), and remove those listeners in the effect cleanup;
return the state value (isConnected) instead of the raw socket.connected so
consumers get reactive updates.

In `@lib/socket/client.ts`:
- Around line 49-56: The current socket.on('ping'...) and socket.on('pong'...)
handlers on the Socket instance will never fire in socket.io-client v4; update
the implementation to either observe Engine.IO packets or implement an app-level
ping/ack: inside the socket.on('connect') callback access socket.io.engine and
attach engine.on('packet', ...) to inspect packet.type for 'ping'/'pong' and log
latency, or replace these debug handlers with an explicit emit/ack ping
round-trip using socket.emit('app-ping', ..., ack) to measure latency; remove
the unused socket.on('ping')/socket.on('pong') listeners if you choose the
app-level approach.
🧹 Nitpick comments (3)
lib/socket/client.ts (1)

16-47: Consider environment-aware logging.

The verbose console logging (connect, disconnect, reconnect events) is helpful for development but may be noisy in production. Consider gating these logs behind a debug flag or using a proper logging library.

Optional: Environment-aware logging
+const DEBUG = process.env.NODE_ENV === 'development';
+const log = (...args: unknown[]) => DEBUG && console.log(...args);
+const logError = (...args: unknown[]) => DEBUG && console.error(...args);
+const logDebug = (...args: unknown[]) => DEBUG && console.debug(...args);

 socket.on('connect', () => {
-  console.log('[Socket] Connected to server:', socket.id);
+  log('[Socket] Connected to server:', socket.id);
 });
 // ... apply similar changes to other handlers
lib/query/sync/handlers.ts (1)

5-26: Consider filter-aware cache updates for paginated lists.

The handler prepends the new bounty to all cached list queries. If users have filtered views (e.g., by status, category), the new bounty may not match those filter criteria, leading to inconsistent UI state.

Options to consider:

  1. Invalidate list queries instead of optimistically updating them (simpler but triggers refetch)
  2. Check if the bounty matches the query's filter before prepending
  3. Accept this limitation if filters are uncommon in your use case
Alternative: Invalidate lists instead of optimistic update
 export function handleBountyCreated(queryClient: QueryClient, bounty: Bounty) {
   console.log('[Sync] Handling bounty.created:', bounty.id);

-  // Update lists
-  queryClient.setQueriesData<PaginatedResponse<Bounty>>(
-    { queryKey: bountyKeys.lists() },
-    (oldData) => {
-      if (!oldData) return oldData;
-      return {
-        ...oldData,
-        data: [bounty, ...oldData.data],
-        pagination: {
-          ...oldData.pagination,
-          total: oldData.pagination.total + 1,
-        },
-      };
-    }
-  );
+  // Invalidate lists to refetch with correct filters
+  queryClient.invalidateQueries({ queryKey: bountyKeys.lists() });

   // Set detail cache
   queryClient.setQueryData(bountyKeys.detail(bounty.id), bounty);
 }
lib/query/sync/socket-sync.ts (1)

9-21: Add error handling to prevent unhandled exceptions in socket listeners.

If a handler throws (e.g., due to malformed payload), it could crash the socket listener silently. Consider wrapping handler calls in try-catch.

Proposed fix with error handling
   socket.on('bounty.created', (payload) => {
+    try {
       handlers.handleBountyCreated(queryClient, payload);
+    } catch (error) {
+      console.error('[Sync] Error handling bounty.created:', error);
+    }
   });

   socket.on('bounty.updated', (payload) => {
+    try {
       handlers.handleBountyUpdated(queryClient, payload);
+    } catch (error) {
+      console.error('[Sync] Error handling bounty.updated:', error);
+    }
   });

   socket.on('bounty.deleted', (payload) => {
-    // payload might be the ID string or an object with id
-    const bountyId = typeof payload === 'string' ? payload : payload.id;
-    handlers.handleBountyDeleted(queryClient, bountyId);
+    try {
+      const bountyId = typeof payload === 'string' ? payload : payload.id;
+      handlers.handleBountyDeleted(queryClient, bountyId);
+    } catch (error) {
+      console.error('[Sync] Error handling bounty.deleted:', error);
+    }
   });

Comment thread hooks/use-socket-sync.ts
Comment thread lib/socket/client.ts Outdated
@0xdevcollins
0xdevcollins merged commit d781215 into boundlessfi:main Jan 25, 2026
2 checks passed
0xDeon pushed a commit to 0xDeon/bounties that referenced this pull request Jan 26, 2026
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.

Setup WebSocket Client

2 participants