pmxtjs 2.35.30 → 2.35.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/generated/src/apis/DefaultApi.d.ts +33 -1
- package/dist/esm/generated/src/apis/DefaultApi.js +48 -1
- package/dist/esm/generated/src/models/WatchOrderBooks200Response.d.ts +48 -0
- package/dist/esm/generated/src/models/WatchOrderBooks200Response.js +48 -0
- package/dist/esm/generated/src/models/WatchOrderBooksRequest.d.ts +40 -0
- package/dist/esm/generated/src/models/WatchOrderBooksRequest.js +47 -0
- package/dist/esm/generated/src/models/WatchOrderBooksRequestArgsInner.d.ts +21 -0
- package/dist/esm/generated/src/models/WatchOrderBooksRequestArgsInner.js +47 -0
- package/dist/esm/generated/src/models/index.d.ts +3 -0
- package/dist/esm/generated/src/models/index.js +3 -0
- package/dist/esm/pmxt/client.d.ts +42 -0
- package/dist/esm/pmxt/client.js +149 -4
- package/dist/esm/pmxt/ws-client.d.ts +37 -0
- package/dist/esm/pmxt/ws-client.js +272 -0
- package/dist/generated/src/apis/DefaultApi.d.ts +33 -1
- package/dist/generated/src/apis/DefaultApi.js +48 -1
- package/dist/generated/src/models/WatchOrderBooks200Response.d.ts +48 -0
- package/dist/generated/src/models/WatchOrderBooks200Response.js +55 -0
- package/dist/generated/src/models/WatchOrderBooksRequest.d.ts +40 -0
- package/dist/generated/src/models/WatchOrderBooksRequest.js +54 -0
- package/dist/generated/src/models/WatchOrderBooksRequestArgsInner.d.ts +21 -0
- package/dist/generated/src/models/WatchOrderBooksRequestArgsInner.js +53 -0
- package/dist/generated/src/models/index.d.ts +3 -0
- package/dist/generated/src/models/index.js +3 -0
- package/dist/pmxt/client.d.ts +42 -0
- package/dist/pmxt/client.js +149 -4
- package/dist/pmxt/ws-client.d.ts +37 -0
- package/dist/pmxt/ws-client.js +276 -0
- package/generated/.openapi-generator/FILES +6 -0
- package/generated/docs/DefaultApi.md +71 -0
- package/generated/docs/WatchOrderBooks200Response.md +38 -0
- package/generated/docs/WatchOrderBooksRequest.md +36 -0
- package/generated/docs/WatchOrderBooksRequestArgsInner.md +32 -0
- package/generated/package.json +1 -1
- package/generated/src/apis/DefaultApi.ts +71 -0
- package/generated/src/models/WatchOrderBooks200Response.ts +96 -0
- package/generated/src/models/WatchOrderBooksRequest.ts +89 -0
- package/generated/src/models/WatchOrderBooksRequestArgsInner.ts +59 -0
- package/generated/src/models/index.ts +3 -0
- package/package.json +2 -2
- package/pmxt/client.ts +181 -8
- package/pmxt/ws-client.ts +347 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* tslint:disable */
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
/**
|
|
5
|
+
* PMXT Sidecar API
|
|
6
|
+
* A unified local sidecar API for prediction markets (Polymarket, Kalshi, Limitless). This API acts as a JSON-RPC-style gateway. Each endpoint corresponds to a specific method on the generic exchange implementation.
|
|
7
|
+
*
|
|
8
|
+
* The version of the OpenAPI document: 0.4.4
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
12
|
+
* https://openapi-generator.tech
|
|
13
|
+
* Do not edit the class manually.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.WatchOrderBooksRequestArgsInnerFromJSON = WatchOrderBooksRequestArgsInnerFromJSON;
|
|
17
|
+
exports.WatchOrderBooksRequestArgsInnerFromJSONTyped = WatchOrderBooksRequestArgsInnerFromJSONTyped;
|
|
18
|
+
exports.WatchOrderBooksRequestArgsInnerToJSON = WatchOrderBooksRequestArgsInnerToJSON;
|
|
19
|
+
exports.WatchOrderBooksRequestArgsInnerToJSONTyped = WatchOrderBooksRequestArgsInnerToJSONTyped;
|
|
20
|
+
function WatchOrderBooksRequestArgsInnerFromJSON(json) {
|
|
21
|
+
return WatchOrderBooksRequestArgsInnerFromJSONTyped(json, false);
|
|
22
|
+
}
|
|
23
|
+
function WatchOrderBooksRequestArgsInnerFromJSONTyped(json, ignoreDiscriminator) {
|
|
24
|
+
if (json == null) {
|
|
25
|
+
return json;
|
|
26
|
+
}
|
|
27
|
+
if (typeof json === 'number') {
|
|
28
|
+
return json;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(json)) {
|
|
31
|
+
if (json.every(item => typeof item === 'string')) {
|
|
32
|
+
return json;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
function WatchOrderBooksRequestArgsInnerToJSON(json) {
|
|
38
|
+
return WatchOrderBooksRequestArgsInnerToJSONTyped(json, false);
|
|
39
|
+
}
|
|
40
|
+
function WatchOrderBooksRequestArgsInnerToJSONTyped(value, ignoreDiscriminator = false) {
|
|
41
|
+
if (value == null) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'number') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
if (value.every(item => typeof item === 'string')) {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
@@ -91,4 +91,7 @@ export * from './WatchAddressRequest';
|
|
|
91
91
|
export * from './WatchAddressRequestArgsInner';
|
|
92
92
|
export * from './WatchOrderBookRequest';
|
|
93
93
|
export * from './WatchOrderBookRequestArgsInner';
|
|
94
|
+
export * from './WatchOrderBooks200Response';
|
|
95
|
+
export * from './WatchOrderBooksRequest';
|
|
96
|
+
export * from './WatchOrderBooksRequestArgsInner';
|
|
94
97
|
export * from './WatchTradesRequest';
|
|
@@ -109,4 +109,7 @@ __exportStar(require("./WatchAddressRequest"), exports);
|
|
|
109
109
|
__exportStar(require("./WatchAddressRequestArgsInner"), exports);
|
|
110
110
|
__exportStar(require("./WatchOrderBookRequest"), exports);
|
|
111
111
|
__exportStar(require("./WatchOrderBookRequestArgsInner"), exports);
|
|
112
|
+
__exportStar(require("./WatchOrderBooks200Response"), exports);
|
|
113
|
+
__exportStar(require("./WatchOrderBooksRequest"), exports);
|
|
114
|
+
__exportStar(require("./WatchOrderBooksRequestArgsInner"), exports);
|
|
112
115
|
__exportStar(require("./WatchTradesRequest"), exports);
|
package/dist/pmxt/client.d.ts
CHANGED
|
@@ -75,6 +75,10 @@ export declare abstract class Exchange {
|
|
|
75
75
|
* POST directly and skip the GET probe for the lifetime of this client.
|
|
76
76
|
*/
|
|
77
77
|
private _getReadsUnsupported;
|
|
78
|
+
/** Shared WebSocket client for streaming methods (lazy). */
|
|
79
|
+
private _wsClient;
|
|
80
|
+
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
81
|
+
private _wsUnsupported;
|
|
78
82
|
constructor(exchangeName: string, options?: ExchangeOptions);
|
|
79
83
|
private initializeServer;
|
|
80
84
|
protected handleResponse(response: any): any;
|
|
@@ -96,6 +100,18 @@ export declare abstract class Exchange {
|
|
|
96
100
|
* attempts to restart the sidecar.
|
|
97
101
|
*/
|
|
98
102
|
private fetchWithRetry;
|
|
103
|
+
/**
|
|
104
|
+
* Return the shared WebSocket client, creating it on first use.
|
|
105
|
+
*
|
|
106
|
+
* Returns `null` if the sidecar /ws endpoint was previously found
|
|
107
|
+
* to be unavailable, letting callers fall back to HTTP.
|
|
108
|
+
*/
|
|
109
|
+
private getOrCreateWs;
|
|
110
|
+
/**
|
|
111
|
+
* Attempt to use the WS transport for a watch method.
|
|
112
|
+
* Returns the raw data on success, or `null` if WS is unavailable.
|
|
113
|
+
*/
|
|
114
|
+
private watchViaWs;
|
|
99
115
|
/**
|
|
100
116
|
* Call an exchange-specific REST endpoint by its operationId.
|
|
101
117
|
* This provides direct access to all implicit API methods defined in
|
|
@@ -207,6 +223,32 @@ export declare abstract class Exchange {
|
|
|
207
223
|
* ```
|
|
208
224
|
*/
|
|
209
225
|
watchOrderBook(outcomeId: string | MarketOutcome, limit?: number): Promise<OrderBook>;
|
|
226
|
+
/**
|
|
227
|
+
* Watch real-time order book updates for multiple outcomes at once.
|
|
228
|
+
*
|
|
229
|
+
* Returns a record mapping each outcome ID (ticker) to its latest
|
|
230
|
+
* order book snapshot. Call repeatedly in a loop to stream updates
|
|
231
|
+
* (CCXT Pro pattern).
|
|
232
|
+
*
|
|
233
|
+
* Prefers the sidecar WebSocket transport when available, falling
|
|
234
|
+
* back to HTTP POST for older sidecars.
|
|
235
|
+
*
|
|
236
|
+
* @param outcomeIds - Array of outcome IDs (or MarketOutcome objects)
|
|
237
|
+
* @param limit - Optional depth limit for each order book
|
|
238
|
+
* @returns Record mapping ticker to OrderBook
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const ids = markets.slice(0, 3).map(m => m.outcomes[0].outcomeId);
|
|
243
|
+
* while (true) {
|
|
244
|
+
* const books = await exchange.watchOrderBooks(ids);
|
|
245
|
+
* for (const [ticker, ob] of Object.entries(books)) {
|
|
246
|
+
* console.log(`${ticker}: bid=${ob.bids[0]?.price}`);
|
|
247
|
+
* }
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
watchOrderBooks(outcomeIds: (string | MarketOutcome)[], limit?: number): Promise<Record<string, OrderBook>>;
|
|
210
252
|
/**
|
|
211
253
|
* Watch real-time trade updates via WebSocket.
|
|
212
254
|
*
|
package/dist/pmxt/client.js
CHANGED
|
@@ -12,6 +12,7 @@ const models_js_1 = require("./models.js");
|
|
|
12
12
|
const server_manager_js_1 = require("./server-manager.js");
|
|
13
13
|
const errors_js_1 = require("./errors.js");
|
|
14
14
|
const constants_js_1 = require("./constants.js");
|
|
15
|
+
const ws_client_js_1 = require("./ws-client.js");
|
|
15
16
|
/**
|
|
16
17
|
* Resolve a MarketOutcome shorthand to a plain outcome ID string.
|
|
17
18
|
* Accepts either a raw string ID or a MarketOutcome object.
|
|
@@ -248,6 +249,10 @@ class Exchange {
|
|
|
248
249
|
* POST directly and skip the GET probe for the lifetime of this client.
|
|
249
250
|
*/
|
|
250
251
|
_getReadsUnsupported = false;
|
|
252
|
+
/** Shared WebSocket client for streaming methods (lazy). */
|
|
253
|
+
_wsClient = null;
|
|
254
|
+
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
255
|
+
_wsUnsupported = false;
|
|
251
256
|
constructor(exchangeName, options = {}) {
|
|
252
257
|
this.exchangeName = exchangeName.toLowerCase();
|
|
253
258
|
this.apiKey = options.apiKey;
|
|
@@ -383,6 +388,58 @@ class Exchange {
|
|
|
383
388
|
}
|
|
384
389
|
throw lastError;
|
|
385
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Return the shared WebSocket client, creating it on first use.
|
|
393
|
+
*
|
|
394
|
+
* Returns `null` if the sidecar /ws endpoint was previously found
|
|
395
|
+
* to be unavailable, letting callers fall back to HTTP.
|
|
396
|
+
*/
|
|
397
|
+
async getOrCreateWs() {
|
|
398
|
+
if (this._wsUnsupported)
|
|
399
|
+
return null;
|
|
400
|
+
if (this._wsClient?.connected)
|
|
401
|
+
return this._wsClient;
|
|
402
|
+
const host = this.resolveBaseUrl();
|
|
403
|
+
const accessToken = this.serverManager.getAccessToken();
|
|
404
|
+
const client = new ws_client_js_1.SidecarWsClient(host, accessToken || undefined);
|
|
405
|
+
try {
|
|
406
|
+
// Trigger connection to validate the endpoint exists.
|
|
407
|
+
// subscribe() calls ensureConnected internally, but we want
|
|
408
|
+
// to detect failure eagerly so we can set _wsUnsupported.
|
|
409
|
+
await client.subscribe(this.exchangeName, "_ping", [], undefined, 3000).catch(() => {
|
|
410
|
+
// Expected -- no _ping method. The connection itself
|
|
411
|
+
// succeeded if we got a WS error frame back. If the
|
|
412
|
+
// connection itself failed, we'll catch below.
|
|
413
|
+
});
|
|
414
|
+
// If we got here without the connect promise rejecting,
|
|
415
|
+
// the WS endpoint exists.
|
|
416
|
+
if (!client.connected) {
|
|
417
|
+
throw new Error("WS handshake failed");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
this._wsUnsupported = true;
|
|
422
|
+
client.close();
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
this._wsClient = client;
|
|
426
|
+
return this._wsClient;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Attempt to use the WS transport for a watch method.
|
|
430
|
+
* Returns the raw data on success, or `null` if WS is unavailable.
|
|
431
|
+
*/
|
|
432
|
+
async watchViaWs(method, args) {
|
|
433
|
+
const ws = await this.getOrCreateWs();
|
|
434
|
+
if (!ws)
|
|
435
|
+
return null;
|
|
436
|
+
try {
|
|
437
|
+
return await ws.subscribe(this.exchangeName, method, args, this.getCredentials());
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
386
443
|
// Low-Level API Access
|
|
387
444
|
/**
|
|
388
445
|
* Call an exchange-specific REST endpoint by its operationId.
|
|
@@ -1328,11 +1385,17 @@ class Exchange {
|
|
|
1328
1385
|
async watchOrderBook(outcomeId, limit) {
|
|
1329
1386
|
await this.initPromise;
|
|
1330
1387
|
const resolvedOutcomeId = resolveOutcomeId(outcomeId);
|
|
1388
|
+
const args = [resolvedOutcomeId];
|
|
1389
|
+
if (limit !== undefined) {
|
|
1390
|
+
args.push(limit);
|
|
1391
|
+
}
|
|
1392
|
+
// Try WebSocket transport first
|
|
1393
|
+
const wsData = await this.watchViaWs("watchOrderBook", args);
|
|
1394
|
+
if (wsData !== null) {
|
|
1395
|
+
return convertOrderBook(wsData);
|
|
1396
|
+
}
|
|
1397
|
+
// HTTP fallback
|
|
1331
1398
|
try {
|
|
1332
|
-
const args = [resolvedOutcomeId];
|
|
1333
|
-
if (limit !== undefined) {
|
|
1334
|
-
args.push(limit);
|
|
1335
|
-
}
|
|
1336
1399
|
const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/watchOrderBook`, {
|
|
1337
1400
|
method: 'POST',
|
|
1338
1401
|
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
|
|
@@ -1355,6 +1418,88 @@ class Exchange {
|
|
|
1355
1418
|
throw new errors_js_1.PmxtError(`Failed to watch order book: ${error}`);
|
|
1356
1419
|
}
|
|
1357
1420
|
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Watch real-time order book updates for multiple outcomes at once.
|
|
1423
|
+
*
|
|
1424
|
+
* Returns a record mapping each outcome ID (ticker) to its latest
|
|
1425
|
+
* order book snapshot. Call repeatedly in a loop to stream updates
|
|
1426
|
+
* (CCXT Pro pattern).
|
|
1427
|
+
*
|
|
1428
|
+
* Prefers the sidecar WebSocket transport when available, falling
|
|
1429
|
+
* back to HTTP POST for older sidecars.
|
|
1430
|
+
*
|
|
1431
|
+
* @param outcomeIds - Array of outcome IDs (or MarketOutcome objects)
|
|
1432
|
+
* @param limit - Optional depth limit for each order book
|
|
1433
|
+
* @returns Record mapping ticker to OrderBook
|
|
1434
|
+
*
|
|
1435
|
+
* @example
|
|
1436
|
+
* ```typescript
|
|
1437
|
+
* const ids = markets.slice(0, 3).map(m => m.outcomes[0].outcomeId);
|
|
1438
|
+
* while (true) {
|
|
1439
|
+
* const books = await exchange.watchOrderBooks(ids);
|
|
1440
|
+
* for (const [ticker, ob] of Object.entries(books)) {
|
|
1441
|
+
* console.log(`${ticker}: bid=${ob.bids[0]?.price}`);
|
|
1442
|
+
* }
|
|
1443
|
+
* }
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
async watchOrderBooks(outcomeIds, limit) {
|
|
1447
|
+
await this.initPromise;
|
|
1448
|
+
const resolvedIds = outcomeIds.map(resolveOutcomeId);
|
|
1449
|
+
const args = [resolvedIds];
|
|
1450
|
+
if (limit !== undefined) {
|
|
1451
|
+
args.push(limit);
|
|
1452
|
+
}
|
|
1453
|
+
// Try WebSocket transport first
|
|
1454
|
+
const ws = await this.getOrCreateWs();
|
|
1455
|
+
if (ws) {
|
|
1456
|
+
try {
|
|
1457
|
+
const rawResult = await ws.subscribeBatch(this.exchangeName, "watchOrderBooks", args, this.getCredentials());
|
|
1458
|
+
if (rawResult && typeof rawResult === "object") {
|
|
1459
|
+
const result = {};
|
|
1460
|
+
for (const [k, v] of Object.entries(rawResult)) {
|
|
1461
|
+
if (v && typeof v === "object") {
|
|
1462
|
+
result[k] = convertOrderBook(v);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
catch {
|
|
1469
|
+
// fall through to HTTP
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
// HTTP fallback
|
|
1473
|
+
try {
|
|
1474
|
+
const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/watchOrderBooks`, {
|
|
1475
|
+
method: 'POST',
|
|
1476
|
+
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
|
|
1477
|
+
body: JSON.stringify({ args, credentials: this.getCredentials() }),
|
|
1478
|
+
});
|
|
1479
|
+
if (!response.ok) {
|
|
1480
|
+
const body = await response.json().catch(() => ({}));
|
|
1481
|
+
if (body.error && typeof body.error === "object") {
|
|
1482
|
+
throw (0, errors_js_1.fromServerError)(body.error);
|
|
1483
|
+
}
|
|
1484
|
+
throw new errors_js_1.PmxtError(body.error?.message || response.statusText);
|
|
1485
|
+
}
|
|
1486
|
+
const json = await response.json();
|
|
1487
|
+
const data = this.handleResponse(json);
|
|
1488
|
+
if (data && typeof data === "object") {
|
|
1489
|
+
const result = {};
|
|
1490
|
+
for (const [k, v] of Object.entries(data)) {
|
|
1491
|
+
result[k] = convertOrderBook(v);
|
|
1492
|
+
}
|
|
1493
|
+
return result;
|
|
1494
|
+
}
|
|
1495
|
+
return {};
|
|
1496
|
+
}
|
|
1497
|
+
catch (error) {
|
|
1498
|
+
if (error instanceof errors_js_1.PmxtError)
|
|
1499
|
+
throw error;
|
|
1500
|
+
throw new errors_js_1.PmxtError(`Failed to watch order books: ${error}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1358
1503
|
/**
|
|
1359
1504
|
* Watch real-time trade updates via WebSocket.
|
|
1360
1505
|
*
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket client for streaming methods.
|
|
3
|
+
*
|
|
4
|
+
* Provides a multiplexed WebSocket connection to the sidecar server,
|
|
5
|
+
* used by watchOrderBook and watchOrderBooks as an alternative to
|
|
6
|
+
* HTTP long-polling. Falls back to HTTP transparently when the sidecar
|
|
7
|
+
* does not support the /ws endpoint.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Multiplexed WebSocket client for the pmxt sidecar.
|
|
11
|
+
*
|
|
12
|
+
* Lazily connects to ws://{host}/ws?token={accessToken}. A single
|
|
13
|
+
* WebSocket connection is shared across all streaming subscriptions.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SidecarWsClient {
|
|
16
|
+
private ws;
|
|
17
|
+
private host;
|
|
18
|
+
private accessToken;
|
|
19
|
+
private closed;
|
|
20
|
+
/** requestId -> latest data payload */
|
|
21
|
+
private dataStore;
|
|
22
|
+
/** requestId -> subscription metadata */
|
|
23
|
+
private subscriptions;
|
|
24
|
+
/** (method:symbolKey) -> requestId -- avoids duplicate subscribes */
|
|
25
|
+
private activeSubs;
|
|
26
|
+
private connectPromise;
|
|
27
|
+
constructor(host: string, accessToken?: string);
|
|
28
|
+
private ensureConnected;
|
|
29
|
+
private connect;
|
|
30
|
+
private getWebSocketConstructor;
|
|
31
|
+
private dispatch;
|
|
32
|
+
subscribe(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<any>;
|
|
33
|
+
subscribeBatch(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<Record<string, any>>;
|
|
34
|
+
close(): void;
|
|
35
|
+
get connected(): boolean;
|
|
36
|
+
private waitForData;
|
|
37
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebSocket client for streaming methods.
|
|
4
|
+
*
|
|
5
|
+
* Provides a multiplexed WebSocket connection to the sidecar server,
|
|
6
|
+
* used by watchOrderBook and watchOrderBooks as an alternative to
|
|
7
|
+
* HTTP long-polling. Falls back to HTTP transparently when the sidecar
|
|
8
|
+
* does not support the /ws endpoint.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SidecarWsClient = void 0;
|
|
12
|
+
const errors_js_1 = require("./errors.js");
|
|
13
|
+
/**
|
|
14
|
+
* Multiplexed WebSocket client for the pmxt sidecar.
|
|
15
|
+
*
|
|
16
|
+
* Lazily connects to ws://{host}/ws?token={accessToken}. A single
|
|
17
|
+
* WebSocket connection is shared across all streaming subscriptions.
|
|
18
|
+
*/
|
|
19
|
+
class SidecarWsClient {
|
|
20
|
+
ws = null;
|
|
21
|
+
host;
|
|
22
|
+
accessToken;
|
|
23
|
+
closed = false;
|
|
24
|
+
/** requestId -> latest data payload */
|
|
25
|
+
dataStore = new Map();
|
|
26
|
+
/** requestId -> subscription metadata */
|
|
27
|
+
subscriptions = new Map();
|
|
28
|
+
/** (method:symbolKey) -> requestId -- avoids duplicate subscribes */
|
|
29
|
+
activeSubs = new Map();
|
|
30
|
+
connectPromise = null;
|
|
31
|
+
constructor(host, accessToken) {
|
|
32
|
+
this.host = host;
|
|
33
|
+
this.accessToken = accessToken;
|
|
34
|
+
}
|
|
35
|
+
// ------------------------------------------------------------------
|
|
36
|
+
// Connection lifecycle
|
|
37
|
+
// ------------------------------------------------------------------
|
|
38
|
+
async ensureConnected() {
|
|
39
|
+
if (this.ws && !this.closed)
|
|
40
|
+
return;
|
|
41
|
+
if (this.connectPromise)
|
|
42
|
+
return this.connectPromise;
|
|
43
|
+
this.connectPromise = this.connect();
|
|
44
|
+
try {
|
|
45
|
+
await this.connectPromise;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.connectPromise = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
connect() {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
let hostPart = this.host;
|
|
54
|
+
let scheme = "ws";
|
|
55
|
+
if (hostPart.startsWith("https://")) {
|
|
56
|
+
hostPart = hostPart.slice("https://".length);
|
|
57
|
+
scheme = "wss";
|
|
58
|
+
}
|
|
59
|
+
else if (hostPart.startsWith("http://")) {
|
|
60
|
+
hostPart = hostPart.slice("http://".length);
|
|
61
|
+
}
|
|
62
|
+
let url = `${scheme}://${hostPart}/ws`;
|
|
63
|
+
if (this.accessToken) {
|
|
64
|
+
url = `${url}?token=${this.accessToken}`;
|
|
65
|
+
}
|
|
66
|
+
// Use the ws package in Node.js, native WebSocket in browsers
|
|
67
|
+
const WsConstructor = this.getWebSocketConstructor();
|
|
68
|
+
if (!WsConstructor) {
|
|
69
|
+
reject(new errors_js_1.PmxtError("No WebSocket implementation available"));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const ws = new WsConstructor(url);
|
|
73
|
+
this.closed = false;
|
|
74
|
+
ws.onopen = () => {
|
|
75
|
+
this.ws = ws;
|
|
76
|
+
resolve();
|
|
77
|
+
};
|
|
78
|
+
ws.onerror = (err) => {
|
|
79
|
+
if (!this.ws) {
|
|
80
|
+
// Connection failed during handshake
|
|
81
|
+
reject(new errors_js_1.PmxtError(`WebSocket connection failed: ${err.message || err}`));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
ws.onclose = () => {
|
|
85
|
+
this.closed = true;
|
|
86
|
+
this.ws = null;
|
|
87
|
+
};
|
|
88
|
+
ws.onmessage = (event) => {
|
|
89
|
+
try {
|
|
90
|
+
const data = typeof event.data === "string"
|
|
91
|
+
? event.data
|
|
92
|
+
: event.data.toString();
|
|
93
|
+
const msg = JSON.parse(data);
|
|
94
|
+
this.dispatch(msg);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore unparseable frames
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
getWebSocketConstructor() {
|
|
103
|
+
// Browser / Deno / Bun
|
|
104
|
+
if (typeof globalThis !== "undefined" && globalThis.WebSocket) {
|
|
105
|
+
return globalThis.WebSocket;
|
|
106
|
+
}
|
|
107
|
+
// Node.js -- try to require ws
|
|
108
|
+
try {
|
|
109
|
+
// Dynamic require to avoid bundler issues
|
|
110
|
+
const wsModule = require("ws");
|
|
111
|
+
return wsModule.default || wsModule;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
dispatch(msg) {
|
|
118
|
+
const eventType = msg.event;
|
|
119
|
+
const requestId = msg.id;
|
|
120
|
+
if (eventType === "error" && requestId) {
|
|
121
|
+
const sub = this.subscriptions.get(requestId);
|
|
122
|
+
if (sub?.reject) {
|
|
123
|
+
sub.reject(new errors_js_1.PmxtError(msg.error?.message || "WebSocket subscription error"));
|
|
124
|
+
sub.reject = null;
|
|
125
|
+
sub.resolve = null;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (eventType === "subscribed") {
|
|
130
|
+
// Acknowledgement -- nothing to do
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (eventType === "data" && requestId) {
|
|
134
|
+
const symbol = msg.symbol || "";
|
|
135
|
+
const data = msg.data || {};
|
|
136
|
+
// Store by (requestId:symbol) for batch methods
|
|
137
|
+
this.dataStore.set(`${requestId}:${symbol}`, data);
|
|
138
|
+
// Store by requestId alone for single-symbol methods
|
|
139
|
+
this.dataStore.set(requestId, data);
|
|
140
|
+
const sub = this.subscriptions.get(requestId);
|
|
141
|
+
if (sub?.resolve) {
|
|
142
|
+
sub.resolve(data);
|
|
143
|
+
sub.resolve = null;
|
|
144
|
+
sub.reject = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// ------------------------------------------------------------------
|
|
149
|
+
// Public API
|
|
150
|
+
// ------------------------------------------------------------------
|
|
151
|
+
async subscribe(exchange, method, args, credentials, timeoutMs = 30000) {
|
|
152
|
+
const firstArg = args[0] ?? "";
|
|
153
|
+
const subKey = Array.isArray(firstArg)
|
|
154
|
+
? `${method}:${[...firstArg].sort().join(",")}`
|
|
155
|
+
: `${method}:${firstArg}`;
|
|
156
|
+
// Reuse existing subscription
|
|
157
|
+
const existingId = this.activeSubs.get(subKey);
|
|
158
|
+
if (existingId && this.subscriptions.has(existingId)) {
|
|
159
|
+
return this.waitForData(existingId, timeoutMs);
|
|
160
|
+
}
|
|
161
|
+
await this.ensureConnected();
|
|
162
|
+
const requestId = `req-${Math.random().toString(36).slice(2, 14)}`;
|
|
163
|
+
const symbols = Array.isArray(firstArg) ? firstArg : firstArg ? [firstArg] : [];
|
|
164
|
+
const sub = {
|
|
165
|
+
requestId,
|
|
166
|
+
method,
|
|
167
|
+
symbols,
|
|
168
|
+
resolve: null,
|
|
169
|
+
reject: null,
|
|
170
|
+
};
|
|
171
|
+
this.subscriptions.set(requestId, sub);
|
|
172
|
+
this.activeSubs.set(subKey, requestId);
|
|
173
|
+
const message = {
|
|
174
|
+
id: requestId,
|
|
175
|
+
action: "subscribe",
|
|
176
|
+
exchange,
|
|
177
|
+
method,
|
|
178
|
+
args,
|
|
179
|
+
};
|
|
180
|
+
if (credentials) {
|
|
181
|
+
message.credentials = credentials;
|
|
182
|
+
}
|
|
183
|
+
this.ws.send(JSON.stringify(message));
|
|
184
|
+
return this.waitForData(requestId, timeoutMs);
|
|
185
|
+
}
|
|
186
|
+
async subscribeBatch(exchange, method, args, credentials, timeoutMs = 30000) {
|
|
187
|
+
const symbols = Array.isArray(args[0]) ? args[0] : [];
|
|
188
|
+
await this.ensureConnected();
|
|
189
|
+
const requestId = `req-${Math.random().toString(36).slice(2, 14)}`;
|
|
190
|
+
const sub = {
|
|
191
|
+
requestId,
|
|
192
|
+
method,
|
|
193
|
+
symbols,
|
|
194
|
+
resolve: null,
|
|
195
|
+
reject: null,
|
|
196
|
+
};
|
|
197
|
+
this.subscriptions.set(requestId, sub);
|
|
198
|
+
const message = {
|
|
199
|
+
id: requestId,
|
|
200
|
+
action: "subscribe",
|
|
201
|
+
exchange,
|
|
202
|
+
method,
|
|
203
|
+
args,
|
|
204
|
+
};
|
|
205
|
+
if (credentials) {
|
|
206
|
+
message.credentials = credentials;
|
|
207
|
+
}
|
|
208
|
+
this.ws.send(JSON.stringify(message));
|
|
209
|
+
// Wait for first data event
|
|
210
|
+
await this.waitForData(requestId, timeoutMs);
|
|
211
|
+
// Collect per-symbol data
|
|
212
|
+
const result = {};
|
|
213
|
+
for (const symbol of symbols) {
|
|
214
|
+
const storeKey = `${requestId}:${symbol}`;
|
|
215
|
+
const data = this.dataStore.get(storeKey);
|
|
216
|
+
if (data !== undefined) {
|
|
217
|
+
result[symbol] = data;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// If no per-symbol data, return the single data event as-is
|
|
221
|
+
if (Object.keys(result).length === 0) {
|
|
222
|
+
const data = this.dataStore.get(requestId);
|
|
223
|
+
if (data && typeof data === "object") {
|
|
224
|
+
return data;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
close() {
|
|
230
|
+
this.closed = true;
|
|
231
|
+
if (this.ws) {
|
|
232
|
+
try {
|
|
233
|
+
this.ws.close();
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// ignore
|
|
237
|
+
}
|
|
238
|
+
this.ws = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
get connected() {
|
|
242
|
+
return this.ws !== null && !this.closed;
|
|
243
|
+
}
|
|
244
|
+
// ------------------------------------------------------------------
|
|
245
|
+
// Internal
|
|
246
|
+
// ------------------------------------------------------------------
|
|
247
|
+
waitForData(requestId, timeoutMs) {
|
|
248
|
+
// Check if data is already available
|
|
249
|
+
const existing = this.dataStore.get(requestId);
|
|
250
|
+
if (existing !== undefined) {
|
|
251
|
+
this.dataStore.delete(requestId);
|
|
252
|
+
return Promise.resolve(existing);
|
|
253
|
+
}
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
const sub = this.subscriptions.get(requestId);
|
|
256
|
+
if (!sub) {
|
|
257
|
+
reject(new errors_js_1.PmxtError("Subscription not found"));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const timer = setTimeout(() => {
|
|
261
|
+
sub.resolve = null;
|
|
262
|
+
sub.reject = null;
|
|
263
|
+
reject(new errors_js_1.PmxtError(`Timeout waiting for WebSocket data (method=${sub.method})`));
|
|
264
|
+
}, timeoutMs);
|
|
265
|
+
sub.resolve = (data) => {
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
resolve(data);
|
|
268
|
+
};
|
|
269
|
+
sub.reject = (err) => {
|
|
270
|
+
clearTimeout(timer);
|
|
271
|
+
reject(err);
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.SidecarWsClient = SidecarWsClient;
|
|
@@ -96,6 +96,9 @@ docs/WatchAddressRequest.md
|
|
|
96
96
|
docs/WatchAddressRequestArgsInner.md
|
|
97
97
|
docs/WatchOrderBookRequest.md
|
|
98
98
|
docs/WatchOrderBookRequestArgsInner.md
|
|
99
|
+
docs/WatchOrderBooks200Response.md
|
|
100
|
+
docs/WatchOrderBooksRequest.md
|
|
101
|
+
docs/WatchOrderBooksRequestArgsInner.md
|
|
99
102
|
docs/WatchTradesRequest.md
|
|
100
103
|
package.json
|
|
101
104
|
src/apis/DefaultApi.ts
|
|
@@ -194,6 +197,9 @@ src/models/WatchAddressRequest.ts
|
|
|
194
197
|
src/models/WatchAddressRequestArgsInner.ts
|
|
195
198
|
src/models/WatchOrderBookRequest.ts
|
|
196
199
|
src/models/WatchOrderBookRequestArgsInner.ts
|
|
200
|
+
src/models/WatchOrderBooks200Response.ts
|
|
201
|
+
src/models/WatchOrderBooksRequest.ts
|
|
202
|
+
src/models/WatchOrderBooksRequestArgsInner.ts
|
|
197
203
|
src/models/WatchTradesRequest.ts
|
|
198
204
|
src/models/index.ts
|
|
199
205
|
src/runtime.ts
|