UNPKG

betfair-node

Version:

A comprehensive Node.js TypeScript library for the Betfair Exchange API, providing both JSON-RPC API integration and real-time Exchange Stream API support for automated betting and trading applications.

332 lines 13.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.createAndConnectRecordingStream = exports.createAndConnectStream = exports.restartStream = exports.setAuthCredentials = exports.getOrderStreamCache = exports.getStreamCache = exports.subscribeToOrders = exports.subscribeToMarkets = exports.authenticateStream = exports.closeStream = exports.openStream = exports.createStreamApiState = void 0; const tls = __importStar(require("tls")); const readline_1 = require("readline"); const betfair_stream_decoder_1 = require("./betfair-stream-decoder"); const heartbeat_1 = require("./heartbeat"); const utils_1 = require("./utils"); const STREAM_URL = 'stream-api.betfair.com'; const STREAM_PORT = 443; /** * Creates initial stream API state */ const createStreamApiState = (authToken, appKey, segmentationEnabled, conflateMs, heartbeatMs, audCurrencyRate, marketChangeCallback, orderChangeCallback, rawDataCallback) => { const onHeartAttack = () => { console.warn('Heartbeat timeout detected - stream may be stale, but continuing...'); // Don't crash - just log a warning and continue // In production, you might want to implement reconnection logic here }; return { authenticationStatus: false, authToken, appKey, pendingPackets: {}, streamDecoder: (0, betfair_stream_decoder_1.createStreamDecoderState)(audCurrencyRate), heartbeat: (0, heartbeat_1.createHeartbeatState)(heartbeatMs, onHeartAttack), segmentationEnabled, conflateMs, heartbeatMs, audCurrencyRate, lastMarkets: [], marketChangeCallback, orderChangeCallback, rawDataCallback, }; }; exports.createStreamApiState = createStreamApiState; /** * Opens the TLS stream connection */ const openStream = (state) => { return new Promise((resolve, reject) => { try { const tlsSocket = tls.connect({ host: STREAM_URL, port: STREAM_PORT, }, () => { console.log('Stream connected successfully'); resolve({ ...state, tlsSocket, authenticationStatus: false, }); }); const readline = (0, readline_1.createInterface)({ input: tlsSocket, }); const streamDecoderCallbacks = { onMarketChange: state.marketChangeCallback, onOrderChange: state.orderChangeCallback, onStatus: (statusMessage) => handleStatusMessage(state, statusMessage), onConnection: (connectionMessage) => handleConnectionMessage(state, connectionMessage), onHeartbeat: () => handleHeartbeat(state), }; readline.on('line', (data) => { // Call raw data callback first (for recording raw transmissions) if (state.rawDataCallback) { state.rawDataCallback(data); } // Refresh heartbeat on each data packet (this will start it if not already running) const refreshedHeartbeat = state.heartbeat.isBeating ? (0, heartbeat_1.refreshHeartbeat)(state.heartbeat) : (0, heartbeat_1.startHeartbeat)(state.heartbeat); const updatedStreamDecoder = (0, betfair_stream_decoder_1.processDataPacket)(state.streamDecoder, streamDecoderCallbacks, data); // Update state (in real implementation, this would be managed by state management) Object.assign(state, { heartbeat: refreshedHeartbeat, streamDecoder: updatedStreamDecoder, }); }); readline.on('close', () => { console.warn('Stream has closed'); Object.assign(state, { authenticationStatus: false }); }); tlsSocket.on('error', (error) => { console.error('Stream error:', error); reject(error); }); Object.assign(state, { readline, tlsSocket }); } catch (error) { reject(error); } }); }; exports.openStream = openStream; /** * Closes the stream connection */ const closeStream = (state) => { if (state.tlsSocket) { try { console.log('Destroying TLS connection'); state.tlsSocket.destroy(); } catch (error) { console.error('Error destroying stream:', error); } } const stoppedHeartbeat = (0, heartbeat_1.stopHeartbeat)(state.heartbeat); return { ...state, tlsSocket: undefined, readline: undefined, authenticationStatus: false, heartbeat: stoppedHeartbeat, }; }; exports.closeStream = closeStream; /** * Authenticates the stream connection */ const authenticateStream = (state) => { if (!state.tlsSocket) { throw new Error('Stream not connected'); } const packetId = (0, utils_1.generatePacketId)(); const authMessage = { op: 'authentication', appKey: state.appKey, session: state.authToken, id: packetId, }; const payload = JSON.stringify(authMessage); console.log(`Authenticating stream with payload: ${payload}`); const updatedPendingPackets = { ...state.pendingPackets, [packetId]: 'AWAITING', }; state.tlsSocket.write(`${payload}\r\n`); return { ...state, authenticationStatus: true, pendingPackets: updatedPendingPackets, }; }; exports.authenticateStream = authenticateStream; /** * Subscribes to market updates */ const subscribeToMarkets = (state, marketIds) => { if (!state.tlsSocket || !state.authenticationStatus) { throw new Error('Stream not connected or authenticated'); } // Clear cache and update subscribed markets let updatedStreamDecoder = (0, betfair_stream_decoder_1.resetStreamDecoder)(state.streamDecoder); updatedStreamDecoder = (0, betfair_stream_decoder_1.updateSubscribedMarkets)(updatedStreamDecoder, marketIds); // Stop heartbeat if no markets let updatedHeartbeat = state.heartbeat; if (marketIds.length === 0) { updatedHeartbeat = (0, heartbeat_1.stopHeartbeat)(state.heartbeat); } const message = { id: (0, utils_1.generatePacketId)(), op: 'marketSubscription', marketFilter: { marketIds, }, segmentationEnabled: state.segmentationEnabled, conflateMs: state.conflateMs, heartbeatMs: state.heartbeatMs, }; const payload = JSON.stringify(message); console.log(`Subscribing to markets: ${payload}`); const updatedPendingPackets = { ...state.pendingPackets, [message.id]: 'AWAITING', }; state.tlsSocket.write(`${payload}\r\n`); return { ...state, streamDecoder: updatedStreamDecoder, heartbeat: updatedHeartbeat, lastMarketSubscription: message, lastMarkets: marketIds, pendingPackets: updatedPendingPackets, }; }; exports.subscribeToMarkets = subscribeToMarkets; /** * Subscribes to order updates */ const subscribeToOrders = (state, orderFilter) => { if (!state.tlsSocket || !state.authenticationStatus) { throw new Error('Stream not connected or authenticated'); } // Clear order cache let updatedStreamDecoder = (0, betfair_stream_decoder_1.clearOrderCache)(state.streamDecoder); const message = { id: (0, utils_1.generatePacketId)(), op: 'orderSubscription', orderFilter, segmentationEnabled: state.segmentationEnabled, conflateMs: state.conflateMs, heartbeatMs: state.heartbeatMs, }; const payload = JSON.stringify(message); console.log(`🔧 Order subscription payload: ${payload}`); console.log('📋 Order subscription details:', { operation: message.op, hasFilter: !!orderFilter, filterDetails: orderFilter, segmentation: message.segmentationEnabled, conflate: message.conflateMs, heartbeat: message.heartbeatMs }); const updatedPendingPackets = { ...state.pendingPackets, [message.id]: 'AWAITING', }; state.tlsSocket.write(`${payload}\r\n`); return { ...state, streamDecoder: updatedStreamDecoder, lastOrderSubscription: message, pendingPackets: updatedPendingPackets, }; }; exports.subscribeToOrders = subscribeToOrders; /** * Gets the current market cache */ const getStreamCache = (state) => { return (0, betfair_stream_decoder_1.getMarketCache)(state.streamDecoder); }; exports.getStreamCache = getStreamCache; /** * Gets the current order cache */ const getOrderStreamCache = (state) => { return (0, betfair_stream_decoder_1.getOrderCache)(state.streamDecoder); }; exports.getOrderStreamCache = getOrderStreamCache; /** * Updates authentication credentials */ const setAuthCredentials = (state, appKey, authToken) => ({ ...state, appKey, authToken, }); exports.setAuthCredentials = setAuthCredentials; /** * Restarts the stream connection */ const restartStream = async (state) => { console.log('Restarting stream connection'); let updatedState = (0, exports.closeStream)(state); updatedState = await (0, exports.openStream)(updatedState); updatedState = (0, exports.authenticateStream)(updatedState); if (updatedState.lastMarketSubscription) { updatedState = (0, exports.subscribeToMarkets)(updatedState, updatedState.lastMarkets); } return updatedState; }; exports.restartStream = restartStream; // Event Handlers const handleStatusMessage = (state, statusMessage) => { if (statusMessage.statusCode !== 'SUCCESS') { throw new Error(`Operation failed: ${JSON.stringify(statusMessage)}`); } // Remove from pending packets delete state.pendingPackets[statusMessage.id]; }; const handleConnectionMessage = (state, connectionMessage) => { Object.assign(state, { connectionId: connectionMessage.connectionId }); }; const handleHeartbeat = (state) => { const refreshedHeartbeat = (0, heartbeat_1.refreshHeartbeat)(state.heartbeat); Object.assign(state, { heartbeat: refreshedHeartbeat }); }; // Higher-level API functions const createAndConnectStream = async (authToken, appKey, segmentationEnabled, conflateMs, heartbeatMs, audCurrencyRate, marketChangeCallback, orderChangeCallback, rawDataCallback) => { let state = (0, exports.createStreamApiState)(authToken, appKey, segmentationEnabled, conflateMs, heartbeatMs, audCurrencyRate, marketChangeCallback, orderChangeCallback, rawDataCallback); state = await (0, exports.openStream)(state); state = (0, exports.authenticateStream)(state); return state; }; exports.createAndConnectStream = createAndConnectStream; /** * Creates and connects a stream specifically optimized for recording with more lenient heartbeat */ const createAndConnectRecordingStream = async (authToken, appKey, segmentationEnabled, conflateMs, audCurrencyRate, marketChangeCallback, orderChangeCallback, rawDataCallback) => { // Use a longer heartbeat for recording (30 seconds instead of 5) const recordingHeartbeatMs = 30000; return (0, exports.createAndConnectStream)(authToken, appKey, segmentationEnabled, conflateMs, recordingHeartbeatMs, audCurrencyRate, marketChangeCallback, orderChangeCallback, rawDataCallback); }; exports.createAndConnectRecordingStream = createAndConnectRecordingStream; //# sourceMappingURL=betfair-exchange-stream-api.js.map