Skip to content

feat: server-side owner filtering for WebSocket channels#133

Open
loothero wants to merge 1 commit intonextfrom
ws-channel-filters
Open

feat: server-side owner filtering for WebSocket channels#133
loothero wants to merge 1 commit intonextfrom
ws-channel-filters

Conversation

@loothero
Copy link
Member

@loothero loothero commented Mar 9, 2026

Summary

  • Add consumables channel to the WebSocket subscription hub (PG LISTEN on consumables_update)
  • Add optional filters parameter to subscribe messages, enabling per-subscription server-side filtering
  • summit and consumables channels support owner filter to receive only updates matching a specific address
  • event channel remains unfiltered (global activity feed)
  • Backwards compatible: omitting filters preserves existing broadcast-all behavior
  • Client updated to pass wallet address as owner filter via addAddressPadding
  • Client retains client-side owner check in handleConsumables as a safety fallback

Changed files

API:

  • api/src/ws/subscriptions.ts -- ChannelFilter type, filters stored per client per channel, broadcast checks owner match
  • api/src/ws/subscriptions.test.ts -- 13 new tests (consumables channel + channel filter behavior)
  • api/src/index.ts -- root endpoint docs updated with filter example
  • api/AGENTS.md -- WebSocket docs updated

Client:

  • client/src/hooks/useWebSocket.ts -- ConsumablesData, ChannelFilter types, filters option, onConsumables callback
  • client/src/hooks/useWebSocket.test.ts -- new test file (5 tests including filter serialization)
  • client/src/contexts/GameDirector.tsx -- handleConsumables handler, wsFilters with padded owner address
  • client/src/contexts/GameDirector.test.tsx -- 4 new tests (consumables handling + filter passing)

Test plan

  • cd api && pnpm exec tsc --noEmit -- passes
  • cd api && pnpm test -- 92 tests pass (34 subscription tests including 13 new)
  • cd client && pnpm lint -- passes
  • cd client && pnpm exec tsc -b -- passes
  • cd client && pnpm test -- 192 tests pass (including 9 new)
  • Manual: subscribe without filters, verify all updates received (backwards compat)
  • Manual: subscribe with owner filter, verify only matching updates received

Generated with Claude Code

Add consumables channel support and optional per-subscription filters
to the WebSocket subscription hub. Clients can now subscribe with an
owner filter for summit and consumables channels to receive only
updates relevant to their wallet address.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 9, 2026 12:47
@vercel
Copy link

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
summit Ready Ready Preview, Comment Mar 9, 2026 0:47am

Request Review

@railway-app railway-app bot temporarily deployed to Summit stack / summit-pr-133 March 9, 2026 12:47 Destroyed
@railway-app
Copy link

railway-app bot commented Mar 9, 2026

🚅 Deployed to the summit-pr-133 environment in Summit stack

Service Status Web Updated (UTC)
summit-api ◻️ Removed (View Logs) Web Mar 11, 2026 at 1:04 am
1 service not affected by this PR
  • summit-indexer

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2107bca2-1f0e-462a-aeff-690e4ff4f3a0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ws-channel-filters

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the real-time data delivery system by introducing a dedicated WebSocket channel for consumable item updates and implementing server-side filtering capabilities. This allows for more targeted and efficient data distribution, ensuring clients only receive information relevant to their specific interests, such as a particular owner's assets. The changes improve network efficiency and user experience while maintaining compatibility with existing client implementations.

