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.
@@ -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 { v4 as uuidv4 } from 'uuid'
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 = uuidv4()
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 util = require('util')
5
- const Transform = require('stream').Transform
4
+ const { pipeline } = require('stream/promises')
6
5
 
7
- // thanks to https://github.com/thejoshwolfe/yauzl/blob/master/examples/unzip.js
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
- let zipFilePath = null
10
- let offsetArg
11
- let lenArg
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 mkdirp (dir, cb) {
60
- if (dir === '.') return cb()
61
- fs.stat(dir, function (err) {
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
- console.log(err)
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
- // extend RandomAccessReader
97
- function MiddleOfFileReader () {
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
- yauzl.fromRandomAccessReader(new MiddleOfFileReader(), endArg - offsetArg, options, handleZipFile)
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
- // track when we've closed all our file handles
128
- let handleCount = 0
129
-
130
- function incrementHandleCount () {
131
- handleCount++
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
- zipfile.readEntry()
147
- zipfile.on('entry', function (entry) {
148
- if (/\/$/.test(entry.fileName)) {
149
- // directory file names end with '/'
150
- mkdirp(entry.fileName, function () {
151
- if (err) throw err
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
- process.stdout.write(clearString + msg)
173
- lastReportedString = msg
174
- }
175
- // report progress at 60Hz
176
- const progressInterval = setInterval(function () {
177
- reportString(byteCount + '/' + totalBytes + ' ' + ((byteCount / totalBytes * 100) | 0) + '%')
178
- }, 1000 / 60)
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
+ })