Skip to content

Commit 24edc8d

Browse files
committed
feat(lazer/js/sdk): QSP auth for browser support
1 parent ab26746 commit 24edc8d

File tree

3 files changed

+136
-9
lines changed

3 files changed

+136
-9
lines changed

lazer/sdk/js/README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1-
# pyth-lazer-sdk - Readme
1+
# Pyth Lazer JavaScript SDK
2+
3+
A JavaScript/TypeScript SDK for connecting to the Pyth Lazer service, supporting both Node and browser environments.
4+
5+
6+
## Quick Start
7+
Install with:
8+
```sh
9+
npm install @pythnetwork/pyth-lazer-sdk
10+
```
11+
12+
Connect to Lazer and process the messages:
13+
```javascript
14+
import { PythLazerClient } from '@pythnetwork/pyth-lazer-sdk';
15+
16+
const client = await PythLazerClient.create({
17+
urls: ['wss://your-lazer-endpoint/v1/stream'],
18+
token: 'your-access-token',
19+
numConnections: 3
20+
});
21+
22+
// Register an event handler for each price update message.
23+
client.addMessageListener((message) => {
24+
console.log('Received:', message);
25+
});
26+
27+
// Subscribe to a feed. You can call subscribe() multiple times.
28+
client.subscribe({
29+
type: "subscribe",
30+
subscriptionId: 1,
31+
priceFeedIds: [1, 2],
32+
properties: ["price"],
33+
formats: ["solana"],
34+
deliveryFormat: "binary",
35+
channel: "fixed_rate@200ms",
36+
parsed: false,
37+
jsonBinaryEncoding: "base64",
38+
});
39+
```
40+
41+
For a full demo, run the example in `examples/index.ts` with:
42+
```
43+
pnpm run example
44+
```
45+
### Build locally
46+
47+
Build ESM and CJS packages with:
48+
49+
```sh
50+
pnpm turbo build -F @pythnetwork/pyth-lazer-sdk
51+
```
52+
53+
## API Reference
54+
55+
For detailed API documentation, see the [TypeDoc documentation](docs/typedoc/).
256

357
## Contributing & Development
458

lazer/sdk/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/pyth-lazer-sdk",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "Pyth Lazer SDK",
55
"publishConfig": {
66
"access": "public"

lazer/sdk/js/src/socket/websocket-pool.ts

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,77 @@ import type { Request, Response } from "../protocol.js";
88
import type { ResilientWebSocketConfig } from "./resilient-websocket.js";
99
import { ResilientWebSocket } from "./resilient-websocket.js";
1010

11+
/**
12+
* Detects if the code is running in a regular DOM or Web Worker context.
13+
* @returns true if running in a DOM or Web Worker context, false if running in Node.js
14+
*/
15+
function isBrowser(): boolean {
16+
try {
17+
// Check for browser's window object (DOM context)
18+
if (typeof globalThis !== "undefined") {
19+
const global = globalThis as any;
20+
if (global.window && global.window.document) {
21+
return true;
22+
}
23+
24+
// Check for Web Worker context (has importScripts but no window)
25+
if (typeof global.importScripts === "function" && !global.window) {
26+
return true;
27+
}
28+
}
29+
30+
// Node.js environment
31+
return false;
32+
} catch {
33+
// If any error occurs, assume Node.js environment
34+
return false;
35+
}
36+
}
37+
38+
/**
39+
* Adds authentication to either the URL as a query parameter or as an Authorization header.
40+
*
41+
* Browsers don't support custom headers for WebSocket connections, so if a browser is detected,
42+
* the token is added as a query parameter instead. Else, the token is added as an Authorization header.
43+
*
44+
* @param url - The WebSocket URL
45+
* @param token - The authentication token
46+
* @param wsOptions - Existing WebSocket options
47+
* @returns Object containing the modified endpoint and wsOptions
48+
*/
49+
function addAuthentication(
50+
url: string,
51+
token: string,
52+
wsOptions: any = {}
53+
): { endpoint: string; wsOptions: any } {
54+
if (isBrowser()) {
55+
// Browser: Add token as query parameter
56+
const urlObj = new URL(url);
57+
urlObj.searchParams.set("ACCESS_TOKEN", token);
58+
59+
// For browsers, we need to filter out any options that aren't valid for WebSocket constructor
60+
// Browser WebSocket constructor only accepts protocols as second parameter
61+
const browserWsOptions = wsOptions.protocols ? wsOptions.protocols : undefined;
62+
63+
return {
64+
endpoint: urlObj.toString(),
65+
wsOptions: browserWsOptions,
66+
};
67+
} else {
68+
// Node.js: Add Authorization header
69+
return {
70+
endpoint: url,
71+
wsOptions: {
72+
...wsOptions,
73+
headers: {
74+
...wsOptions.headers,
75+
Authorization: `Bearer ${token}`,
76+
},
77+
},
78+
};
79+
}
80+
}
81+
1182
const DEFAULT_NUM_CONNECTIONS = 4;
1283

1384
export type WebSocketPoolConfig = {
@@ -63,15 +134,17 @@ export class WebSocketPool {
63134
if (!url) {
64135
throw new Error(`URLs must not be null or empty`);
65136
}
66-
const wsOptions = {
67-
...config.rwsConfig?.wsOptions,
68-
headers: {
69-
Authorization: `Bearer ${config.token}`,
70-
},
71-
};
137+
138+
// Apply authentication based on environment (browser vs Node.js)
139+
const { endpoint, wsOptions } = addAuthentication(
140+
url,
141+
config.token,
142+
config.rwsConfig?.wsOptions
143+
);
144+
72145
const rws = new ResilientWebSocket({
73146
...config.rwsConfig,
74-
endpoint: url,
147+
endpoint,
75148
wsOptions,
76149
logger,
77150
});

0 commit comments

Comments
 (0)