Highlights

  • New Consumables WebSocket Channel: A new consumables WebSocket channel has been introduced to provide real-time updates on potion balances per owner.
  • Server-Side Owner Filtering: Implemented server-side filtering for summit and consumables WebSocket channels, allowing clients to subscribe to updates specific to a particular owner address. The event channel remains unfiltered.
  • Client-Side Integration: The client-side useWebSocket hook and GameDirector context have been updated to utilize the new consumables channel and dynamically pass owner-based filters for subscriptions when a wallet is connected.
  • Backward Compatibility: The new filtering mechanism is backward compatible; clients omitting the filters parameter will continue to receive all broadcast updates for the subscribed channels.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • api/AGENTS.md
    • Updated WebSocket documentation to include the new consumables channel.
    • Added examples for subscribing with filters and clarified supported filter types.
    • Updated the 'Real-Time Pattern' description to include consumables_update notifications.
  • api/src/index.ts
    • Updated the root endpoint documentation to reflect the new consumables channel and provide an example for subscribing with filters.
  • api/src/ws/subscriptions.test.ts
    • Added new test cases for subscribing and unsubscribing from the consumables channel.
    • Introduced comprehensive tests for channel filtering, covering filter storage, broadcasting to matching owners, preventing broadcasts to non-matching owners, ensuring the event channel remains unfiltered, clearing filters on unsubscribe, and normalizing owner addresses.
  • api/src/ws/subscriptions.ts
    • Added consumables to the Channel type and defined the ChannelFilter interface.
    • Modified ClientSubscription to store channel-specific filters.
    • Added ConsumablesPayload interface and configured the PostgreSQL client to listen for consumables_update.
    • Extended the broadcast method to accept ConsumablesPayload and implemented owner-based filtering logic for summit and consumables channels.
    • Updated the subscribe method to accept and store channel-specific filters, normalizing owner addresses to lowercase.
    • Ensured the unsubscribe method clears associated filters and modified handleMessage to pass filters in the subscribe message and response.
  • client/src/contexts/GameDirector.test.tsx
    • Added mock implementations for useWebSocket and useAccount for testing purposes.
    • Introduced tests to verify handleConsumables correctly updates token balances for the connected wallet and ignores updates for different wallets.
    • Added tests to confirm that owner filters are correctly passed to useWebSocket when a wallet is connected and not passed when no wallet is connected.
  • client/src/contexts/GameDirector.tsx
    • Imported ChannelFilter and ConsumablesData types.
    • Implemented handleConsumables function to update token balances based on incoming consumables data, including a client-side safety check for the owner.
    • Created wsFilters object to dynamically generate owner-based filters for summit and consumables channels when an account is connected.
    • Updated the useWebSocket hook call to include consumables in channels, pass wsFilters, and register the onConsumables callback.
  • client/src/hooks/useWebSocket.test.ts
    • Created a new test file for the useWebSocket hook with a MockWebSocket class for controlled testing.
    • Added tests to verify the onConsumables callback is triggered for consumables messages and not for other channel messages.
    • Confirmed that consumables is included in the subscribe message and verified that filters are correctly included in the subscribe message when provided and omitted when not.
  • client/src/hooks/useWebSocket.ts
    • Extended the Channel type to include consumables and defined ConsumablesData and ChannelFilter interfaces.
    • Updated UseWebSocketOptions to include filters and an onConsumables callback.
    • Modified callbacksRef and its useEffect dependency array to include onConsumables.
    • Extended handleMessage to dispatch onConsumables for consumables type messages.
    • Modified the connect function to include filters in the subscribe message payload if they are provided.
    • Updated the main connection useEffect dependency array to include filters.
Activity
  • The author, loothero, created this pull request to introduce server-side owner filtering for WebSocket channels and add a new consumables channel.
  • The pull request includes a detailed summary, lists of changed files, and a comprehensive test plan.
  • Automated checks for tsc, pnpm test, and pnpm lint passed for both api and client directories.
  • Manual testing steps are outlined for verifying backward compatibility and filter functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @loothero's task —— View job


Claude Review - General Engineering Review

Todo List

  • Analyze PR diff for files outside main app packages
  • Review CI workflows, scripts, tooling, and shared config changes
  • Identify correctness, reliability, performance, and maintainability issues
  • Format findings according to review template

run=22854132982 attempt=1 sha=4c8fa1ed5b6a60824eb419843cc7651498084530 scope=general

Review Findings

After analyzing the PR diff with focus on files outside the main app packages (contracts/, client/, indexer/, api/), I found that this PR contains no changes to:

  • CI workflows (.github/workflows/)
  • Root-level configuration files
  • Scripts or tooling directories
  • Docker files or deployment configuration
  • Repository documentation or setup files
  • Shared configuration that could affect build, test, or deployment processes

