1
1
import WebSocket from "isomorphic-ws" ;
2
+ import { dummyLogger , type Logger } from "ts-log" ;
2
3
3
4
import {
4
5
BINARY_UPDATE_FORMAT_MAGIC ,
@@ -9,6 +10,7 @@ import {
9
10
type Response ,
10
11
SOLANA_FORMAT_MAGIC_BE ,
11
12
} from "./protocol.js" ;
13
+ import { WebSocketPool } from "./socket/web-socket-pool.js" ;
12
14
13
15
export type BinaryResponse = {
14
16
subscriptionId : number ;
@@ -28,52 +30,58 @@ const UINT32_NUM_BYTES = 4;
28
30
const UINT64_NUM_BYTES = 8 ;
29
31
30
32
export class PythLazerClient {
31
- ws : WebSocket ;
33
+ wsp : WebSocketPool ;
32
34
33
- constructor ( url : string , token : string ) {
34
- const finalUrl = new URL ( url ) ;
35
- finalUrl . searchParams . append ( "ACCESS_TOKEN" , token ) ;
36
- this . ws = new WebSocket ( finalUrl ) ;
35
+ /**
36
+ * Creates a new PythLazerClient instance.
37
+ * @param urls - List of WebSocket URLs of the Pyth Lazer service
38
+ * @param token - The access token for authentication
39
+ * @param numConnections - The number of parallel WebSocket connections to establish (default: 3). A higher number gives a more reliable stream.
40
+ * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
41
+ */
42
+ constructor (
43
+ urls : string [ ] ,
44
+ token : string ,
45
+ numConnections = 3 ,
46
+ logger : Logger = dummyLogger
47
+ ) {
48
+ this . wsp = new WebSocketPool ( urls , token , numConnections , logger ) ;
37
49
}
38
50
39
51
addMessageListener ( handler : ( event : JsonOrBinaryResponse ) => void ) {
40
- this . ws . addEventListener ( "message" , ( event : WebSocket . MessageEvent ) => {
41
- if ( typeof event . data == "string" ) {
52
+ this . wsp . addMessageListener ( ( data : WebSocket . Data ) => {
53
+ if ( typeof data == "string" ) {
42
54
handler ( {
43
55
type : "json" ,
44
- value : JSON . parse ( event . data ) as Response ,
56
+ value : JSON . parse ( data ) as Response ,
45
57
} ) ;
46
- } else if ( Buffer . isBuffer ( event . data ) ) {
58
+ } else if ( Buffer . isBuffer ( data ) ) {
47
59
let pos = 0 ;
48
- const magic = event . data
49
- . subarray ( pos , pos + UINT32_NUM_BYTES )
50
- . readUint32BE ( ) ;
60
+ const magic = data . subarray ( pos , pos + UINT32_NUM_BYTES ) . readUint32BE ( ) ;
51
61
pos += UINT32_NUM_BYTES ;
52
62
if ( magic != BINARY_UPDATE_FORMAT_MAGIC ) {
53
63
throw new Error ( "binary update format magic mismatch" ) ;
54
64
}
55
65
// TODO: some uint64 values may not be representable as Number.
56
66
const subscriptionId = Number (
57
- event . data . subarray ( pos , pos + UINT64_NUM_BYTES ) . readBigInt64BE ( )
67
+ data . subarray ( pos , pos + UINT64_NUM_BYTES ) . readBigInt64BE ( )
58
68
) ;
59
69
pos += UINT64_NUM_BYTES ;
60
70
61
71
const value : BinaryResponse = { subscriptionId } ;
62
- while ( pos < event . data . length ) {
63
- const len = event . data
64
- . subarray ( pos , pos + UINT16_NUM_BYTES )
65
- . readUint16BE ( ) ;
72
+ while ( pos < data . length ) {
73
+ const len = data . subarray ( pos , pos + UINT16_NUM_BYTES ) . readUint16BE ( ) ;
66
74
pos += UINT16_NUM_BYTES ;
67
- const magic = event . data
75
+ const magic = data
68
76
. subarray ( pos , pos + UINT32_NUM_BYTES )
69
77
. readUint32BE ( ) ;
70
78
if ( magic == EVM_FORMAT_MAGIC ) {
71
- value . evm = event . data . subarray ( pos , pos + len ) ;
79
+ value . evm = data . subarray ( pos , pos + len ) ;
72
80
} else if ( magic == SOLANA_FORMAT_MAGIC_BE ) {
73
- value . solana = event . data . subarray ( pos , pos + len ) ;
81
+ value . solana = data . subarray ( pos , pos + len ) ;
74
82
} else if ( magic == PARSED_FORMAT_MAGIC ) {
75
83
value . parsed = JSON . parse (
76
- event . data . subarray ( pos + UINT32_NUM_BYTES , pos + len ) . toString ( )
84
+ data . subarray ( pos + UINT32_NUM_BYTES , pos + len ) . toString ( )
77
85
) as ParsedPayload ;
78
86
} else {
79
87
throw new Error ( "unknown magic: " + magic . toString ( ) ) ;
@@ -87,7 +95,22 @@ export class PythLazerClient {
87
95
} ) ;
88
96
}
89
97
90
- send ( request : Request ) {
91
- this . ws . send ( JSON . stringify ( request ) ) ;
98
+ async subscribe ( request : Request ) : Promise < void > {
99
+ if ( request . type !== "subscribe" ) {
100
+ throw new Error ( "Request must be a subscribe request" ) ;
101
+ }
102
+ await this . wsp . addSubscription ( request ) ;
103
+ }
104
+
105
+ async unsubscribe ( subscriptionId : number ) : Promise < void > {
106
+ await this . wsp . removeSubscription ( subscriptionId ) ;
107
+ }
108
+
109
+ async send ( request : Request ) : Promise < void > {
110
+ await this . wsp . sendRequest ( request ) ;
111
+ }
112
+
113
+ shutdown ( ) : void {
114
+ this . wsp . shutdown ( ) ;
92
115
}
93
116
}
0 commit comments