jspurefix
Version:
pure node js fix engine
116 lines (105 loc) • 3.94 kB
text/typescript
import { MsgTransport } from '../msg-transport'
import { ITcpTransportDescription } from '../session-description'
import { FixInitiator } from '../fix-initiator'
import { IJsFixConfig, IJsFixLogger } from '../../config'
import { TcpDuplex, FixDuplex } from '../duplex'
import * as util from 'util'
import * as net from 'net'
export enum InitiatorState {
Idle = 1,
Connecting = 2,
Connected = 3,
Stopped = 4
}
export class TcpInitiator extends FixInitiator {
public tcp: ITcpTransportDescription
public state: InitiatorState = InitiatorState.Idle
private readonly logger: IJsFixLogger
private duplex: FixDuplex
constructor (public readonly jsFixConfig: IJsFixConfig) {
super(jsFixConfig.description.application)
this.logger = jsFixConfig.logFactory.logger(`${this.application.name}:TcpInitiator`)
if (!this.application) {
const e: Error = new Error(`no application in session description.`)
this.logger.error(e)
throw e
}
this.tcp = this.application.tcp
if (!this.tcp) {
const e = new Error(`no tcp in session description need tcp { host: hostname, port: port }`)
this.logger.error(e)
throw e
}
}
public end (): void {
switch (this.state) {
case InitiatorState.Connected: {
this.logger.info('end')
this.duplex.end()
this.state = InitiatorState.Stopped
break
}
default: {
this.logger.info(`end :state ${this.state}`)
}
}
}
public connect (timeoutSeconds: number): Promise<MsgTransport> {
return new Promise<MsgTransport>(async (resolve, reject) => {
switch (this.state) {
case InitiatorState.Idle: {
this.state = InitiatorState.Connecting
this.logger.info(`connecting with timeout ${timeoutSeconds}`)
this.tryConnect().then((t: MsgTransport) => resolve(t)).catch((e: Error) => {
this.logger.error(e)
this.repeatConnect(timeoutSeconds).then((t: MsgTransport) => resolve(t)).catch((e: Error) => reject(e))
})
break
}
default:
const e: Error = new Error(`connect not valid from non idle state`)
this.logger.warning(`rejecting from state ${this.state}`)
reject(e)
}
})
}
private tryConnect (): Promise<MsgTransport> {
return new Promise<MsgTransport>((resolve, reject) => {
const tcp = this.tcp
this.logger.info(`tryConnect ${tcp.host}:${tcp.port}`)
const socket = net.createConnection(tcp, () => {
this.logger.info(`net.createConnection cb, resolving`)
this.duplex = new TcpDuplex(socket)
resolve(new MsgTransport(0, this.jsFixConfig, this.duplex))
})
socket.on('error', (e) => {
reject(e)
})
})
}
private repeatConnect (timeoutSeconds: number): Promise<MsgTransport> {
return new Promise<MsgTransport>(async (resolve, reject) => {
const application = this.application
const promisify = util.promisify
const timeoutPromise = promisify(setTimeout)
let retries = 0
let timer = setInterval(() => {
++retries
this.tryConnect().then((t: MsgTransport) => {
this.state = InitiatorState.Connected
clearInterval(timer)
resolve(t)
}).catch((e: Error) => {
this.logger.info(`${application.name}: retries ${retries} ${e.message}`)
})
}, application.reconnectSeconds * 1000)
timeoutPromise(timeoutSeconds * 1000).then(() => {
clearInterval(timer)
this.state = InitiatorState.Stopped
const e = new Error(`${application.name}: timeout of ${timeoutSeconds} whilst connecting`)
this.logger.warning(`repeatConnect reject with message ${e.message}`)
reject(e)
})
})
}
}