ml-testing-toolkit 18.15.1 → 18.16.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/.ncurc.yaml CHANGED
@@ -4,6 +4,7 @@ reject: [
4
4
  "postman-collection",
5
5
  "postman-sandbox",
6
6
  "jest",
7
- "@types/jest"
7
+ "@types/jest",
8
+ "uuid",
8
9
  ]
9
10
 
package/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
1
  # Changelog: [mojaloop/thirdparty-api-svc](https://github.com/mojaloop/thirdparty-api-svc)
2
+ ## [18.16.0](https://github.com/mojaloop/ml-testing-toolkit/compare/v18.15.1...v18.16.0) (2025-09-16)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **csi-1782:** fix MyEmitter memory leak ([#342](https://github.com/mojaloop/ml-testing-toolkit/issues/342)) ([c141048](https://github.com/mojaloop/ml-testing-toolkit/commit/c1410487a3c17824b0869b6920c2132d491a6007))
8
+
2
9
  ### [18.15.1](https://github.com/mojaloop/ml-testing-toolkit/compare/v18.15.0...v18.15.1) (2025-09-01)
3
10
 
4
11
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ml-testing-toolkit",
3
3
  "description": "Testing Toolkit for Mojaloop implementations",
4
- "version": "18.15.1",
4
+ "version": "18.16.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Vijaya Kumar Guthi, ModusBox Inc. ",
7
7
  "contributors": [
@@ -78,23 +78,23 @@
78
78
  "@hapi/hapi": "21.4.3",
79
79
  "@hapi/inert": "7.1.0",
80
80
  "@hapi/vision": "7.0.3",
81
- "@mojaloop/central-services-logger": "11.9.1",
82
- "@mojaloop/central-services-metrics": "12.6.0",
83
- "@mojaloop/ml-schema-transformer-lib": "2.7.7",
81
+ "@mojaloop/central-services-logger": "11.9.3",
82
+ "@mojaloop/central-services-metrics": "12.7.1",
83
+ "@mojaloop/ml-schema-transformer-lib": "2.7.8",
84
84
  "@mojaloop/ml-testing-toolkit-shared-lib": "14.2.0",
85
- "@mojaloop/sdk-standard-components": "19.16.7",
85
+ "@mojaloop/sdk-standard-components": "19.17.0",
86
86
  "@now-ims/hapi-now-auth": "2.1.0",
87
87
  "@types/socket.io": "3.0.2",
88
88
  "adm-zip": "0.5.16",
89
89
  "ajv-formats": "3.0.1",
90
90
  "atob": "2.1.2",
91
- "axios": "1.11.0",
91
+ "axios": "1.12.2",
92
92
  "chai": "4.4.1",
93
93
  "connection-string": "^5.0.0",
94
94
  "cookie-parser": "1.4.7",
95
95
  "cookies": "0.9.1",
96
96
  "cors": "2.8.5",
97
- "dotenv": "17.2.1",
97
+ "dotenv": "17.2.2",
98
98
  "express": "5.1.0",
99
99
  "express-validator": "7.2.1",
100
100
  "fs": "0.0.1-security",
@@ -108,7 +108,7 @@
108
108
  "json-rules-engine": "7.3.1",
109
109
  "jsonwebtoken": "9.0.2",
110
110
  "lodash": "4.17.21",
111
- "mongoose": "8.18.0",
111
+ "mongoose": "8.18.1",
112
112
  "multer": "2.0.2",
113
113
  "mustache": "4.2.0",
114
114
  "mv": "2.1.1",
@@ -138,7 +138,7 @@
138
138
  "jest": "29.7.0",
139
139
  "jest-junit": "16.0.0",
140
140
  "nodemon": "3.1.10",
141
- "npm-check-updates": "18.0.3",
141
+ "npm-check-updates": "18.1.1",
142
142
  "nyc": "17.1.0",
143
143
  "parse-strings-in-object": "1.6.0",
144
144
  "pre-commit": "1.2.2",
@@ -47,6 +47,12 @@ class InboundEventListener {
47
47
 
48
48
  async init () {
49
49
  this.userConfig = await Config.getStoredUserConfig()
50
+
51
+ // Fixes the MyEmitter listener leak issue
52
+ if (this.emitter.listenerCount('newInbound') > 0) {
53
+ this.emitter.removeAllListeners('newInbound')
54
+ }
55
+
50
56
  this.emitter.on('newInbound', (data) => {
51
57
  for (const [, eventListener] of Object.entries(this.eventListeners)) {
52
58
  // Match method, path and condition for each inbound request
@@ -98,59 +104,74 @@ class InboundEventListener {
98
104
 
99
105
  getMessage (clientName, timeout = 5000) {
100
106
  return new Promise((resolve, reject) => {
107
+ let timer = null
108
+
109
+ const cleanup = () => {
110
+ if (timer) {
111
+ clearTimeout(timer)
112
+ timer = null
113
+ }
114
+ if (this.eventListeners[clientName] && this.eventListeners[clientName].eventEmitter) {
115
+ this.eventListeners[clientName].eventEmitter.removeAllListeners('newMessage')
116
+ }
117
+ }
118
+
101
119
  if (!this.eventListeners[clientName]) {
102
120
  resolve(null)
103
- } else {
104
- // Check for the message received already
105
- if (this.eventListeners[clientName].message !== null) {
106
- // Store the message somewhere
107
- const retMessage = this.parseMessage(this.eventListeners[clientName].message)
121
+ return
122
+ }
123
+
124
+ // Check for the message received already
125
+ if (this.eventListeners[clientName].message !== null) {
126
+ // Store the message somewhere
127
+ const retMessage = this.parseMessage(this.eventListeners[clientName].message)
128
+ // Destroy the event listener
129
+ this.destroy(clientName)
130
+ // Return the stored message
131
+ // this.customLog('Returning stored message...')
132
+ if (this.transformer.reverseTransform) {
133
+ this.transformer.reverseTransform(retMessage).then((result) => {
134
+ resolve(result)
135
+ }).catch((err) => {
136
+ this.customLog('Error transforming message: ' + err)
137
+ resolve(retMessage)
138
+ })
139
+ } else {
140
+ resolve(retMessage)
141
+ }
142
+ return
143
+ }
144
+
145
+ // Listen for the new message for some time
146
+ // Set the timer
147
+ timer = setTimeout(() => {
148
+ try {
149
+ this.customLog('Error: Timeout')
108
150
  // Destroy the event listener
109
151
  this.destroy(clientName)
110
- // Return the stored message
111
- // this.customLog('Returning stored message...')
112
- if (this.transformer.reverseTransform) {
113
- this.transformer.reverseTransform(retMessage).then((result) => {
114
- resolve(result)
115
- }).catch((err) => {
116
- this.customLog('Error transforming message: ' + err)
117
- resolve(retMessage)
118
- })
119
- } else {
152
+ resolve(null)
153
+ } catch (err) {
154
+ reject(err)
155
+ }
156
+ }, timeout)
157
+
158
+ // Listen for message
159
+ this.eventListeners[clientName].eventEmitter.once('newMessage', (message) => {
160
+ cleanup()
161
+ this.destroy(clientName)
162
+ const retMessage = this.parseMessage(message)
163
+
164
+ if (this.transformer.reverseTransform) {
165
+ this.transformer.reverseTransform(retMessage).then((result) => {
166
+ resolve(result)
167
+ }).catch((err) => {
168
+ this.customLog('Error transforming message: ' + err)
120
169
  resolve(retMessage)
121
- }
122
- } else {
123
- // Listen for the new message for some time
124
- let timer = null
125
- // Set the timer
126
- timer = setTimeout(() => {
127
- try {
128
- this.customLog('Error: Timeout')
129
- // Destroy the event listener
130
- this.destroy(clientName)
131
- resolve(null)
132
- } catch (err) {
133
- reject(err)
134
- }
135
- }, timeout)
136
- // Listen for message
137
- this.eventListeners[clientName].eventEmitter.once('newMessage', (message) => {
138
- clearTimeout(timer)
139
- this.destroy(clientName)
140
- const retMessage = this.parseMessage(message)
141
- if (this.transformer.reverseTransform) {
142
- this.transformer.reverseTransform(retMessage).then((result) => {
143
- resolve(result)
144
- }).catch((err) => {
145
- this.customLog('Error transforming message: ' + err)
146
- resolve(retMessage)
147
- })
148
- } else {
149
- resolve(retMessage)
150
- }
151
170
  })
171
+ } else {
172
+ resolve(retMessage)
152
173
  }
153
- }
174
+ })
154
175
  })
155
176
  }
156
177
 
@@ -53,6 +53,8 @@ class WebSocketClientManager {
53
53
  }
54
54
 
55
55
  connect (url, clientName, timeout = 15000) {
56
+ this.customLog(`Connecting to WebSocket URL: ${url} with client name: ${clientName} and timeout: ${timeout}ms`)
57
+
56
58
  const tlsOptions = { rejectUnauthorized: false }
57
59
  const urlObject = new URL(url)
58
60
  if (this.userConfig.CLIENT_MUTUAL_TLS_ENABLED) {
@@ -107,41 +109,48 @@ class WebSocketClientManager {
107
109
 
108
110
  getMessage (clientName, timeout = 5000) {
109
111
  return new Promise((resolve, reject) => {
112
+ let timer = null
113
+
114
+ const cleanup = () => {
115
+ if (timer) {
116
+ clearTimeout(timer)
117
+ timer = null
118
+ }
119
+ if (this.ws[clientName] && this.ws[clientName].eventEmitter) {
120
+ this.ws[clientName].eventEmitter.removeAllListeners('newMessage')
121
+ }
122
+ }
123
+
110
124
  if (!this.ws[clientName]) {
111
125
  resolve(null)
112
- } else {
113
- // Check for the message received already
114
- if (this.ws[clientName].message !== null) {
115
- // Store the message somewhere
116
- const retMessage = this.parseMessage(this.ws[clientName].message)
117
- // Disconnect the websocket connection
126
+ return
127
+ }
128
+
129
+ // Check for already received message
130
+ if (this.ws[clientName].message !== null) {
131
+ const retMessage = this.parseMessage(this.ws[clientName].message)
132
+ this.disconnect(clientName)
133
+ resolve(retMessage)
134
+ return
135
+ }
136
+
137
+ // Set timeout
138
+ timer = setTimeout(() => {
139
+ try {
140
+ cleanup()
118
141
  this.disconnect(clientName)
119
- // Return the stored message
120
- // this.customLog('Returning stored message...')
121
- resolve(retMessage)
122
- } else {
123
- // Listen for the new message for some time
124
- let timer = null
125
- // Set the timer
126
- timer = setTimeout(() => {
127
- try {
128
- this.ws[clientName].eventEmitter.removeAllListeners('newMessage')
129
- // Disconnect the websocket connection
130
- this.disconnect(clientName)
131
- resolve(null)
132
- } catch (err) {
133
- reject(err)
134
- }
135
- }, timeout)
136
- // Listen for message
137
- this.ws[clientName].eventEmitter.once('newMessage', (message) => {
138
- clearTimeout(timer)
139
- this.ws[clientName].eventEmitter.removeAllListeners('newMessage')
140
- this.disconnect(clientName)
141
- resolve(this.parseMessage(message))
142
- })
142
+ resolve(null)
143
+ } catch (err) {
144
+ reject(err)
143
145
  }
144
- }
146
+ }, timeout)
147
+
148
+ // Listen for message
149
+ this.ws[clientName].eventEmitter.once('newMessage', (message) => {
150
+ cleanup()
151
+ this.disconnect(clientName)
152
+ resolve(this.parseMessage(message))
153
+ })
145
154
  })
146
155
  }
147
156
 
@@ -155,7 +164,10 @@ class WebSocketClientManager {
155
164
 
156
165
  disconnect (clientName) {
157
166
  if (this.ws[clientName]) {
158
- this.ws[clientName].client.close()
167
+ if (this.ws[clientName].client) {
168
+ this.ws[clientName].client.removeAllListeners()
169
+ this.ws[clientName].client.close()
170
+ }
159
171
  if (this.ws[clientName].eventEmitter) {
160
172
  this.ws[clientName].eventEmitter.removeAllListeners()
161
173
  }
@@ -170,7 +182,10 @@ class WebSocketClientManager {
170
182
 
171
183
  disconnectAll () {
172
184
  for (const clientName in this.ws) {
173
- this.ws[clientName].client.close()
185
+ if (this.ws[clientName].client) {
186
+ this.ws[clientName].client.removeAllListeners()
187
+ this.ws[clientName].client.close()
188
+ }
174
189
  if (this.ws[clientName].eventEmitter) {
175
190
  this.ws[clientName].eventEmitter.removeAllListeners()
176
191
  }
@@ -183,13 +198,6 @@ class WebSocketClientManager {
183
198
  }
184
199
  return true
185
200
  }
186
-
187
- clearAllTimers () {
188
- Object.keys(this.timers).forEach((clientName) => {
189
- clearTimeout(this.timers[clientName])
190
- delete this.timers[clientName]
191
- })
192
- }
193
201
  }
194
202
 
195
203
  module.exports = {