Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions lazer/sdk/js/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,24 @@ import { PythLazerClient } from "../src/index.js";
// Ignore debug messages
console.debug = () => {};

const client = await PythLazerClient.create(
["wss://pyth-lazer.dourolabs.app/v1/stream"],
"access_token",
3, // Optionally specify number of parallel redundant connections to reduce the chance of dropped messages. The connections will round-robin across the provided URLs. Default is 3.
console, // Optionally log socket operations (to the console in this case.)
);
const client = await PythLazerClient.create({
urls: [
"wss://pyth-lazer-0.dourolabs.app/v1/stream",
"wss://pyth-lazer-1.dourolabs.app/v1/stream",
],
token: "you-access-token-here", // Replace with your actual access token
numConnections: 4, // Optionally specify number of parallel redundant connections to reduce the chance of dropped messages. The connections will round-robin across the provided URLs. Default is 4.
logger: console, // Optionally log socket operations (to the console in this case.)
onError: (error) => {
console.error("WebSocket error:", error);
},
// Optional configuration for resilient WebSocket connections
rwsConfig: {
heartbeatTimeoutDurationMs: 5000, // Optional heartbeat timeout duration in milliseconds
maxRetryDelayMs: 1000, // Optional maximum retry delay in milliseconds
logAfterRetryCount: 10, // Optional log after how many retries
},
});

// Read and process messages from the Lazer stream
client.addMessageListener((message) => {
Expand Down Expand Up @@ -47,7 +59,7 @@ client.addAllConnectionsDownListener(() => {
});

// Create and remove one or more subscriptions on the fly
await client.subscribe({
client.subscribe({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember one of the early consumers of the SDK wanted the ability to await the subscribe() (i.e. promise is fulfilled when the lazer server acks the subscription.) Is that no longer a requirement? Personally I think it makes for nicer ergonomics to keep it async.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah good call. I'll check it.

type: "subscribe",
subscriptionId: 1,
priceFeedIds: [1, 2],
Expand All @@ -58,7 +70,7 @@ await client.subscribe({
parsed: false,
jsonBinaryEncoding: "base64",
});
await client.subscribe({
client.subscribe({
type: "subscribe",
subscriptionId: 2,
priceFeedIds: [1, 2, 3, 4, 5],
Expand All @@ -72,6 +84,6 @@ await client.subscribe({

await new Promise((resolve) => setTimeout(resolve, 10_000));

await client.unsubscribe(1);
await client.unsubscribe(2);
client.unsubscribe(1);
client.unsubscribe(2);
client.shutdown();
2 changes: 1 addition & 1 deletion lazer/sdk/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pythnetwork/pyth-lazer-sdk",
"version": "0.5.1",
"version": "1.0.0",
"description": "Pyth Lazer SDK",
"publishConfig": {
"access": "public"
Expand Down
24 changes: 9 additions & 15 deletions lazer/sdk/js/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import WebSocket from "isomorphic-ws";
import type { Logger } from "ts-log";
import { dummyLogger } from "ts-log";

import type { ParsedPayload, Request, Response } from "./protocol.js";
import { BINARY_UPDATE_FORMAT_MAGIC_LE, FORMAT_MAGICS_LE } from "./protocol.js";
import type { WebSocketPoolConfig } from "./socket/websocket-pool.js";
import { WebSocketPool } from "./socket/websocket-pool.js";

export type BinaryResponse = {
Expand Down Expand Up @@ -35,13 +34,8 @@ export class PythLazerClient {
* @param numConnections - The number of parallel WebSocket connections to establish (default: 3). A higher number gives a more reliable stream. The connections will round-robin across the provided URLs.
* @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
*/
static async create(
urls: string[],
token: string,
numConnections = 3,
logger: Logger = dummyLogger,
): Promise<PythLazerClient> {
const wsp = await WebSocketPool.create(urls, token, numConnections, logger);
static async create(config: WebSocketPoolConfig): Promise<PythLazerClient> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of a nit, and not something you added here, but for future reference IMO static methods are an antipattern. Unless you have a good reason why you really need the BinaryResponse.create syntax, this is equivalent to just exporting a function createBinaryResponse that isn't scoped to the class. There are lots of downsides to using the static method version:

  1. Classes are not tree-shakeable
  2. It implies the code is more OOP than it actually is -- es6 classes aren't Java classes and I don't believe it's healthy to use patterns that make them look like they are.
  3. It subtly discourages functional coding patterns in favor of procedural code (by nature of encouraging thinking about code in Java OOP terms)
  4. In Javascript, IMO, modules are the best namespacing construct. Classes should be reserved for state management. This pattern abuses those constructs and turns classes into a namespacing tool instead.

const wsp = await WebSocketPool.create(config);
return new PythLazerClient(wsp);
}

Expand Down Expand Up @@ -102,19 +96,19 @@ export class PythLazerClient {
});
}

async subscribe(request: Request): Promise<void> {
subscribe(request: Request) {
if (request.type !== "subscribe") {
throw new Error("Request must be a subscribe request");
}
await this.wsp.addSubscription(request);
this.wsp.addSubscription(request);
}

async unsubscribe(subscriptionId: number): Promise<void> {
await this.wsp.removeSubscription(subscriptionId);
unsubscribe(subscriptionId: number) {
this.wsp.removeSubscription(subscriptionId);
}

async send(request: Request): Promise<void> {
await this.wsp.sendRequest(request);
send(request: Request) {
this.wsp.sendRequest(request);
}

/**
Expand Down
Loading
Loading