Skip to content

Commit 9086625

Browse files
authored
Merge pull request #973 from pcriadoperez/ws-api
feat: update websockets to subscribe using ws-api
2 parents 92f7167 + 130f5e8 commit 9086625

File tree

9 files changed

+1569
-92
lines changed

9 files changed

+1569
-92
lines changed

.github/workflows/js.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ jobs:
3535
run: npm run static-test
3636
- name: Live Tests (TS ESM)
3737
run: npm run ts-test-live
38-
- name: Ws Live Tests (spot)
39-
run: npm run ws-tests-spot
40-
- name: Ws Live Tests (futures)
41-
run: npm run ws-tests-futures
4238
- name: CJS test
4339
run: npm run test-cjs
4440
- name: Package test
45-
run: npm run package-test
41+
run: npm run package-test
42+
- name: Ws Live Tests
43+
run: npm run ws-live-tests

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"ws-tests": "mocha ./tests/binance-class-ws.test.ts",
3333
"ws-tests-spot": "mocha ./tests/binance-ws-spot.test.ts --exit",
3434
"ws-tests-futures": "mocha ./tests/binance-ws-futures.test.ts --exit",
35+
"ws-api-userdata-tests": "mocha ./tests/binance-ws-api-userdata.test.ts --exit",
36+
"ws-live-tests": "mocha ./tests/binance-ws-spot.test.ts ./tests/binance-ws-futures.test.ts ./tests/binance-ws-api-userdata.test.ts ./tests/binance-ws-api-ticker.test.ts --exit",
3537
"test-debug": "mocha --inspect-brk",
3638
"lint": "eslint src/",
3739
"cover": "istanbul cover _mocha --report lcovonly",

src/node-binance-api.ts

