Skip to content

Commit 5aec239

Browse files
committed
feat: improve promise handling, fix eslint
1 parent 1e1c2ea commit 5aec239

File tree

4 files changed

+129
-94
lines changed

4 files changed

+129
-94
lines changed

lazer/sdk/js/examples/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/* eslint-disable no-console */
2+
/* eslint-disable @typescript-eslint/no-empty-function */
3+
14
import { PythLazerClient } from "../src/index.js";
25

36
// Ignore debug messages
@@ -37,7 +40,7 @@ client.addMessageListener((message) => {
3740
});
3841

3942
// Create and remove one or more subscriptions on the fly
40-
client.subscribe({
43+
await client.subscribe({
4144
type: "subscribe",
4245
subscriptionId: 1,
4346
priceFeedIds: [1, 2],
@@ -48,7 +51,7 @@ client.subscribe({
4851
parsed: false,
4952
jsonBinaryEncoding: "base64",
5053
});
51-
client.subscribe({
54+
await client.subscribe({
5255
type: "subscribe",
5356
subscriptionId: 2,
5457
priceFeedIds: [1, 2, 3, 4, 5],
@@ -60,8 +63,8 @@ client.subscribe({
6063
jsonBinaryEncoding: "hex",
6164
});
6265

63-
await new Promise((resolve) => setTimeout(resolve, 10000));
66+
await new Promise((resolve) => setTimeout(resolve, 10_000));
6467

65-
client.unsubscribe(1);
66-
client.unsubscribe(2);
68+
await client.unsubscribe(1);
69+
await client.unsubscribe(2);
6770
client.shutdown();

lazer/sdk/js/src/client.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import WebSocket from "isomorphic-ws";
2+
import { dummyLogger, type Logger } from "ts-log";
23

34
import {
45
BINARY_UPDATE_FORMAT_MAGIC,
@@ -9,8 +10,7 @@ import {
910
type Response,
1011
SOLANA_FORMAT_MAGIC_BE,
1112
} from "./protocol.js";
12-
import { WebSocketPool } from "./socket/WebSocketPool.js";
13-
import { dummyLogger, type Logger } from "ts-log";
13+
import { WebSocketPool } from "./socket/web-socket-pool.js";
1414

1515
export type BinaryResponse = {
1616
subscriptionId: number;
@@ -34,15 +34,15 @@ export class PythLazerClient {
3434

3535
/**
3636
* 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`.
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`.
4141
*/
4242
constructor(
4343
urls: string[],
4444
token: string,
45-
numConnections: number = 3,
45+
numConnections = 3,
4646
logger: Logger = dummyLogger
4747
) {
4848
this.wsp = new WebSocketPool(urls, token, numConnections, logger);
@@ -95,19 +95,19 @@ export class PythLazerClient {
9595
});
9696
}
9797

98-
subscribe(request: Request) {
98+
async subscribe(request: Request): Promise<void> {
9999
if (request.type !== "subscribe") {
100100
throw new Error("Request must be a subscribe request");
101101
}
102-
this.wsp.addSubscription(request);
102+
await this.wsp.addSubscription(request);
103103
}
104104

105-
unsubscribe(subscriptionId: number) {
106-
this.wsp.removeSubscription(subscriptionId);
105+
async unsubscribe(subscriptionId: number): Promise<void> {
106+
await this.wsp.removeSubscription(subscriptionId);
107107
}
108108

109-
send(request: Request) {
110-
this.wsp.sendRequest(request);
109+
async send(request: Request): Promise<void> {
110+
await this.wsp.sendRequest(request);
111111
}
112112

113113
shutdown(): void {

lazer/sdk/js/src/socket/ResillientWebSocket.ts renamed to lazer/sdk/js/src/socket/resilient-web-socket.ts

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import WebSocket, { type ClientOptions } from "isomorphic-ws";
21
import type { ClientRequestArgs } from "node:http";
2+
3+
import WebSocket, { type ClientOptions, type ErrorEvent } from "isomorphic-ws";
34
import type { Logger } from "ts-log";
45

56
// Reconnect with expo backoff if we don't get a message or ping for 10 seconds
6-
const HEARTBEAT_TIMEOUT_DURATION = 10000;
7+
const HEARTBEAT_TIMEOUT_DURATION = 10_000;
78

89
/**
910
* This class wraps websocket to provide a resilient web socket client.
@@ -24,7 +25,7 @@ export class ResilientWebSocket {
2425
private heartbeatTimeout: undefined | NodeJS.Timeout;
2526
private logger: undefined | Logger;
2627

27-
onError: (error: Error) => void;
28+
onError: (error: ErrorEvent) => void;
2829
onMessage: (data: WebSocket.Data) => void;
2930
onReconnect: () => void;
3031
constructor(
@@ -37,16 +38,20 @@ export class ResilientWebSocket {
3738
this.logger = logger;
3839

3940
this.wsFailedAttempts = 0;
40-
this.onError = (error: Error) => {
41-
this.logger?.error(error);
41+
this.onError = (error: ErrorEvent) => {
42+
this.logger?.error(error.error);
4243
};
4344
this.wsUserClosed = true;
44-
this.onMessage = () => {};
45-
this.onReconnect = () => {};
45+
this.onMessage = (data: WebSocket.Data): void => {
46+
void data;
47+
};
48+
this.onReconnect = (): void => {
49+
// Empty function, can be set by the user.
50+
};
4651
}
4752

48-
async send(data: any) {
49-
this.logger?.info(`Sending ${data}`);
53+
async send(data: string | Buffer) {
54+
this.logger?.info(`Sending message`);
5055

5156
await this.waitForMaybeReadyWebSocket();
5257

@@ -55,11 +60,11 @@ export class ResilientWebSocket {
5560
"Couldn't connect to the websocket server. Error callback is called."
5661
);
5762
} else {
58-
this.wsClient?.send(data);
63+
this.wsClient.send(data);
5964
}
6065
}
6166

62-
async startWebSocket() {
67+
startWebSocket(): void {
6368
if (this.wsClient !== undefined) {
6469
return;
6570
}
@@ -69,42 +74,26 @@ export class ResilientWebSocket {
6974
this.wsClient = new WebSocket(this.endpoint, this.wsOptions);
7075
this.wsUserClosed = false;
7176

72-
this.wsClient.onopen = () => {
77+
this.wsClient.addEventListener("open", () => {
7378
this.wsFailedAttempts = 0;
7479
this.resetHeartbeat();
75-
};
80+
});
7681

77-
this.wsClient.onerror = (event) => {
78-
this.onError(event.error);
79-
};
82+
this.wsClient.addEventListener("error", (event) => {
83+
this.onError(event);
84+
});
8085

81-
this.wsClient.onmessage = (event) => {
86+
this.wsClient.addEventListener("message", (event) => {
8287
this.resetHeartbeat();
8388
this.onMessage(event.data);
84-
};
89+
});
8590

86-
this.wsClient.onclose = async () => {
87-
if (this.heartbeatTimeout !== undefined) {
88-
clearTimeout(this.heartbeatTimeout);
89-
}
90-
91-
if (this.wsUserClosed === false) {
92-
this.wsFailedAttempts += 1;
93-
this.wsClient = undefined;
94-
const waitTime = expoBackoff(this.wsFailedAttempts);
95-
96-
this.logger?.error(
97-
`Connection closed unexpectedly or because of timeout. Reconnecting after ${waitTime}ms.`
98-
);
99-
100-
await sleep(waitTime);
101-
this.restartUnexpectedClosedWebsocket();
102-
} else {
103-
this.logger?.info("The connection has been closed successfully.");
104-
}
105-
};
91+
this.wsClient.addEventListener("close", () => {
92+
void this.handleClose();
93+
});
10694

107-
if (this.wsClient.on !== undefined) {
95+
// Handle ping events if supported (Node.js only)
96+
if ("on" in this.wsClient) {
10897
// Ping handler is undefined in browser side
10998
this.wsClient.on("ping", () => {
11099
this.logger?.info("Ping received");
@@ -118,19 +107,19 @@ export class ResilientWebSocket {
118107
* from the server. If we don't receive any message within HEARTBEAT_TIMEOUT_DURATION,
119108
* we assume the connection is dead and reconnect.
120109
*/
121-
private resetHeartbeat() {
110+
private resetHeartbeat(): void {
122111
if (this.heartbeatTimeout !== undefined) {
123112
clearTimeout(this.heartbeatTimeout);
124113
}
125114

126115
this.heartbeatTimeout = setTimeout(() => {
127-
this.logger?.warn(`Connection timed out. Reconnecting...`);
116+
this.logger?.warn("Connection timed out. Reconnecting...");
128117
this.wsClient?.terminate();
129-
this.restartUnexpectedClosedWebsocket();
118+
void this.restartUnexpectedClosedWebsocket();
130119
}, HEARTBEAT_TIMEOUT_DURATION);
131120
}
132121

133-
private async waitForMaybeReadyWebSocket() {
122+
private async waitForMaybeReadyWebSocket(): Promise<void> {
134123
let waitedTime = 0;
135124
while (
136125
this.wsClient !== undefined &&
@@ -146,12 +135,35 @@ export class ResilientWebSocket {
146135
}
147136
}
148137

149-
private async restartUnexpectedClosedWebsocket() {
150-
if (this.wsUserClosed === true) {
138+
private async handleClose(): Promise<void> {
139+
if (this.heartbeatTimeout !== undefined) {
140+
clearTimeout(this.heartbeatTimeout);
141+
}
142+
143+
if (this.wsUserClosed) {
144+
this.logger?.info("The connection has been closed successfully.");
145+
} else {
146+
this.wsFailedAttempts += 1;
147+
this.wsClient = undefined;
148+
const waitTime = expoBackoff(this.wsFailedAttempts);
149+
150+
this.logger?.error(
151+
"Connection closed unexpectedly or because of timeout. Reconnecting after " +
152+
String(waitTime) +
153+
"ms."
154+
);
155+
156+
await sleep(waitTime);
157+
await this.restartUnexpectedClosedWebsocket();
158+
}
159+
}
160+
161+
private async restartUnexpectedClosedWebsocket(): Promise<void> {
162+
if (this.wsUserClosed) {
151163
return;
152164
}
153165

154-
await this.startWebSocket();
166+
this.startWebSocket();
155167
await this.waitForMaybeReadyWebSocket();
156168

157169
if (this.wsClient === undefined) {
@@ -164,7 +176,7 @@ export class ResilientWebSocket {
164176
this.onReconnect();
165177
}
166178

167-
closeWebSocket() {
179+
closeWebSocket(): void {
168180
if (this.wsClient !== undefined) {
169181
const client = this.wsClient;
170182
this.wsClient = undefined;
@@ -174,7 +186,7 @@ export class ResilientWebSocket {
174186
}
175187
}
176188

177-
async function sleep(ms: number) {
189+
async function sleep(ms: number): Promise<void> {
178190
return new Promise((resolve) => setTimeout(resolve, ms));
179191
}
180192

0 commit comments

Comments
 (0)