Skip to content

Commit bed7e5d

Browse files
committed
chore: more docs on ws acct susbcriber v2
1 parent 7f60edd commit bed7e5d

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed

sdk/src/accounts/webSocketAccountSubscriberV2.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,54 @@ import {
2121
import { PublicKey } from '@solana/web3.js';
2222
import bs58 from 'bs58';
2323

24+
/**
25+
* WebSocketAccountSubscriberV2
26+
*
27+
* High-level overview
28+
* - WebSocket-first subscriber for a single Solana account with optional
29+
* polling safeguards when the WS feed goes quiet.
30+
* - Emits decoded updates via `onChange` and maintains the latest
31+
* `{buffer, slot}` and decoded `{data, slot}` internally.
32+
*
33+
* Why polling if this is a WebSocket subscriber?
34+
* - Under real-world conditions, WS notifications can stall or get dropped.
35+
* - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
36+
* - resubscribe to the WS stream (default), or
37+
* - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
38+
* account via RPC to check for missed changes.
39+
* - Polling compares the fetched buffer to the last known buffer. If different
40+
* at an equal-or-later slot, it indicates a missed update and we resubscribe
41+
* to WS to restore a clean stream.
42+
*
43+
* Initial fetch (on subscribe)
44+
* - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
45+
* emit the latest account state, ensuring consumers start from ground truth
46+
* even before WS events arrive.
47+
*
48+
* Continuous polling (opt-in)
49+
* - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
50+
* polling loop that periodically `fetch()`es the account and checks for
51+
* changes. On change, polling stops and we resubscribe to WS.
52+
* - If not set (default), the inactivity timeout immediately triggers a WS
53+
* resubscription (no polling loop).
54+
*
55+
* Account focus
56+
* - This class tracks exactly one account — the one passed to the constructor —
57+
* which is by definition the account the consumer cares about. The extra
58+
* logic is narrowly scoped to this account to minimize overhead.
59+
*
60+
* Tuning knobs
61+
* - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
62+
* - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
63+
* - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
64+
* - `resubOpts.logResubMessages`: verbose logs for diagnostics.
65+
* - `commitment`: WS/RPC commitment used for reads and notifications.
66+
* - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
67+
*
68+
* Implementation notes
69+
* - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
70+
* program provider’s RPC endpoint. Handles base58/base64 encoded data.
71+
*/
2472
export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
2573
dataAndSlot?: DataAndSlot<T>;
2674
bufferAndSlot?: BufferAndSlot;
@@ -49,6 +97,19 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
4997
>['rpcSubscriptions'];
5098
private abortController?: AbortController;
5199

100+
/**
101+
* Create a single-account WebSocket subscriber with optional polling fallback.
102+
*
103+
* @param accountName Name of the Anchor account type (used for default decode).
104+
* @param program Anchor `Program` used for decoding and provider access.
105+
* @param accountPublicKey Public key of the account to track.
106+
* @param decodeBuffer Optional custom decode function; if omitted, uses
107+
* program coder to decode `accountName`.
108+
* @param resubOpts Resubscription/polling options. See class docs.
109+
* @param commitment Commitment for WS and RPC operations.
110+
* @param rpcSubscriptions Optional override/injection for testing.
111+
* @param rpc Optional override/injection for testing.
112+
*/
52113
public constructor(
53114
accountName: string,
54115
program: Program,
@@ -137,6 +198,19 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
137198
}
138199

139200
async subscribe(onChange: (data: T) => void): Promise<void> {
201+
/**
202+
* Start the WebSocket subscription and (optionally) setup inactivity
203+
* fallback.
204+
*
205+
* Flow
206+
* - If we do not have initial state, perform a one-time `fetch()` to seed
207+
* internal buffers and emit current data.
208+
* - Subscribe to account notifications via WS.
209+
* - If `resubOpts.resubTimeoutMs` is set, schedule an inactivity timeout.
210+
* When it fires:
211+
* - if `usePollingInsteadOfResub` is true, start polling loop;
212+
* - otherwise, resubscribe to WS immediately.
213+
*/
140214
if (this.listenerId != null || this.isUnsubscribing) {
141215
if (this.resubOpts?.logResubMessages) {
142216
console.log(
@@ -192,6 +266,11 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
192266
}
193267

194268
protected setTimeout(): void {
269+
/**
270+
* Schedule inactivity handling. If WS is quiet for
271+
* `resubOpts.resubTimeoutMs` and `receivingData` is true, trigger either
272+
* a polling loop or a resubscribe depending on options.
273+
*/
195274
if (!this.onChange) {
196275
throw new Error('onChange callback function must be set');
197276
}
@@ -244,6 +323,11 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
244323
);
245324
}
246325

326+
/**
327+
* Start the polling loop (single-account).
328+
* - Periodically calls `fetch()` and compares buffers to detect changes.
329+
* - On detected change, stops polling and resubscribes to WS.
330+
*/
247331
private startPolling(): void {
248332
const pollingInterval = this.resubOpts?.pollingIntervalMs || 30000; // Default to 30s
249333

@@ -306,6 +390,10 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
306390
}
307391
}
308392

393+
/**
394+
* Fetch the current account state via RPC and process it through the same
395+
* decoding and update pathway as WS notifications.
396+
*/
309397
async fetch(): Promise<void> {
310398
// Use gill's rpc for fetching account info
311399
const accountAddress = this.accountPublicKey.toBase58() as Address;
@@ -400,6 +488,11 @@ export class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
400488
}
401489

402490
unsubscribe(onResub = false): Promise<void> {
491+
/**
492+
* Stop timers, polling, and WS subscription.
493+
* - When called during a resubscribe (`onResub=true`), we preserve
494+
* `resubOpts.resubTimeoutMs` for the restarted subscription.
495+
*/
403496
if (!onResub && this.resubOpts) {
404497
this.resubOpts.resubTimeoutMs = undefined;
405498
}

0 commit comments

Comments
 (0)