-
Notifications
You must be signed in to change notification settings - Fork 5
feat: reactive potion balances via WebSocket #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
33b866f
f8dabb4
ffede6c
ae2fb12
ef4e4a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| * Channels: | ||
| * - summit: Beast stats updates for summit beast | ||
| * - event: Activity feed from summit_log | ||
| * - consumables: Potion balance updates per owner | ||
| * - supply: Aggregate player-held supply per ERC20 token | ||
| */ | ||
|
|
||
| import { pool } from "../db/client.js"; | ||
|
|
@@ -18,7 +20,7 @@ interface WebSocketLike { | |
| OPEN?: number; | ||
| } | ||
|
|
||
| export type Channel = "summit" | "event"; | ||
| export type Channel = "summit" | "event" | "consumables" | "supply"; | ||
|
|
||
| interface ClientSubscription { | ||
| ws: WebSocketLike; | ||
|
|
@@ -64,6 +66,17 @@ interface EventPayload { | |
| created_at: string; | ||
| } | ||
|
|
||
| interface ConsumablesPayload { | ||
| owner: string; | ||
| xlife_count: number; | ||
| attack_count: number; | ||
| revive_count: number; | ||
| poison_count: number; | ||
| } | ||
|
|
||
| /** Per-ERC20 token supply keyed by token name (e.g. "ATTACK", "REVIVE") */ | ||
| type SupplyPayload = Record<string, number>; | ||
|
|
||
| export class SubscriptionHub { | ||
| private clients: Map<string, ClientSubscription> = new Map(); | ||
| private pgClient: pg.PoolClient | null = null; | ||
|
|
@@ -103,8 +116,10 @@ export class SubscriptionHub { | |
|
|
||
| await this.pgClient.query("LISTEN summit_update"); | ||
| await this.pgClient.query("LISTEN summit_log_insert"); | ||
| await this.pgClient.query("LISTEN consumables_update"); | ||
| await this.pgClient.query("LISTEN consumables_supply"); | ||
|
|
||
| console.log("[SubscriptionHub] Listening on: summit_update, summit_log_insert"); | ||
| console.log("[SubscriptionHub] Listening on: summit_update, summit_log_insert, consumables_update, consumables_supply"); | ||
| } catch (error) { | ||
| console.error("[SubscriptionHub] Failed to connect:", error); | ||
| this.reconnect(); | ||
|
|
@@ -155,13 +170,19 @@ export class SubscriptionHub { | |
| case "summit_log_insert": | ||
| this.broadcast("event", payload as EventPayload); | ||
| break; | ||
| case "consumables_update": | ||
| this.broadcast("consumables", payload as ConsumablesPayload); | ||
| break; | ||
|
Comment on lines
+173
to
+175
|
||
| case "consumables_supply": | ||
| this.broadcast("supply", payload as SupplyPayload); | ||
| break; | ||
| } | ||
| } catch (error) { | ||
| console.error("[SubscriptionHub] Failed to parse notification:", error); | ||
| } | ||
| } | ||
|
|
||
| private broadcast(channel: Channel, data: SummitPayload | EventPayload): void { | ||
| private broadcast(channel: Channel, data: SummitPayload | EventPayload | ConsumablesPayload | SupplyPayload): void { | ||
| const message = JSON.stringify({ type: channel, data }); | ||
|
|
||
| let sentCount = 0; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
consumableschannel broadcasts potion balance updates for all users to every client subscribed to the channel. While the client-sideGameDirectorfilters these messages to only process updates for the connected wallet (line 422), a malicious client can subscribe to the channel and monitor the real-time balance changes of all players. This information exposure can be used for tactical advantage in a competitive game. To remediate this, implement server-side filtering: clients should provide the owner address they are interested in when subscribing, and thebroadcastmethod should only send updates to clients whose interested address matches the owner in the payload.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declining this one — this follows the same broadcast pattern as
summitandeventchannels, which also send all data to all subscribers. Token balances are on-chain public data (anyone can callbalanceOf), so there's no real information exposure. Adding server-side owner filtering would introduce significant complexity (subscription state per-owner, address normalization in the hub) for no meaningful security gain. If we decide to add per-owner filtering later, it should apply to all three channels uniformly.