@@ -154,6 +154,8 @@ import type {
154154 SSEInvoiceEvent ,
155155} from "./sse.js" ;
156156import { ConnectionPool } from "./connectionPool.js" ;
157+ import { WebSocketTransport } from "./websocket.js" ;
158+ import type { TransportType , TransportStatus } from "./websocket.js" ;
157159import { snapshotInvoice as _snapshotInvoice } from "./snapshot.js" ;
158160import type { InvoiceSnapshot } from "./snapshot.js" ;
159161import { SimpleCache } from "./cache.js" ;
@@ -327,6 +329,21 @@ export interface StellarSplitClientConfig {
327329 * a MockRpcClient) or alternative transport environments.
328330 */
329331 rpcClient ?: RpcClient ;
332+ /**
333+ * Transport selection for real-time invoice event streaming.
334+ * - `'http'` (default): Use polling-based RPC event fetching.
335+ * - `'websocket'`: Use WebSocket connection to the RPC's event-streaming
336+ * endpoint for pushed events. Falls back to HTTP polling if the WebSocket
337+ * connection fails after 3 reconnect attempts.
338+ */
339+ transport ?: TransportType ;
340+ /**
341+ * Optional WebSocket URL override. When not provided, the WebSocket URL is
342+ * derived from the RPC URL by replacing `https://` with `wss://` (or
343+ * `http://` with `ws://`).
344+ * Only used when `transport: 'websocket'`.
345+ */
346+ wsUrl ?: string ;
330347}
331348
332349/** Network configuration. */
@@ -468,6 +485,10 @@ export class StellarSplitClient {
468485 private _timeoutManager : TimeoutManager | null = null ;
469486 private _traceIdManager = new TraceIdManager ( ) ;
470487 private _injectedRpcClient : RpcClient | null = null ;
488+ private _wsTransport : WebSocketTransport | null = null ;
489+ private _transportType : TransportType = 'http' ;
490+ private _activeTransportType : TransportType = 'http' ;
491+ private _fallbackListeners : Array < ( event : { from : 'websocket' ; to : 'http' } ) => void > = [ ] ;
471492
472493 // eslint-disable-next-line @typescript-eslint/no-explicit-any
473494 private get server ( ) : any {
@@ -640,6 +661,19 @@ export class StellarSplitClient {
640661 this . _idempotency = new IdempotencyManager ( config . idempotency ) ;
641662 }
642663
664+ // WebSocket transport (Issue #377)
665+ if ( config . transport === 'websocket' ) {
666+ this . _transportType = 'websocket' ;
667+ this . _activeTransportType = 'websocket' ;
668+ this . _wsTransport = new WebSocketTransport ( primaryUrl , config . wsUrl ) ;
669+ this . _wsTransport . onFallback ( ( event : { from : 'websocket' ; to : 'http' } ) => {
670+ this . _activeTransportType = 'http' ;
671+ for ( const cb of this . _fallbackListeners ) {
672+ try { cb ( event ) ; } catch { }
673+ }
674+ } ) ;
675+ }
676+
643677 initHealthDashboard ( this . server , this . _dedup ) ;
644678
645679 // Register and initialize config-level plugins
@@ -1447,12 +1481,7 @@ export class StellarSplitClient {
14471481 */
14481482 async getInvoice (
14491483 invoiceId : string ,
1450- opts ?: {
1451- retry ?: PerMethodRetryOptions ;
1452- dedupe ?: boolean ;
1453- traceId ?: string ;
1454- timeout ?: number ;
1455- } ,
1484+ opts ?: { retry ?: PerMethodRetryOptions ; dedupe ?: boolean ; traceId ?: string ; timeout ?: number }
14561485 ) : Promise < Invoice > {
14571486 return this . _withCache ( "getInvoice" , [ invoiceId ] , async ( ) => {
14581487 const fetcher = this . _batcher
@@ -1482,10 +1511,7 @@ export class StellarSplitClient {
14821511 return this . _dedup . getDedupStats ( ) ;
14831512 }
14841513
1485- private async _fetchInvoice (
1486- invoiceId : string ,
1487- traceId ?: string ,
1488- ) : Promise < Invoice > {
1514+ private async _fetchInvoice ( invoiceId : string , traceId ?: string ) : Promise < Invoice > {
14891515 const startTime = Date . now ( ) ;
14901516 const req = {
14911517 method : "getInvoice" ,
@@ -1878,6 +1904,9 @@ export class StellarSplitClient {
18781904 } finally {
18791905 this . _standby ?. stop ( ) ;
18801906
1907+ this . _wsTransport ?. disconnect ( ) ;
1908+ this . _wsTransport = null ;
1909+
18811910 this . _pool ?. dispose ( ) ;
18821911 this . _pool = null ;
18831912
@@ -2579,6 +2608,26 @@ export class StellarSplitClient {
25792608 | ( ( events : SSEInvoiceEvent [ ] ) => void ) ,
25802609 optionsOrInterval ?: Partial < SubscribeToInvoiceOptions > | number ,
25812610 ) : ( ) => void {
2611+ // WebSocket transport: use the active WebSocket connection when configured
2612+ if ( this . _wsTransport && this . _transportType === 'websocket' ) {
2613+ if ( typeof handlerOrCallbacks !== "function" ) {
2614+ throw new ValidationError (
2615+ "WebSocket transport requires a function handler. Callbacks object is not supported."
2616+ ) ;
2617+ }
2618+
2619+ const handler = handlerOrCallbacks as InvoiceEventHandler ;
2620+ const wrappedHandler = ( event : unknown ) => {
2621+ handler ( event as SSEInvoiceEvent ) ;
2622+ } ;
2623+
2624+ this . _wsTransport . subscribe ( invoiceId , wrappedHandler ) ;
2625+
2626+ return ( ) => {
2627+ this . _wsTransport ?. unsubscribe ( invoiceId , wrappedHandler ) ;
2628+ } ;
2629+ }
2630+
25822631 // A function handler with options object selects the SSE transport.
25832632 // A function handler with number interval selects the RPC polling transport (new API).
25842633 // A callbacks object selects the legacy RPC-polling transport.
@@ -2622,6 +2671,28 @@ export class StellarSplitClient {
26222671 ) ;
26232672 }
26242673
2674+ /**
2675+ * Returns the current status of the active transport.
2676+ *
2677+ * When `transport: 'websocket'` was configured, returns `{ type: 'websocket', connected, reconnectAttempts }`.
2678+ * Otherwise returns `{ type: 'http', connected: true, reconnectAttempts: 0 }`.
2679+ */
2680+ getTransportStatus ( ) : TransportStatus {
2681+ if ( this . _wsTransport ) {
2682+ return this . _wsTransport . getStatus ( ) ;
2683+ }
2684+ return { type : 'http' , connected : true , reconnectAttempts : 0 } ;
2685+ }
2686+
2687+ /**
2688+ * Register a callback for the `transport:fallback` event.
2689+ * Fired when the WebSocket transport fails to connect after 3 attempts
2690+ * and the client falls back to HTTP polling.
2691+ */
2692+ onTransportFallback ( cb : ( event : { from : 'websocket' ; to : 'http' } ) => void ) : void {
2693+ this . _fallbackListeners . push ( cb ) ;
2694+ }
2695+
26252696 // ---------------------------------------------------------------------------
26262697 // Issue #3 — offline signing flow
26272698 // ---------------------------------------------------------------------------
0 commit comments