Skip to content

feat: realtime UI updates via Pusher/Soketi + TanStack Query invalidation (Phase 1 & 2)#98

Merged
kingRayhan merged 7 commits intomainfrom
copilot/add-realtime-ui-updates
Apr 2, 2026
Merged

feat: realtime UI updates via Pusher/Soketi + TanStack Query invalidation (Phase 1 & 2)#98
kingRayhan merged 7 commits intomainfrom
copilot/add-realtime-ui-updates

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

Adds Pusher/Soketi-protocol realtime signalling so browsers invalidate TanStack Query caches on push events instead of polling. The database and server actions remain the source of truth; the socket carries only event names plus a small payload (e.g. scope).

Dependencies

  • pusher (server) and pusher-js (browser)

Environment (src/env.ts)

Pusher-related variables are required when running the app:

Variable Where
PUSHER_WS_HOST Server — broker HTTP API host
PUSHER_APP_ID, PUSHER_APP_KEY, PUSHER_APP_SECRET Server
NEXT_PUBLIC_PUSHER_APP_KEY Client
NEXT_PUBLIC_PUSHER_WS_HOST Client — WebSocket host (e.g. Soketi)

Module layout (src/lib/pusher/)

File Role
realtime-events.ts Single registry: REALTIME_PUSHER_EVENTS (const object), RealtimePusherEvent (union), RealtimeListenHandlers (typed handler map). Add new events here so listeners and triggers stay aligned.
pusher.server.ts Lazy pusherServer; publishMessage(channel, event, data?) where event is RealtimePusherEvent (best-effort; logs broker failures, must not break callers). Re-exports REALTIME_PUSHER_EVENTS.
pusher.client.ts Shared browser client; listenChannel(channel, handlers) with handlers: RealtimeListenHandlers (only known event keys; excess keys are a type error). Re-exports event types and REALTIME_PUSHER_EVENTS.

Registered events (REALTIME_PUSHER_EVENTS)

Constant Wire value
NOTIFICATION_NEW notification.new
COMMENT_CREATED comment.created
COMMENT_UPDATED comment.updated
COMMENT_DELETED comment.deleted

Call sites use [REALTIME_PUSHER_EVENTS.…] (listeners) and REALTIME_PUSHER_EVENTS.… ( publishMessage ) to avoid string drift.

Client API — listenChannel

Object-only API; each key must be a RealtimePusherEvent:

return listenChannel(channelName, {
  [REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW]: invalidate,
});

return listenChannel(channelName, {
  [REALTIME_PUSHER_EVENTS.COMMENT_CREATED]: invalidate,
  [REALTIME_PUSHER_EVENTS.COMMENT_UPDATED]: invalidate,
  [REALTIME_PUSHER_EVENTS.COMMENT_DELETED]: invalidate,
});

Returns an unsubscribe function for useEffect cleanup.

Private channel auth

  • Route: POST /api/socket/auth (src/app/api/socket/auth/route.ts)
  • Behaviour: Requires a valid session; only authorizes private-user.{authenticatedUserId} (403 for any other private channel name).

pusher-js uses authEndpoint: "/api/socket/auth" and wsHost / key from public env.

Phase 1 — Notifications

After the Inngest handler inserts a notification row, it calls publishMessage(private-user.${recipientId}, REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, { scope: "notifications" }).

RealtimeProvider subscribes with listenChannel on the signed-in user’s private channel and invalidates my-notifications and unread-notification-count.

Phase 2 — Comments

comment.action.ts calls publishMessage on resource.{RESOURCE_TYPE}.{resourceId} with COMMENT_CREATED, COMMENT_UPDATED, or COMMENT_DELETED after each mutation.

CommentSection uses listenChannel on mount and invalidates the comments query on those events.

Public resource.* channels let anonymous readers on a public article receive live comment updates without private-channel auth.

Channel naming

Channel Type Use
private-user.{userId} Private (auth via /api/socket/auth) Notifications; future user-scoped realtime
resource.{TYPE}.{id} Public Comments; future resource-scoped realtime

Updates since the initial merge description: modules under src/lib/pusher/ including realtime-events.ts, typed events shared by listenChannel and publishMessage, object-only listener API, PUSHER_WS_HOST / NEXT_PUBLIC_PUSHER_WS_HOST, and auth at /api/socket/auth.

…tion (Phase 1 & 2)

Agent-Logs-Url: https://github.com/techdiary-dev/techdiary.dev/sessions/b073d05d-7457-4406-b59a-cafb8dac4c4d

Co-authored-by: shoaibsharif <29075110+shoaibsharif@users.noreply.github.com>
Copilot AI changed the title [WIP] Add realtime state management with Soketi and TanStack Query feat: realtime UI updates via Pusher/Soketi + TanStack Query invalidation (Phase 1 & 2) Apr 1, 2026
Copilot AI requested a review from shoaibsharif April 1, 2026 20:32
- Eliminated the unused 'lt' import from the article-cleanup-service.ts file, streamlining the code and improving clarity.
- Deleted the Pusher private-channel auth endpoint to streamline the codebase.
- Updated the Pusher client configuration to use a new auth endpoint and simplified the initialization process, enhancing clarity and maintainability.
- Replaced Pusher client and server imports with a unified publishMessage function for better abstraction.
- Updated environment variable validation to require non-empty values for Pusher-related keys.
- Removed deprecated Pusher client and server files to streamline the codebase.
- Added a comma after the number 4 in the array splice method to ensure correct syntax and prevent potential errors in the ArticleFeed component.
@kingRayhan kingRayhan marked this pull request as ready for review April 2, 2026 20:02
@kingRayhan kingRayhan merged commit bec220f into main Apr 2, 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.

Plan: realtime state (Soketi / Pusher protocol + TanStack Query invalidation)

3 participants