@runesx/api-client 0.5.4 → 0.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runesx/api-client",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "A Node.js client for interacting with the RunesX platform API and WebSocket",
5
5
  "main": "src/index.mjs",
6
6
  "type": "module",
package/src/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.mjs
2
- import { setupSocket } from './socket.mjs';
2
+ import { setupSocket, setupPublicSocket } from './socket.mjs';
3
3
  import { createApi } from './api.mjs';
4
4
  import { createConfig } from './config.mjs';
5
5
  import { getPools, getPool } from './store/poolStore.mjs';
@@ -10,7 +10,7 @@ import { getUserShares, getUserShareByPoolId } from './store/userSharesStore.mjs
10
10
  import { getAllOrderBooks, getOrderBook, getOrderBookPairs, getUserOrders } from './store/orderbookStore.mjs';
11
11
  import { getMarkets, getMarketByCoinKey, getMarketByCoins } from './store/marketStore.mjs';
12
12
  import { getClobFees } from './store/exchangeConfigStore.mjs';
13
- import { waitForStores } from './waitForStores.mjs';
13
+ import { waitForStores, waitForSelectiveStores } from './waitForStores.mjs';
14
14
  import { estimateLiquidityFrontend, checkRunesLiquidityFrontend, calculateShareAmounts, estimateDepositShares } from './utils/liquidityUtils.mjs';
15
15
  import { estimateSwap } from './utils/swapUtils.mjs';
16
16
  import { createPriceUtils } from './utils/priceUtils.mjs';
@@ -31,6 +31,13 @@ export function createRunesXClient(options = {}) {
31
31
  return { pools, coins, chains, wallets, userShares, orderbooks };
32
32
  }
33
33
 
