betfair-exchange-api
Version:
A TypeScript client for the Betfair Exchange API
229 lines (228 loc) • 8.52 kB
JavaScript
"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.BetfairStreamClient = exports.SegmentType = exports.ChangeType = exports.StreamOperation = void 0;
const tls = __importStar(require("tls"));
const events_1 = require("events");
// ============================================================================
// Stream API Types
// ============================================================================
var StreamOperation;
(function (StreamOperation) {
StreamOperation["AUTHENTICATION"] = "authentication";
StreamOperation["MARKET_SUBSCRIPTION"] = "marketSubscription";
StreamOperation["ORDER_SUBSCRIPTION"] = "orderSubscription";
StreamOperation["HEARTBEAT"] = "heartbeat";
})(StreamOperation || (exports.StreamOperation = StreamOperation = {}));
var ChangeType;
(function (ChangeType) {
ChangeType["SUB_IMAGE"] = "SUB_IMAGE";
ChangeType["RESUB_DELTA"] = "RESUB_DELTA";
ChangeType["HEARTBEAT"] = "HEARTBEAT";
})(ChangeType || (exports.ChangeType = ChangeType = {}));
var SegmentType;
(function (SegmentType) {
SegmentType["SEG_START"] = "SEG_START";
SegmentType["SEG"] = "SEG";
SegmentType["SEG_END"] = "SEG_END";
})(SegmentType || (exports.SegmentType = SegmentType = {}));
class BetfairStreamClient extends events_1.EventEmitter {
constructor(appKey, sessionToken, config) {
super();
this.host = 'stream-api.betfair.com';
this.port = 443;
this.socket = null;
this.buffer = '';
this.connectionId = null;
this.authenticated = false;
this.requestIdCounter = 1;
this.appKey = appKey;
this.sessionToken = sessionToken;
}
connect() {
if (this.socket) {
this.socket.destroy();
this.socket = null;
}
this.socket = tls.connect({
host: this.host,
port: this.port,
}, () => {
this.emit('connected');
this.authenticate();
});
this.socket.on('data', (data) => this.handleData(data));
this.socket.on('close', () => {
this.authenticated = false;
this.connectionId = null;
this.emit('disconnected');
});
this.socket.on('error', (error) => this.emit('error', error));
}
disconnect() {
if (this.socket) {
this.socket.destroy();
this.socket = null;
}
}
authenticate() {
const message = {
op: StreamOperation.AUTHENTICATION,
id: this.getNextRequestId(),
appKey: this.appKey,
session: this.sessionToken,
};
this.sendMessage(message);
}
subscribeToMarkets(marketFilter = {}, marketDataFilter = {}, conflateMs, heartbeatMs, segmentationEnabled = true) {
if (!this.authenticated) {
throw new Error('Client not authenticated. Call authenticate() first.');
}
this.conflateMs = conflateMs;
this.heartbeatMs = heartbeatMs;
this.segmentationEnabled = segmentationEnabled;
const message = {
op: StreamOperation.MARKET_SUBSCRIPTION,
id: this.getNextRequestId(),
marketFilter,
marketDataFilter,
conflateMs,
heartbeatMs,
segmentationEnabled,
initialClk: this.marketSubscriptionInitialClk,
clk: this.marketSubscriptionClk,
};
this.sendMessage(message);
}
subscribeToOrders(orderFilter = {}, conflateMs, heartbeatMs, segmentationEnabled = true) {
if (!this.authenticated) {
throw new Error('Client not authenticated. Call authenticate() first.');
}
this.conflateMs = conflateMs;
this.heartbeatMs = heartbeatMs;
this.segmentationEnabled = segmentationEnabled;
const message = {
op: StreamOperation.ORDER_SUBSCRIPTION,
id: this.getNextRequestId(),
orderFilter,
conflateMs,
heartbeatMs,
segmentationEnabled,
initialClk: this.orderSubscriptionInitialClk,
clk: this.orderSubscriptionClk,
};
this.sendMessage(message);
}
sendHeartbeat() {
if (!this.authenticated) {
throw new Error('Client not authenticated. Call authenticate() first.');
}
const message = {
op: StreamOperation.HEARTBEAT,
id: this.getNextRequestId(),
};
this.sendMessage(message);
}
handleData(data) {
this.buffer += data.toString();
let newlineIndex;
while ((newlineIndex = this.buffer.indexOf('\r\n')) !== -1) {
const message = this.buffer.substring(0, newlineIndex);
this.buffer = this.buffer.substring(newlineIndex + 2);
try {
if (message.trim().length > 0) {
this.processMessage(JSON.parse(message));
}
}
catch (error) {
this.emit('error', new Error(`Failed to parse message: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
}
processMessage(message) {
switch (message.op) {
case 'connection':
this.connectionId = message.connectionId;
this.emit('connection', message);
break;
case 'status':
if (message.id === 1 && message.statusCode === 'SUCCESS') {
this.authenticated = true;
}
else if (message.errorCode) {
this.emit('error', new Error(`Status error: ${message.errorCode} - ${message.errorMessage}`));
}
this.emit('status', message);
break;
case 'mcm':
if (message.initialClk) {
this.marketSubscriptionInitialClk = message.initialClk;
}
if (message.clk) {
this.marketSubscriptionClk = message.clk;
}
this.emit('marketChange', message);
break;
case 'ocm':
if (message.initialClk) {
this.orderSubscriptionInitialClk = message.initialClk;
}
if (message.clk) {
this.orderSubscriptionClk = message.clk;
}
this.emit('orderChange', message);
break;
default:
this.emit('error', new Error(`Unknown message operation: ${message.op}`));
}
}
sendMessage(message) {
if (!this.socket) {
throw new Error('Not connected. Call connect() first.');
}
const jsonMessage = JSON.stringify(message);
this.socket.write(`${jsonMessage}\r\n`);
}
getNextRequestId() {
return this.requestIdCounter++;
}
getConnectionId() {
return this.connectionId;
}
isAuthenticated() {
return this.authenticated;
}
}
exports.BetfairStreamClient = BetfairStreamClient;