All changes in this PR are contained within the api/ and client/ directories, which are outside my review scope for this general engineering review.

No issues found.

Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO


@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @loothero's task —— View job


Claude Review - Indexer/API Review

run=22854132982 attempt=1 sha=4c8fa1ed5b6a60824eb419843cc7651498084530 scope=indexer-api

Todo List

  • Get git diff for indexer/ and api/ directories
  • Review api/src/ws/subscriptions.ts changes
  • Check test coverage in subscriptions.test.ts
  • Review other API changes
  • Analyze for the 7 focus areas
  • Post comprehensive review findings

Findings

[MEDIUM] api/src/ws/subscriptions.ts:175-176 - Missing PostgreSQL notification source validation
Impact: The consumables_update channel is added without verification that the corresponding PostgreSQL trigger/notification is implemented in the indexer. If the indexer doesn't emit consumables_update notifications, clients will subscribe to a silent channel.
Fix: Verify that the indexer properly implements the consumables_update PostgreSQL notification trigger, or add documentation confirming this dependency.

[LOW] api/src/ws/subscriptions.ts:188-190 - Potential null owner handling inconsistency
Impact: The owner filtering logic extracts owner from payloads but uses optional chaining that could result in null being passed to toLowerCase(). While this is handled correctly, the type annotation suggests the owner should always be present for filterable channels.
Fix: Consider making the owner field required in ConsumablesPayload interface or add explicit null checks before applying filters.

[LOW] api/src/ws/subscriptions.ts:286-292 - Filter mutation risk during subscription
Impact: The code creates a shallow copy of filters ({ ...filters[channel] }) but only normalizes the owner field. If filters are extended with nested objects in the future, this pattern could lead to shared reference mutations.
Fix: Consider using structured cloning or deep copy for filter objects, or document that filters must be flat objects.

[INFO] api/src/ws/subscriptions.ts:321 - Subscription response includes echoed filters
Impact: The response to subscription includes the filters object, which echoes back the client's filter request. This increases message size but provides confirmation of applied filters.
Fix: No action required - this appears intentional for client-side validation.

Analysis Summary

The WebSocket filtering implementation is generally well-structured and follows good practices:

  1. Data Correctness: The owner filtering logic correctly normalizes addresses to lowercase for consistent matching. Filter cleanup on unsubscribe prevents stale filter state.

  2. Filtering Efficiency: Server-side filtering is properly implemented for summit and consumables channels while preserving the global nature of the event channel. This reduces bandwidth for clients only interested in their own data.

  3. Backward Compatibility: The implementation correctly maintains backwards compatibility by treating missing filters as "receive all" behavior.

  4. Test Coverage: The test suite comprehensively covers the filtering behavior with 13 new tests including edge cases like case-insensitive matching and filter cleanup.

  5. Operational Safety: The implementation includes proper error handling and client cleanup for dead connections.

The main concern is the missing verification that the consumables_update PostgreSQL notification is properly implemented in the indexer layer, as this PR only shows the API consumer side.

Summary: 0 CRITICAL, 0 HIGH, 1 MEDIUM, 2 LOW, 1 INFO


@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Codex Review - General Engineering Review

No issues found.
Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @loothero's task —— View job


Claude Review - React/Frontend Review

Todo List

  • Read and analyze client-side changes in PR diff
  • Review useWebSocket.ts changes for type safety and React hooks correctness
  • Review GameDirector.tsx changes for wallet integration and state management
  • Review test files for coverage and correctness
  • Check for the 8 focus areas: type safety, React hooks, wallet safety, query optimization, domain logic, UX reliability, performance, validation
  • Format findings with severity levels and post review

run=22854132982 attempt=1 sha=4c8fa1ed5b6a60824eb419843cc7651498084530 scope=client

Review Findings

[INFO] client/src/hooks/useWebSocket.ts:222 - useEffect dependency array includes filters but filters object may change reference on every render
Impact: Could cause unnecessary reconnections if filters object isn't memoized
Fix: Consider memoizing wsFilters in GameDirector.tsx with useMemo based on account.address

