jspurefix 5.6.1 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEMO_PORT_PLAN.md +1 -1
- package/README.md +256 -587
- package/dist/runtime/session-launcher.d.ts +2 -0
- package/dist/runtime/session-launcher.js +14 -0
- package/dist/runtime/session-launcher.js.map +1 -1
- package/dist/transport/ascii/ascii-session.d.ts +1 -0
- package/dist/transport/ascii/ascii-session.js +19 -0
- package/dist/transport/ascii/ascii-session.js.map +1 -1
- package/dist/transport/http/http-acceptor.js +2 -2
- package/dist/transport/http/http-acceptor.js.map +1 -1
- package/dist/transport/session/fix-session.d.ts +1 -0
- package/dist/transport/session/fix-session.js +7 -0
- package/dist/transport/session/fix-session.js.map +1 -1
- package/jsfix.test_client.txt +75 -71
- package/jsfix.test_server.txt +72 -68
- package/package.json +2 -4
- package/src/runtime/session-launcher.ts +18 -0
- package/src/transport/ascii/ascii-session.ts +20 -0
- package/src/transport/http/http-acceptor.ts +2 -2
- package/src/transport/session/fix-session.ts +14 -0
- package/src/util/unzip.js +54 -184
|
@@ -320,6 +320,26 @@ export abstract class AsciiSession extends FixSession {
|
|
|
320
320
|
this.sessionLogger.info('coordinator reset transient state for reconnect')
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
protected override async onPreLogon (): Promise<void> {
|
|
324
|
+
if (!this.config.description.ResetSeqNumFlag) return
|
|
325
|
+
|
|
326
|
+
this.sessionLogger.info('initiator has ResetSeqNumFlag=Y, resetting sequences before logon')
|
|
327
|
+
await this.coordinator.resetSession('initiator ResetSeqNumFlag=Y')
|
|
328
|
+
|
|
329
|
+
const transmitter = this.transport?.transmitter as AsciiMsgTransmitter | undefined
|
|
330
|
+
if (transmitter) {
|
|
331
|
+
transmitter.msgSeqNum = this.coordinator.nextSenderSeqNum
|
|
332
|
+
}
|
|
333
|
+
this.sessionState.lastPeerMsgSeqNum = this.coordinator.lastProcessedPeerSeqNum
|
|
334
|
+
|
|
335
|
+
if (this.store) {
|
|
336
|
+
this.store.clear()
|
|
337
|
+
this.resender = new FixMsgAsciiStoreResend(this.store, this.config)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.sessionLogger.info(`pre-logon reset complete: encoderSeqNum=${transmitter?.msgSeqNum}`)
|
|
341
|
+
}
|
|
342
|
+
|
|
323
343
|
protected override txOnEncoded (msgType: string, data: string, hdr: ILooseObject): void {
|
|
324
344
|
super.txOnEncoded(msgType, data, hdr)
|
|
325
345
|
// Store the encoded message in the session store for recovery/resend
|
|
@@ -7,7 +7,7 @@ import { FixDuplex, StringDuplex, StringDuplexTraits } from '../duplex'
|
|
|
7
7
|
import express = require('express')
|
|
8
8
|
import * as bodyParser from 'body-parser'
|
|
9
9
|
import * as http from 'http'
|
|
10
|
-
import {
|
|
10
|
+
import { randomUUID } from 'crypto'
|
|
11
11
|
import { inject, injectable } from 'tsyringe'
|
|
12
12
|
import { DITokens } from '../../runtime/di-tokens'
|
|
13
13
|
|
|
@@ -54,7 +54,7 @@ export class HttpAcceptor extends FixAcceptor {
|
|
|
54
54
|
private saveTransport (tid: number, transport: MsgTransport): string {
|
|
55
55
|
this.transports[tid] = transport
|
|
56
56
|
const keys: string[] = Object.keys(this.transports)
|
|
57
|
-
const a =
|
|
57
|
+
const a = randomUUID()
|
|
58
58
|
this.keys.set(a, transport)
|
|
59
59
|
this.logger.info(`new transport id = ${tid} token = ${a} created total transports = ${keys.length}`)
|
|
60
60
|
this.emit('transport', transport)
|
|
@@ -95,6 +95,11 @@ export abstract class FixSession extends events.EventEmitter {
|
|
|
95
95
|
|
|
96
96
|
private async waitPromise (): Promise<number> {
|
|
97
97
|
const logger = this.sessionLogger
|
|
98
|
+
if (this.initiator) {
|
|
99
|
+
// Hook for subclasses to reset sequences when ResetSeqNumFlag=Y is configured —
|
|
100
|
+
// must run after the store is loaded, before sendLogon stamps a seq num.
|
|
101
|
+
await this.onPreLogon()
|
|
102
|
+
}
|
|
98
103
|
return await new Promise<any>((resolve, reject) => {
|
|
99
104
|
if (this.initiator) {
|
|
100
105
|
logger.debug(`initiator sending logon state = ${this.stateString()}`)
|
|
@@ -398,6 +403,15 @@ export abstract class FixSession extends events.EventEmitter {
|
|
|
398
403
|
// Override in subclass to reset coordinator/transient state
|
|
399
404
|
}
|
|
400
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Called for initiators after the store is loaded but before the Logon is sent.
|
|
408
|
+
* Override to reset sequences/store when ResetSeqNumFlag=Y so the Logon goes out
|
|
409
|
+
* with MsgSeqNum=1 instead of the recovered sender seq num.
|
|
410
|
+
*/
|
|
411
|
+
protected async onPreLogon (): Promise<void> {
|
|
412
|
+
// Default no-op
|
|
413
|
+
}
|
|
414
|
+
|
|
401
415
|
protected stop (error: Error | null = null): void {
|
|
402
416
|
if (this.sessionState.state === SessionState.Stopped) {
|
|
403
417
|
return
|
package/src/util/unzip.js
CHANGED
|
@@ -1,202 +1,72 @@
|
|
|
1
1
|
const yauzl = require('yauzl')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const fs = require('fs')
|
|
4
|
-
const
|
|
5
|
-
const Transform = require('stream').Transform
|
|
4
|
+
const { pipeline } = require('stream/promises')
|
|
6
5
|
|
|
7
|
-
//
|
|
6
|
+
// Extracts a zip file into the current working directory.
|
|
7
|
+
//
|
|
8
|
+
// Replaces an older copy of the yauzl example unzip script. The previous
|
|
9
|
+
// version drove a 60Hz progress interval via terminal backspaces and did
|
|
10
|
+
// not attach error handlers to any of the streams in its pipeline. In a
|
|
11
|
+
// non-TTY environment (eg. GitHub Actions) the backspaces produced
|
|
12
|
+
// unreadable output; worse, if any stream errored silently the script
|
|
13
|
+
// hung forever and CI runs were cancelled at the workflow timeout.
|
|
14
|
+
//
|
|
15
|
+
// Follows the canonical yauzl lazyEntries pattern: install 'entry' /
|
|
16
|
+
// 'end' / 'error' handlers once on the zipfile, drive the next read
|
|
17
|
+
// with zipfile.readEntry() at the end of each entry's work.
|
|
18
|
+
// NB: requires yauzl >= 3.3.1 — 3.3.0 truncates inflated streams on
|
|
19
|
+
// Node 26+ (the next entry's readStream stalls without emitting 'end').
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let endArg
|
|
13
|
-
const args = process.argv.slice(2)
|
|
14
|
-
for (let i = 0; i < args.length; i++) {
|
|
15
|
-
const arg = args[i]
|
|
16
|
-
if (arg === '--offset') {
|
|
17
|
-
i += 1
|
|
18
|
-
offsetArg = parseInt(args[i])
|
|
19
|
-
if (isNaN(offsetArg)) throw new Error('--offset argument not parsable as an int')
|
|
20
|
-
} else if (arg === '--len') {
|
|
21
|
-
i += 1
|
|
22
|
-
lenArg = parseInt(args[i])
|
|
23
|
-
if (isNaN(lenArg)) throw new Error('--len argument not parsable as an int')
|
|
24
|
-
} else if (arg === '--end') {
|
|
25
|
-
i += 1
|
|
26
|
-
endArg = parseInt(args[i])
|
|
27
|
-
if (isNaN(endArg)) throw new Error('--end argument not parsable as an int')
|
|
28
|
-
} else if (['-h', '--help'].includes(arg)) {
|
|
29
|
-
// print help
|
|
30
|
-
zipFilePath = null
|
|
31
|
-
break
|
|
32
|
-
} else if (/^--/.test(arg)) {
|
|
33
|
-
throw new Error('unrecognized option: ' + arg)
|
|
34
|
-
} else {
|
|
35
|
-
if (zipFilePath != null) throw new Error('too many arguments')
|
|
36
|
-
zipFilePath = arg
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (zipFilePath == null || /^-/.test(zipFilePath) || (lenArg != null && endArg != null)) {
|
|
40
|
-
console.log(
|
|
41
|
-
'usage: node unzip.js [options] path/to/file.zip\n' +
|
|
42
|
-
'\n' +
|
|
43
|
-
'unzips the specified zip file into the current directory\n' +
|
|
44
|
-
'\n' +
|
|
45
|
-
'options:\n' +
|
|
46
|
-
' --offset START\n' +
|
|
47
|
-
' --len LEN\n' +
|
|
48
|
-
' --end END\n' +
|
|
49
|
-
' interprets the middle of the specified file as a zipfile.\n' +
|
|
50
|
-
' starting START number of bytes in from the beginning (default 0).\n' +
|
|
51
|
-
' end with length of LEN (default is all the way to the end of the file).\n' +
|
|
52
|
-
' or end at byte offset END (exclusive) (default is the end of the file).\n' +
|
|
53
|
-
' end can be negative to count backwards from the end of the file\n' +
|
|
54
|
-
' (example, `--end -1` excludes the last byte of the file).\n' +
|
|
55
|
-
'')
|
|
21
|
+
const zipFilePath = process.argv[2]
|
|
22
|
+
if (!zipFilePath) {
|
|
23
|
+
console.error('usage: node unzip.js path/to/file.zip')
|
|
56
24
|
process.exit(1)
|
|
57
25
|
}
|
|
58
26
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (err == null) return cb() // already exists
|
|
63
|
-
|
|
64
|
-
const parent = path.dirname(dir)
|
|
65
|
-
mkdirp(parent, function () {
|
|
66
|
-
process.stdout.write(dir.replace(/\/$/, '') + '/\n')
|
|
67
|
-
fs.mkdir(dir, cb)
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (offsetArg != null || lenArg != null || endArg != null) {
|
|
73
|
-
openMiddleOfFile(zipFilePath, { lazyEntries: true }, offsetArg, lenArg, endArg, handleZipFile)
|
|
74
|
-
} else {
|
|
75
|
-
yauzl.open(zipFilePath, { lazyEntries: true }, handleZipFile)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function openMiddleOfFile (zipFilePath, options, offsetArg, lenArg, endArg, handleZipFile) {
|
|
79
|
-
fs.open(zipFilePath, 'r', function (err, fd) {
|
|
80
|
-
if (err != null) throw err
|
|
81
|
-
fs.fstat(fd, function (err, stats) {
|
|
27
|
+
function extract (zipFilePath) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
yauzl.open(zipFilePath, { lazyEntries: true }, (err, zipfile) => {
|
|
82
30
|
if (err) {
|
|
83
|
-
|
|
31
|
+
reject(err)
|
|
32
|
+
return
|
|
84
33
|
}
|
|
85
|
-
// resolve optional parameters
|
|
86
|
-
if (offsetArg == null) offsetArg = 0
|
|
87
|
-
if (lenArg == null && endArg == null) endArg = stats.size
|
|
88
|
-
if (endArg == null) endArg = lenArg + offsetArg
|
|
89
|
-
else if (endArg < 0) endArg = stats.size + endArg
|
|
90
|
-
// validate parameters
|
|
91
|
-
if (offsetArg < 0) throw new Error('--offset < 0')
|
|
92
|
-
if (lenArg < 0) throw new Error('--len < 0')
|
|
93
|
-
if (offsetArg > endArg) throw new Error('--offset > --end')
|
|
94
|
-
if (endArg > stats.size) throw new Error('--end/--len goes past EOF')
|
|
95
34
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
yauzl.RandomAccessReader.call(this)
|
|
99
|
-
}
|
|
100
|
-
util.inherits(MiddleOfFileReader, yauzl.RandomAccessReader)
|
|
101
|
-
// implement required and option methods
|
|
102
|
-
MiddleOfFileReader.prototype._readStreamForRange = function (start, end) {
|
|
103
|
-
return fs.createReadStream(null, {
|
|
104
|
-
fd,
|
|
105
|
-
// shift the start and end offsets
|
|
106
|
-
start: start + offsetArg,
|
|
107
|
-
end: end + offsetArg - 1, // the -1 is because fs.createReadStream()'s end option is inclusive
|
|
108
|
-
autoClose: false
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
MiddleOfFileReader.prototype.read = function (buffer, offset, length, position, callback) {
|
|
112
|
-
// shift the position
|
|
113
|
-
fs.read(fd, buffer, offset, length, position + offsetArg, callback)
|
|
114
|
-
}
|
|
115
|
-
MiddleOfFileReader.prototype.close = function (callback) {
|
|
116
|
-
fs.close(fd, callback)
|
|
117
|
-
}
|
|
35
|
+
zipfile.on('error', reject)
|
|
36
|
+
zipfile.on('end', resolve)
|
|
118
37
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function handleZipFile (err, zipfile) {
|
|
125
|
-
if (err) throw err
|
|
38
|
+
zipfile.on('entry', (entry) => {
|
|
39
|
+
const next = () => zipfile.readEntry()
|
|
126
40
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
function decrementHandleCount () {
|
|
134
|
-
handleCount--
|
|
135
|
-
if (handleCount === 0) {
|
|
136
|
-
console.log('all input and output handles closed')
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
incrementHandleCount()
|
|
141
|
-
zipfile.on('close', function () {
|
|
142
|
-
console.log('closed input file')
|
|
143
|
-
decrementHandleCount()
|
|
144
|
-
})
|
|
41
|
+
if (/\/$/.test(entry.fileName)) {
|
|
42
|
+
fs.promises.mkdir(entry.fileName, { recursive: true })
|
|
43
|
+
.then(next, reject)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
145
46
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
zipfile.readEntry()
|
|
153
|
-
})
|
|
154
|
-
} else {
|
|
155
|
-
// ensure parent directory exists
|
|
156
|
-
mkdirp(path.dirname(entry.fileName), function () {
|
|
157
|
-
zipfile.openReadStream(entry, function (err, readStream) {
|
|
158
|
-
if (err) throw err
|
|
159
|
-
// report progress through large files
|
|
160
|
-
let byteCount = 0
|
|
161
|
-
const totalBytes = entry.uncompressedSize
|
|
162
|
-
let lastReportedString = byteCount + '/' + totalBytes + ' 0%'
|
|
163
|
-
process.stdout.write(entry.fileName + '...' + lastReportedString)
|
|
164
|
-
function reportString (msg) {
|
|
165
|
-
let clearString = ''
|
|
166
|
-
for (let i = 0; i < lastReportedString.length; i++) {
|
|
167
|
-
clearString += '\b'
|
|
168
|
-
if (i >= msg.length) {
|
|
169
|
-
clearString += ' \b'
|
|
47
|
+
fs.promises.mkdir(path.dirname(entry.fileName), { recursive: true })
|
|
48
|
+
.then(() => new Promise((res, rej) => {
|
|
49
|
+
zipfile.openReadStream(entry, (err, readStream) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
rej(err)
|
|
52
|
+
return
|
|
170
53
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const filter = new Transform()
|
|
180
|
-
filter._transform = function (chunk, encoding, cb) {
|
|
181
|
-
byteCount += chunk.length
|
|
182
|
-
cb(null, chunk)
|
|
183
|
-
}
|
|
184
|
-
filter._flush = function (cb) {
|
|
185
|
-
clearInterval(progressInterval)
|
|
186
|
-
reportString('')
|
|
187
|
-
// delete the "..."
|
|
188
|
-
process.stdout.write('\b \b\b \b\b \b\n')
|
|
189
|
-
cb()
|
|
190
|
-
zipfile.readEntry()
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// pump file contents
|
|
194
|
-
const writeStream = fs.createWriteStream(entry.fileName)
|
|
195
|
-
incrementHandleCount()
|
|
196
|
-
writeStream.on('close', decrementHandleCount)
|
|
197
|
-
readStream.pipe(filter).pipe(writeStream)
|
|
198
|
-
})
|
|
54
|
+
pipeline(readStream, fs.createWriteStream(entry.fileName))
|
|
55
|
+
.then(() => {
|
|
56
|
+
console.log(`${entry.fileName} (${entry.uncompressedSize} bytes)`)
|
|
57
|
+
res()
|
|
58
|
+
}, rej)
|
|
59
|
+
})
|
|
60
|
+
}))
|
|
61
|
+
.then(next, reject)
|
|
199
62
|
})
|
|
200
|
-
|
|
63
|
+
|
|
64
|
+
zipfile.readEntry()
|
|
65
|
+
})
|
|
201
66
|
})
|
|
202
67
|
}
|
|
68
|
+
|
|
69
|
+
extract(zipFilePath).catch((err) => {
|
|
70
|
+
console.error(`unzip failed: ${err.message}`)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
})
|