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
JavaScript
;
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