[MEDIUM] client/src/contexts/GameDirector.tsx:423 - Double address normalization creates redundant safety check
Impact: Minor performance overhead from calling addAddressPadding twice on same address, but functionally safe
Fix: Remove client-side check since server-side filtering is primary: if (addAddressPadding(account.address) !== addAddressPadding(data.owner)) return; → trust server-side filtering or normalize once

[LOW] client/src/contexts/GameDirector.tsx:434-440 - wsFilters object recreated on every render when account exists
Impact: Unnecessary object creation and potential useWebSocket reconnections
Fix: Memoize wsFilters with useMemo: const wsFilters = useMemo(() => account?.address ? { summit: { owner: addAddressPadding(account.address) }, consumables: { owner: addAddressPadding(account.address) } } : undefined, [account?.address]);

Positive Observations

Type Safety: All new interfaces (ConsumablesData, ChannelFilter) are well-defined with explicit types
React Hooks Correctness: useEffect dependency arrays properly include all relevant dependencies
Backwards Compatibility: Optional filters parameter maintains existing API compatibility
Error Handling: JSON.parse in handleMessage is properly wrapped in try-catch
Test Coverage: Comprehensive test coverage for new functionality including edge cases
State Management: handleConsumables correctly updates token balances via functional state update
Client-Side Safety: Defensive client-side owner checks provide safety fallback
WebSocket Lifecycle: Connection handling, reconnection logic, and cleanup remain robust

Technical Analysis

Address Normalization: The use of addAddressPadding from starknet library ensures consistent address format for both client and server filtering. This is the correct approach for Starknet address handling.

WebSocket Filter Design: The server-side filtering architecture is well-designed - filters are passed during subscription and applied on the server, with client-side checks as safety fallbacks.

State Consistency: The integration maintains consistency with existing token balance management patterns and properly integrates with the Controller's setTokenBalances function.

Memory Management: Proper useRef usage for callbacks prevents stale closure issues, and cleanup in useEffect prevents memory leaks.

Summary

3 minor findings: 0 CRITICAL, 0 HIGH, 1 MEDIUM, 1 LOW, 1 INFO

The implementation is solid with good type safety, proper React patterns, and comprehensive testing. The main concerns are minor optimization opportunities around object recreation patterns.


@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

❌ Patch coverage is 92.59259% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
api/src/ws/subscriptions.ts 84.21% 0 Missing and 3 partials ⚠️
client/src/contexts/GameDirector.tsx 95.23% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Codex Review - Indexer/API Review