34
+ async function initializePublic({ stores = ['pools', 'coins'] } = {}) {
35
+ socketHandler = setupPublicSocket(config, stores);
36
+ const storeData = await waitForSelectiveStores(socketHandler.socket, stores);
37
+ initialized = true;
38
+ return storeData;
39
+ }
40
+
34
41
  function ensureInitialized() {
35
42
  if (!initialized) {
36
43
  throw new Error('Client not initialized. Call initialize() first.');
@@ -39,6 +46,13 @@ export function createRunesXClient(options = {}) {
39
46
 
40
47
  return {
41
48
  initialize,
49
+ initializePublic,
50
+
51
+ requestStores: (additionalStores) => {
52
+ ensureInitialized();
53
+ socketHandler.requestStores(additionalStores);
54
+ return waitForSelectiveStores(socketHandler.socket, additionalStores);
55
+ },
42
56
 
43
57
  // Raw socket access
44
58
  getSocket: () => {
@@ -75,13 +89,13 @@ export function createRunesXClient(options = {}) {
75
89
  },
76
90
 
77
91
  // ---- Socket emit convenience methods ----
78
- joinCandlesticks: (poolId, timeframe) => {
92
+ joinCandlesticks: (pair, timeframe) => {
79
93
  ensureInitialized();
80
- socketHandler.joinCandlesticks(poolId, timeframe);
94
+ socketHandler.joinCandlesticks(pair, timeframe);
81
95
  },
82
- leaveCandlesticks: (poolId, timeframe) => {
96
+ leaveCandlesticks: (pair, timeframe) => {
83
97
  ensureInitialized();
84
- socketHandler.leaveCandlesticks(poolId, timeframe);
98
+ socketHandler.leaveCandlesticks(pair, timeframe);
85
99
  },
86
100
  sendYardMessage: (text) => {
87
101
  ensureInitialized();
package/src/socket.mjs CHANGED
@@ -10,103 +10,12 @@ import { setInitialOrderBooks, updateOrderBook, resetOrderBooks, setUserOrders,
10
10
  import { setInitialMarkets, addOrUpdateMarket, resetMarkets } from './store/marketStore.mjs';
11
11
  import { setExchangeConfig } from './store/exchangeConfigStore.mjs';
12
12
 
13
- export function setupSocket(config) {
14
- const socket = io(config.socketUrl, {
15
- auth: { authorization: `Bearer ${config.apiKey}` },
16
- extraHeaders: { authorization: `Bearer ${config.apiKey}` },
17
- transports: ['websocket'],
18
- reconnection: true,
19
- reconnectionAttempts: Infinity,
20
- reconnectionDelay: 1000,
21
- reconnectionDelayMax: 5000,
22
- });
23
-
24
- const errorCount = { count: 0 };
25
-
26
- // User-registered event callbacks
27
- const callbacks = {};
28
-
29
- function emit(event, data) {
30
- if (callbacks[event]) {
31
- callbacks[event].forEach((cb) => {
32
- try { cb(data); } catch (e) { console.error(`Error in ${event} callback:`, e.message); }
33
- });
34
- }
35
- }
36
-
37
- const heartbeatInterval = setInterval(() => {
38
- if (socket.connected) {
39
- socket.emit('ping');
40
- }
41
- }, 30000);
42
-
43
- socket.on('connect', () => {
44
- socket.emit('join_public');
45
- socket.emit('join_private');
46
- errorCount.count = 0;
47
- emit('connect', null);
48
- });
49
-
50
- socket.on('connect_error', (err) => {
51
- console.log('Socket connect error:', err.message);
52
- errorCount.count += 1;
53
- if (errorCount.count >= 3) {
54
- resetPools();
55
- resetCoins();
56
- resetChains();
57
- resetWallets();
58
- resetUserShares();
59
- resetOrderBooks();
60
- resetMarkets();
61
- }
62
- emit('connect_error', err);
63
- });
64
-
65
- socket.on('disconnect', (reason) => {
66
- console.log('Disconnected from Socket.IO server:', reason);
67
- clearInterval(heartbeatInterval);
68
- resetPools();
69
- resetCoins();
70
- resetChains();
71
- resetWallets();
72
- resetUserShares();
73
- resetMarkets();
74
- emit('disconnect', reason);
75
- });
76
-
77
- socket.io.on('reconnect_attempt', (attempt) => {
78
- console.log(`Reconnect attempt #${attempt}`);
79
- emit('reconnect_attempt', attempt);
80
- });
81
-
82
- socket.io.on('reconnect', () => {
83
- emit('reconnect', null);
84
- });
85
-
86
- socket.io.on('reconnect_error', (err) => {
87
- console.log('Reconnect error:', err.message);
88
- errorCount.count += 1;
89
- if (errorCount.count >= 3) {
90
- resetPools();
91
- resetCoins();
92
- resetChains();
93
- resetWallets();
94
- resetUserShares();
95
- resetOrderBooks();
96
- }
97
- emit('reconnect_error', err);
98
- });
99
-
100
- socket.on('error', (err) => {
101
- console.log('Socket error:', err.message);
102
- emit('error', err);
103
- });
104
-
105
- // ---- Public room events ----
106
-
107
- socket.on('exchange_config', (config) => {
108
- setExchangeConfig(config);
109
- emit('exchange_config', config);
13
+ // ---- Shared public event handler registration ----
14
+ // Used by both setupSocket and setupPublicSocket to avoid duplicating handler code.
15
+ function _registerPublicHandlers(socket, emit) {
16
+ socket.on('exchange_config', (cfg) => {
17
+ setExchangeConfig(cfg);
18
+ emit('exchange_config', cfg);
110
19
  });
111
20
 
112
21
  socket.on('markets_initial', (data) => {
@@ -204,6 +113,102 @@ export function setupSocket(config) {
204
113
  socket.on('candlestick_updated', (data) => {
205
114
  emit('candlestick_updated', data);
206
115
  });
116
+ }
117
+
118
+ export function setupSocket(config) {
119
+ const socket = io(config.socketUrl, {
120
+ auth: { authorization: `Bearer ${config.apiKey}` },
121
+ extraHeaders: { authorization: `Bearer ${config.apiKey}` },
122
+ transports: ['websocket'],
123
+ reconnection: true,
124
+ reconnectionAttempts: Infinity,
125
+ reconnectionDelay: 1000,
126
+ reconnectionDelayMax: 5000,
127
+ });
128
+
129
+ const errorCount = { count: 0 };
130
+
131
+ // User-registered event callbacks
132
+ const callbacks = {};
133
+
134
+ function emit(event, data) {
135
+ if (callbacks[event]) {
136
+ callbacks[event].forEach((cb) => {
137
+ try { cb(data); } catch (e) { console.error(`Error in ${event} callback:`, e.message); }
138
+ });
139
+ }
140
+ }
141
+
142
+ const heartbeatInterval = setInterval(() => {
143
+ if (socket.connected) {
144
+ socket.emit('ping');
145
+ }
146
+ }, 30000);
147
+
148
+ socket.on('connect', () => {
149
+ socket.emit('join_public');
150
+ socket.emit('join_private');
151
+ errorCount.count = 0;
152
+ emit('connect', null);
153
+ });
154
+
155
+ socket.on('connect_error', (err) => {
156
+ console.log('Socket connect error:', err.message);
157
+ errorCount.count += 1;
158
+ if (errorCount.count >= 3) {
159
+ resetPools();
160
+ resetCoins();
161
+ resetChains();
162
+ resetWallets();
163
+ resetUserShares();
164
+ resetOrderBooks();
165
+ resetMarkets();
166
+ }
167
+ emit('connect_error', err);
168
+ });
169
+
170
+ socket.on('disconnect', (reason) => {
171
+ console.log('Disconnected from Socket.IO server:', reason);
172
+ clearInterval(heartbeatInterval);
173
+ resetPools();
174
+ resetCoins();
175
+ resetChains();
176
+ resetWallets();
177
+ resetUserShares();
178
+ resetMarkets();
179
+ emit('disconnect', reason);
180
+ });
181
+
182
+ socket.io.on('reconnect_attempt', (attempt) => {
183
+ console.log(`Reconnect attempt #${attempt}`);
184
+ emit('reconnect_attempt', attempt);
185
+ });
186
+
187
+ socket.io.on('reconnect', () => {
188
+ emit('reconnect', null);
189
+ });
190
+
191
+ socket.io.on('reconnect_error', (err) => {
192
+ console.log('Reconnect error:', err.message);
193
+ errorCount.count += 1;
194
+ if (errorCount.count >= 3) {
195
+ resetPools();
196
+ resetCoins();
197
+ resetChains();
198
+ resetWallets();
199
+ resetUserShares();
200
+ resetOrderBooks();
201
+ }
202
+ emit('reconnect_error', err);
203
+ });
204
+
205
+ socket.on('error', (err) => {
206
+ console.log('Socket error:', err.message);
207
+ emit('error', err);
208
+ });
209
+
210
+ // ---- Public room events (shared handler) ----
211
+ _registerPublicHandlers(socket, emit);
207
212
 
208
213
  // ---- Private room events ----
209
214
 
@@ -303,12 +308,12 @@ export function setupSocket(config) {
303
308
  }
304
309
  }
305
310
 
306
- function joinCandlesticks(poolId, timeframe) {
307
- socket.emit('join_candlesticks', { poolId, timeframe });
311
+ function joinCandlesticks(pair, timeframe) {
312
+ socket.emit('join_candlesticks', { pair, timeframe });
308
313
  }
309
314
 
310
- function leaveCandlesticks(poolId, timeframe) {
311
- socket.emit('leave_candlesticks', { poolId, timeframe });
315
+ function leaveCandlesticks(pair, timeframe) {
316
+ socket.emit('leave_candlesticks', { pair, timeframe });
312
317
  }
313
318
 
314
319
  function sendYardMessage(text) {
@@ -344,3 +349,144 @@ export function setupSocket(config) {
344
349
  leavePrivate,
345
350
  };
346
351
  }
352
+
353
+ // ---- Public-only socket (no auth) ----
354
+
355
+ const _storeResetMap = {
356
+ pools: resetPools,
357
+ coins: resetCoins,
358
+ chains: resetChains,
359
+ orderbooks: resetOrderBooks,
360
+ markets: resetMarkets,
361
+ };
362
+
363
+ export function setupPublicSocket(config, requestedStores) {
364
+ const _requestedStores = new Set(requestedStores);
365
+
366
+ const socket = io(config.socketUrl, {
367
+ transports: ['websocket'],
368
+ reconnection: true,
369
+ reconnectionAttempts: Infinity,
370
+ reconnectionDelay: 1000,
371
+ reconnectionDelayMax: 5000,
372
+ });
373
+
374
+ const errorCount = { count: 0 };
375
+
376
+ // User-registered event callbacks
377
+ const callbacks = {};
378
+
379
+ function emit(event, data) {
380
+ if (callbacks[event]) {
381
+ callbacks[event].forEach((cb) => {
382
+ try { cb(data); } catch (e) { console.error(`Error in ${event} callback:`, e.message); }
383
+ });
384
+ }
385
+ }
386
+
387
+ const heartbeatInterval = setInterval(() => {
388
+ if (socket.connected) {
389
+ socket.emit('ping');
390
+ }
391
+ }, 30000);
392
+
393
+ socket.on('connect', () => {
394
+ socket.emit('join_public_selective', { stores: [..._requestedStores] });
395
+ errorCount.count = 0;
396
+ emit('connect', null);
397
+ });
398
+
399
+ socket.on('connect_error', (err) => {
400
+ console.log('Socket connect error:', err.message);
401
+ errorCount.count += 1;
402
+ if (errorCount.count >= 3) {
403
+ for (const store of _requestedStores) {
404
+ if (_storeResetMap[store]) { _storeResetMap[store](); }
405
+ }
406
+ }
407
+ emit('connect_error', err);
408
+ });
409
+
410
+ socket.on('disconnect', (reason) => {
411
+ console.log('Disconnected from Socket.IO server:', reason);
412
+ clearInterval(heartbeatInterval);
413
+ for (const store of _requestedStores) {
414
+ if (_storeResetMap[store]) { _storeResetMap[store](); }
415
+ }
416
+ emit('disconnect', reason);
417
+ });
418
+
419
+ socket.io.on('reconnect_attempt', (attempt) => {
420
+ console.log(`Reconnect attempt #${attempt}`);
421
+ emit('reconnect_attempt', attempt);
422
+ });
423
+
424
+ socket.io.on('reconnect', () => {
425
+ emit('reconnect', null);
426
+ });
427
+
428
+ socket.io.on('reconnect_error', (err) => {
429
+ console.log('Reconnect error:', err.message);
430
+ errorCount.count += 1;
431
+ if (errorCount.count >= 3) {
432
+ for (const store of _requestedStores) {
433
+ if (_storeResetMap[store]) { _storeResetMap[store](); }
434
+ }
435
+ }
436
+ emit('reconnect_error', err);
437
+ });
438
+
439
+ socket.on('error', (err) => {
440
+ console.log('Socket error:', err.message);
441
+ emit('error', err);
442
+ });
443
+
444
+ // ---- Public room events (shared handler) ----
445
+ _registerPublicHandlers(socket, emit);
446
+
447
+ socket.on('pong', () => {
448
+ emit('pong', null);
449
+ });
450
+
451
+ // ---- Convenience methods ----
452
+
453
+ function on(event, callback) {
454
+ if (!callbacks[event]) {
455
+ callbacks[event] = [];
456
+ }
457
+ callbacks[event].push(callback);
458
+ }
459
+
460
+ function off(event, callback) {
461
+ if (!callbacks[event]) { return; }
462
+ if (callback) {
463
+ callbacks[event] = callbacks[event].filter((cb) => cb !== callback);
464
+ } else {
465
+ delete callbacks[event];
466
+ }
467
+ }
468
+
469
+ function requestStores(additionalStores) {
470
+ for (const store of additionalStores) {
471
+ _requestedStores.add(store);
472
+ }
473
+ socket.emit('request_stores', { stores: additionalStores });
474
+ }
475
+
476
+ function joinCandlesticks(pair, timeframe) {
477
+ socket.emit('join_candlesticks', { pair, timeframe });
478
+ }
479
+
480
+ function leaveCandlesticks(pair, timeframe) {
481
+ socket.emit('leave_candlesticks', { pair, timeframe });
482
+ }
483
+
484
+ return {
485
+ socket,
486
+ on,
487
+ off,
488
+ requestStores,
489
+ joinCandlesticks,
490
+ leaveCandlesticks,
491
+ };
492
+ }
@@ -4,6 +4,7 @@ import { chainStore, getChains } from './store/chainStore.mjs';
4
4
  import { walletStore, getWallets } from './store/walletStore.mjs';
5
5
  import { userSharesStore, getUserShares } from './store/userSharesStore.mjs';
6
6
  import { orderbookStore, getAllOrderBooks } from './store/orderbookStore.mjs';
7
+ import { getMarkets } from './store/marketStore.mjs';
7
8
 
8
9
  export function waitForStores(socket) {
9
10
  // Function to wait for poolStore to be populated
@@ -224,4 +225,84 @@ export function waitForStores(socket) {
224
225
  userShares,
225
226
  orderbooks,
226
227
  }));
228
+ }
229
+
230
+ // ---- Selective store waiting (for public-only initialization) ----
231
+
232
+ const _storeDefinitions = {
233
+ pools: {
234
+ waitEvent: 'pools_updated',
235
+ checkReady: () => poolStore.isInitialReceived,
236
+ getData: getPools,
237
+ },
238
+ coins: {
239
+ waitEvent: 'coins_updated',
240
+ checkReady: () => coinStore.isInitialReceived,
241
+ getData: getCoins,
242
+ },
243
+ chains: {
244
+ waitEvent: 'chains_updated',
245
+ checkReady: () => chainStore.isInitialReceived,
246
+ getData: getChains,
247
+ },
248
+ orderbooks: {
249
+ waitEvent: 'orderbooks_initial',
250
+ checkReady: () => orderbookStore.isInitialReceived,
251
+ getData: getAllOrderBooks,
252
+ },
253
+ markets: {
254
+ waitEvent: 'markets_initial',
255
+ checkReady: () => true, // fire-and-forget, resolve immediately
256
+ getData: getMarkets,
257
+ },
258
+ };
259
+
260
+ export function waitForSelectiveStores(socket, requestedStores) {
261
+ const promises = [];
262
+
263
+ for (const storeName of requestedStores) {
264
+ const def = _storeDefinitions[storeName];
265
+ if (!def) {
266
+ // Stores not in the map (buckets, operations, messages, status) are silently skipped
267
+ continue;
268
+ }
269
+
270
+ const promise = new Promise((resolve, reject) => {
271
+ if (def.checkReady()) {
272
+ resolve({ [storeName]: def.getData() });
273
+ return;
274
+ }
275
+
276
+ const timeout = setTimeout(() => {
277
+ reject(new Error(`Timeout waiting for initial ${storeName} data`));
278
+ }, 30000);
279
+
280
+ const handler = (data) => {
281
+ const isInitial = data && data.isInitial;
282
+ if (isInitial) {
283
+ clearTimeout(timeout);
284
+ socket.off(def.waitEvent, handler);
285
+ resolve({ [storeName]: def.getData() });
286
+ }
287
+ };
288
+
289
+ socket.on(def.waitEvent, handler);
290
+
291
+ socket.on('connect_error', () => {
292
+ clearTimeout(timeout);
293
+ reject(new Error(`Socket connection failed while waiting for ${storeName}`));
294
+ });
295
+
296
+ socket.on('disconnect', () => {
297
+ clearTimeout(timeout);
298
+ reject(new Error(`Socket disconnected before receiving initial ${storeName} data`));
299
+ });
300
+ });
301
+
302
+ promises.push(promise);
303
+ }
304
+
305
+ return Promise.all(promises).then((results) => {
306
+ return Object.assign({}, ...results);
307
+ });
227
308
  }