Lines changed: 406 additions & 79 deletions
Large diffs are not rendered by default.
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import Binance from '../src/node-binance-api';
2+
import { assert } from 'chai';
3+
4+
const WARN_SHOULD_BE_OBJ = 'should be an object';
5+
const WARN_SHOULD_BE_NOT_NULL = 'should not be null';
6+
const WARN_SHOULD_HAVE_KEY = 'should have key ';
7+
const WARN_SHOULD_BE_TYPE = 'should be a ';
8+
const TIMEOUT = 30000;
9+
10+
const binance = new Binance().options({
11+
APIKEY: 'X4BHNSimXOK6RKs2FcKqExquJtHjMxz5hWqF0BBeVnfa5bKFMk7X0wtkfEz0cPrJ',
12+
APISECRET: 'x8gLihunpNq0d46F2q0TWJmeCDahX5LMXSlv3lSFNbMI3rujSOpTDKdhbcmPSf2i',
13+
test: true,
14+
verbose: false,
15+
httpsProxy: 'http://188.245.226.105:8911'
16+
});
17+
18+
describe('WebSocket API Ticker Price', function () {
19+
20+
describe('tickerPrice - Single Symbol', function () {
21+
it('should fetch price for a single symbol', async function () {
22+
this.timeout(TIMEOUT);
23+
24+
const result = await binance.tickerPrice('BTCUSDT');
25+
26+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
27+
assert(typeof result === 'object', WARN_SHOULD_BE_OBJ);
28+
assert(Object.prototype.hasOwnProperty.call(result, 'symbol'), WARN_SHOULD_HAVE_KEY + 'symbol');
29+
assert(Object.prototype.hasOwnProperty.call(result, 'price'), WARN_SHOULD_HAVE_KEY + 'price');
30+
assert(result.symbol === 'BTCUSDT', 'Symbol should be BTCUSDT');
31+
assert(typeof result.price === 'string', WARN_SHOULD_BE_TYPE + 'string');
32+
assert(parseFloat(result.price) > 0, 'Price should be positive');
33+
34+
console.log('Single symbol result:', result);
35+
});
36+
37+
it('should fetch price for another symbol', async function () {
38+
this.timeout(TIMEOUT);
39+
40+
const result = await binance.tickerPrice('ETHUSDT');
41+
42+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
43+
assert(typeof result === 'object', WARN_SHOULD_BE_OBJ);
44+
assert(result.symbol === 'ETHUSDT', 'Symbol should be ETHUSDT');
45+
assert(Object.prototype.hasOwnProperty.call(result, 'price'), WARN_SHOULD_HAVE_KEY + 'price');
46+
assert(parseFloat(result.price) > 0, 'Price should be positive');
47+
48+
console.log('ETHUSDT price:', result.price);
49+
});
50+
});
51+
52+
describe('tickerPrice - Multiple Symbols', function () {
53+
it('should fetch prices for multiple symbols', async function () {
54+
this.timeout(TIMEOUT);
55+
56+
const symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT'];
57+
const result = await binance.tickerPrice(undefined, symbols);
58+
59+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
60+
assert(Array.isArray(result), 'Result should be an array');
61+
assert(result.length === symbols.length, `Should return ${symbols.length} prices`);
62+
63+
result.forEach((ticker: any, index: number) => {
64+
assert(typeof ticker === 'object', WARN_SHOULD_BE_OBJ);
65+
assert(Object.prototype.hasOwnProperty.call(ticker, 'symbol'), WARN_SHOULD_HAVE_KEY + 'symbol');
66+
assert(Object.prototype.hasOwnProperty.call(ticker, 'price'), WARN_SHOULD_HAVE_KEY + 'price');
67+
assert(symbols.includes(ticker.symbol), `Symbol ${ticker.symbol} should be in requested list`);
68+
assert(typeof ticker.price === 'string', WARN_SHOULD_BE_TYPE + 'string');
69+
assert(parseFloat(ticker.price) > 0, `Price for ${ticker.symbol} should be positive`);
70+
});
71+
72+
console.log('Multiple symbols result:', result);
73+
});
74+
75+
it('should fetch prices for 5 symbols', async function () {
76+
this.timeout(TIMEOUT);
77+
78+
const symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'ADAUSDT', 'DOGEUSDT'];
79+
const result = await binance.tickerPrice(undefined, symbols);
80+
81+
assert(Array.isArray(result), 'Result should be an array');
82+
assert(result.length === symbols.length, `Should return ${symbols.length} prices`);
83+
84+
const returnedSymbols = result.map((ticker: any) => ticker.symbol);
85+
symbols.forEach(symbol => {
86+
assert(returnedSymbols.includes(symbol), `Should include ${symbol}`);
87+
});
88+
89+
console.log(`Fetched prices for ${result.length} symbols`);
90+
});
91+
});
92+
93+
describe('tickerPrice - All Symbols', function () {
94+
it('should fetch prices for all symbols when no parameters provided', async function () {
95+
this.timeout(TIMEOUT);
96+
97+
const result = await binance.tickerPrice();
98+
99+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
100+
assert(Array.isArray(result), 'Result should be an array');
101+
assert(result.length > 0, 'Should return at least one symbol');
102+
103+
// Check first few results
104+
result.slice(0, 5).forEach((ticker: any) => {
105+
assert(typeof ticker === 'object', WARN_SHOULD_BE_OBJ);
106+
assert(Object.prototype.hasOwnProperty.call(ticker, 'symbol'), WARN_SHOULD_HAVE_KEY + 'symbol');
107+
assert(Object.prototype.hasOwnProperty.call(ticker, 'price'), WARN_SHOULD_HAVE_KEY + 'price');
108+
assert(typeof ticker.symbol === 'string', 'Symbol should be string');
109+
assert(typeof ticker.price === 'string', 'Price should be string');
110+
});
111+
112+
console.log(`Fetched prices for ${result.length} total symbols`);
113+
});
114+
});
115+
116+
describe('tickerPrice - With Symbol Status Filter', function () {
117+
it('should filter by TRADING status', async function () {
118+
this.timeout(TIMEOUT);
119+
120+
const result = await binance.tickerPrice('BTCUSDT', undefined, { symbolStatus: 'TRADING' });
121+
122+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
123+
assert(typeof result === 'object', WARN_SHOULD_BE_OBJ);
124+
assert(result.symbol === 'BTCUSDT', 'Symbol should be BTCUSDT');
125+
assert(Object.prototype.hasOwnProperty.call(result, 'price'), WARN_SHOULD_HAVE_KEY + 'price');
126+
127+
console.log('Filtered result (TRADING):', result);
128+
});
129+
130+
it('should filter multiple symbols by status', async function () {
131+
this.timeout(TIMEOUT);
132+
133+
const symbols = ['BTCUSDT', 'ETHUSDT'];
134+
const result = await binance.tickerPrice(undefined, symbols, { symbolStatus: 'TRADING' });
135+
136+
assert(Array.isArray(result), 'Result should be an array');
137+
// Result may be filtered, so length could be <= symbols.length
138+
assert(result.length <= symbols.length, 'Result should not exceed requested symbols');
139+
140+
console.log(`Filtered ${result.length} trading symbols from ${symbols.length} requested`);
141+
});
142+
});
143+
144+
describe('tickerPrice - Error Handling', function () {
145+
it('should reject when both symbol and symbols are provided', async function () {
146+
this.timeout(TIMEOUT);
147+
148+
try {
149+
await binance.tickerPrice('BTCUSDT', ['ETHUSDT']);
150+
assert.fail('Should have thrown an error');
151+
} catch (error: any) {
152+
assert(error.message.includes('Cannot specify both'), 'Should indicate parameter conflict');
153+
}
154+
});
155+
156+
it('should handle invalid symbol gracefully', async function () {
157+
this.timeout(TIMEOUT);
158+
159+
try {
160+
await binance.tickerPrice('INVALIDSYMBOL123');
161+
// May succeed or fail depending on Binance's handling
162+
// If it succeeds, it might return an empty result or error in result
163+
} catch (error: any) {
164+
// Expected to fail with invalid symbol
165+
assert(error !== null, 'Should have error information');
166+
}
167+
});
168+
});
169+
170+
describe('tickerPrice - Response Structure', function () {
171+
it('should include rate limit information', async function () {
172+
this.timeout(TIMEOUT);
173+
174+
// Note: Rate limit info is in the JSON-RPC response but may not be returned by our method
175+
// This test documents the expected structure
176+
177+
const result = await binance.tickerPrice('BTCUSDT');
178+
179+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
180+
assert(Object.prototype.hasOwnProperty.call(result, 'symbol'), 'Should have symbol');
181+
assert(Object.prototype.hasOwnProperty.call(result, 'price'), 'Should have price');
182+
183+
// Note: rateLimits may be stripped by our implementation
184+
// Full response from API includes: { symbol, price }
185+
});
186+
187+
it('should return consistent structure for single and multiple symbols', async function () {
188+
this.timeout(TIMEOUT);
189+
190+
const singleResult = await binance.tickerPrice('BTCUSDT');
191+
const multiResult = await binance.tickerPrice(undefined, ['BTCUSDT']);
192+
193+
assert(typeof singleResult === 'object', 'Single result should be object');
194+
assert(!Array.isArray(singleResult), 'Single result should not be array');
195+
196+
assert(Array.isArray(multiResult), 'Multi result should be array');
197+
assert(multiResult.length > 0, 'Multi result should have items');
198+
199+
// Compare structure
200+
const multiItem = multiResult[0];
201+
assert(Object.keys(singleResult).length > 0, 'Single result should have keys');
202+
assert(Object.keys(multiItem).length > 0, 'Multi item should have keys');
203+
});
204+
});
205+
206+
describe('tickerPrice - WebSocket API Features', function () {
207+
it('should use WebSocket API connection', async function () {
208+
this.timeout(TIMEOUT);
209+
210+
const startTime = Date.now();
211+
const result = await binance.tickerPrice('BTCUSDT');
212+
const endTime = Date.now();
213+
214+
const responseTime = endTime - startTime;
215+
216+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
217+
console.log(`WebSocket API response time: ${responseTime}ms`);
218+
219+
// WebSocket should be reasonably fast
220+
assert(responseTime < 10000, 'Should respond within 10 seconds');
221+
});
222+
223+
it('should handle concurrent requests', async function () {
224+
this.timeout(TIMEOUT);
225+
226+
const promises = [
227+
binance.tickerPrice('BTCUSDT'),
228+
binance.tickerPrice('ETHUSDT'),
229+
binance.tickerPrice('BNBUSDT')
230+
];
231+
232+
const results = await Promise.all(promises);
233+
234+
assert(results.length === 3, 'Should have 3 results');
235+
results.forEach((result, index) => {
236+
assert(result !== null, `Result ${index} should not be null`);
237+
assert(typeof result === 'object', `Result ${index} should be object`);
238+
assert(Object.prototype.hasOwnProperty.call(result, 'price'), `Result ${index} should have price`);
239+
});
240+
241+
console.log('Concurrent requests completed successfully');
242+
});
243+
});
244+
245+
describe('tickerPrice - Price Validation', function () {
246+
it('should return valid numeric price strings', async function () {
247+
this.timeout(TIMEOUT);
248+
249+
const result = await binance.tickerPrice('BTCUSDT');
250+
251+
assert(result !== null, WARN_SHOULD_BE_NOT_NULL);
252+
assert(typeof result.price === 'string', 'Price should be string');
253+
254+
const priceFloat = parseFloat(result.price);
255+
assert(!isNaN(priceFloat), 'Price should be valid number');
256+
assert(isFinite(priceFloat), 'Price should be finite');
257+
assert(priceFloat > 0, 'Price should be positive');
258+
259+
// Check decimal format
260+
assert(/^\d+(\.\d+)?$/.test(result.price), 'Price should match decimal pattern');
261+
262+
console.log(`BTCUSDT price: ${result.price} (${priceFloat})`);
263+
});
264+
265+
it('should return prices with appropriate precision', async function () {
266+
this.timeout(TIMEOUT);
267+
268+
const symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT'];
269+
const results = await binance.tickerPrice(undefined, symbols);
270+
271+
results.forEach((ticker: any) => {
272+
const decimalPlaces = (ticker.price.split('.')[1] || '').length;
273+
assert(decimalPlaces >= 0, `Price for ${ticker.symbol} should have decimal places`);
274+
assert(decimalPlaces <= 8, `Price for ${ticker.symbol} should not exceed 8 decimal places`);
275+
276+
console.log(`${ticker.symbol}: ${ticker.price} (${decimalPlaces} decimals)`);
277+
});
278+
});
279+
});
280+
});

0 commit comments

Comments
 (0)