[HIGH] api/src/ws/subscriptions.ts:122 - consumables_update is now LISTENed to, but the scoped PR diff has no indexer/** migration/trigger change that emits pg_notify('consumables_update', ...).
Impact: consumables WS subscriptions can stay silent in production (unit tests pass because they inject notifications directly).
Fix: add an indexer SQL migration/trigger to publish consumables_update on consumables row changes, and include replay/integration verification.

[HIGH] api/src/ws/subscriptions.ts:200 - Owner filter check is skipped when payload owner is null (payloadOwner !== null guard), so filtered clients still receive those updates.
Impact: per-owner filtering is violated for summit updates with null owner (possible via left join), causing incorrect cross-owner delivery.
Fix: enforce strict match whenever a filter exists, e.g. if (filter?.owner && filter.owner !== payloadOwner) continue; (or explicitly drop null-owner payloads for filtered subscribers).

[MEDIUM] api/src/ws/subscriptions.ts:289 - Filter owner normalization is lowercase-only, not canonical Starknet normalization (0x + 64 hex chars).
Impact: equivalent addresses with different padding formats won’t match, causing silent missed updates.
Fix: normalize both filter owner and payload owner with shared normalizeAddress before comparison.

[LOW] api/src/ws/subscriptions.ts:286 - Re-subscribing to an already subscribed channel without a filter does not clear a previously stored filter.
Impact: clients can remain unintentionally filtered unless they unsubscribe first; this contradicts the PR’s “omit filters => broadcast-all” assumption for this flow.
Fix: when subscribing a channel with no provided filter, call client.filters.delete(channel); add a regression test for subscribe(filtered) then subscribe(unfiltered) without unsubscribe.

Summary: 0 CRITICAL, 2 HIGH, 1 MEDIUM, 1 LOW, 0 INFO

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c8fa1ed5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const wsFilters: Partial<Record<"summit" | "event" | "consumables", ChannelFilter>> | undefined =
account?.address
? {
summit: { owner: addAddressPadding(account.address) },

Choose a reason for hiding this comment

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

P1 Badge Remove owner filter from summit subscription

Applying summit: { owner: ... } here causes connected players to stop receiving summit updates whenever the current summit beast is owned by someone else, because the API now filters by owner before broadcasting. GameDirector depends on those global summit updates to keep the live summit state fresh (it only fetches on mount), so most connected users will see stale summit health/holder data until a reload.

Useful? React with 👍 / 👎.

Comment on lines +171 to +173
if (filters && Object.keys(filters).length > 0) {
subscribeMsg.filters = filters;
}

Choose a reason for hiding this comment

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

P2 Badge Resubscribe when channel filters change

Filters are only attached to the subscribe payload during ws.onopen, so if the caller changes filters later (for example wallet connect/disconnect or account switch), the server-side subscription remains stale for the lifetime of that socket. In this PR that means owner-scoped streams can keep targeting the previous address and the new account can miss consumables updates until a reconnect happens.

Useful? React with 👍 / 👎.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Codex Review - React/Frontend Review

[HIGH] client/src/contexts/GameDirector.tsx:437 - summit channel is now owner-filtered, which prevents connected users from receiving summit updates unless they currently own the summit beast.
Impact: most connected players will keep stale summit state (owner/health/poison/timestamps) after the initial fetch, so live gameplay UI can drift from on-chain reality.
Fix: do not apply owner filter to summit in wsFilters; keep summit unfiltered and only filter consumables (or use a separate unfiltered summit subscription).

[MEDIUM] client/src/hooks/useWebSocket.ts:252 - WebSocket lifecycle effect ignores channels/filters changes ([url, enabled] only, exhaustive-deps disabled), so updated subscription filters are not applied after initial connect.
Impact: if wallet/account changes after mount, the socket does not resubscribe with new owner filters; this can leave the client on stale/unintended filter state (including missing consumables updates after account switch).
Fix: make reconnect/resubscribe deterministic when subscription config changes (e.g., effect depends on connect/disconnect with stable memoized channels/filters, or explicitly send unsubscribe/subscribe on channels/filters change).

[LOW] client/src/contexts/GameDirector.tsx:445 - consumables channel is always subscribed even when no wallet is connected, while handleConsumables immediately returns in that state.
Impact: unnecessary real-time traffic and server fanout for disconnected/spectator sessions, with avoidable client processing.
Fix: conditionally include consumables in channels only when account?.address exists.

Summary: 0 CRITICAL, 1 HIGH, 1 MEDIUM, 1 LOW, 0 INFO

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces server-side filtering for WebSocket channels, a valuable feature for reducing client-side processing and network traffic. The implementation is well-done, adding a new consumables channel and filters to subscriptions, with comprehensive updates to documentation and tests on both the client and server. I've identified one potential issue in the server-side filtering logic that could cause messages with no owner to be incorrectly sent to filtered clients. Once that is addressed, this will be a solid addition.


// Apply owner filter for summit and consumables channels
const filter = client.filters.get(channel);
if (filter?.owner && payloadOwner !== null && filter.owner !== payloadOwner) {

Choose a reason for hiding this comment

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

high

The current filtering logic may not behave as expected for payloads without an owner. If a client subscribes with an owner filter, they will still receive messages for the summit and consumables channels if the message payload has a null or undefined owner. This is because the payloadOwner !== null check short-circuits the filter logic.

A client filtering by a specific owner should likely only receive messages that match that owner, and messages without an owner should be skipped. You can fix this by removing the payloadOwner !== null check.

It would also be beneficial to add a test case for this scenario (a client with an owner filter receiving a message with a null owner) to prevent regressions.

Suggested change
if (filter?.owner && payloadOwner !== null && filter.owner !== payloadOwner) {
if (filter?.owner && filter.owner !== payloadOwner) {

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

This PR adds a new consumables WebSocket channel for real-time potion balance updates and introduces server-side owner-based filtering for WebSocket subscriptions. The API's SubscriptionHub listens on the new consumables_update PG NOTIFY channel and applies per-client owner filters during broadcast. The client is updated to subscribe to the new channel, pass wallet-address filters, and process consumable balance updates in GameDirector.

Changes:

  • Added consumables channel with ConsumablesPayload type, PG LISTEN, and broadcast support in the API's SubscriptionHub, along with a ChannelFilter mechanism that allows per-subscription owner filtering on summit and consumables channels
  • Updated the client useWebSocket hook to support filters and onConsumables, and wired GameDirector to pass owner filters and handle consumable balance updates with a client-side safety fallback
  • Added comprehensive tests: 13 new server-side tests and 9 new client-side tests covering channel subscription, filter behavior, and normalization

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
api/src/ws/subscriptions.ts Core: ChannelFilter, filters map per client, owner-aware broadcast, LISTEN on consumables_update
api/src/ws/subscriptions.test.ts 13 new tests for consumables channel and filter behavior
api/src/index.ts Root endpoint docs updated with new channel and filter example
api/AGENTS.md WebSocket documentation updated with consumables channel and filter info
client/src/hooks/useWebSocket.ts Hook updated with ConsumablesData, ChannelFilter types, filter serialization
client/src/hooks/useWebSocket.test.ts New test file with 5 tests for consumables and filter serialization
client/src/contexts/GameDirector.tsx handleConsumables handler, wsFilters with padded owner address
client/src/contexts/GameDirector.test.tsx 4 new tests for consumables handling and filter passing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +434 to +440
const wsFilters: Partial<Record<"summit" | "event" | "consumables", ChannelFilter>> | undefined =
account?.address
? {
summit: { owner: addAddressPadding(account.address) },
consumables: { owner: addAddressPadding(account.address) },
}
: undefined;
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

Bug: Applying an owner filter on the summit channel will cause the client to only receive summit updates when the connected wallet's beast is on the summit. The summit is a shared resource — all clients need to see every summit update to keep the game state current (e.g., tracking when the summit beast changes, marking previous beasts as dead). The wsFilters object should not include a filter for summit; only consumables should have an owner filter.

Copilot uses AI. Check for mistakes.
updateConnectionState("disconnected");
}
}, [url, enabled, channels, handleMessage, updateConnectionState]);
}, [url, enabled, channels, filters, handleMessage, updateConnectionState]);
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The filters object is included in the connect dependency array, but the useEffect that triggers connection (line 243-252) only depends on [url, enabled]. This means if the user connects their wallet after the WebSocket is already open, the new filters won't be sent to the server until the next natural reconnection (e.g., network drop). The client-side fallback check in handleConsumables mitigates this, but the server-side filtering won't be active until reconnection.

Consider either: (1) storing filters in a ref (like callbacks) to avoid recreating connect, or (2) adding logic to send a re-subscribe message when filters change on an already-open connection, rather than requiring a full reconnection.

Copilot uses AI. Check for mistakes.
Comment on lines +319 to +321
const filters = data.filters || {};
this.subscribe(id, subChannels, filters);
if (!this.send(id, client.ws, JSON.stringify({ type: "subscribed", channels: subChannels, filters }))) {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The subscribed confirmation echoes back the raw filters object from the client (data.filters) rather than the normalized version stored internally (where owner is lowercased). This means the response may show "owner": "0xAAA" while the server actually stores and uses "owner": "0xaaa". Consider echoing the normalized filters from the subscription state, or at minimum document that the echoed filters may differ from the stored ones.

Copilot uses AI. Check for mistakes.
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