@mojaloop/central-services-shared 18.36.1 → 18.37.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/.circleci/config.yml +1 -1
- package/.grype.yaml +1 -3
- package/.nvmrc +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +15 -0
- package/audit-ci.jsonc +0 -5
- package/package.json +17 -14
- package/src/config.js +24 -0
- package/src/util/request.js +16 -3
- package/test/unit/util/request.test.js +58 -0
package/.circleci/config.yml
CHANGED
package/.grype.yaml
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
scan-type: source
|
|
2
2
|
disabled: false
|
|
3
3
|
ignore:
|
|
4
|
-
|
|
5
|
-
include-aliases: true
|
|
6
|
-
reason: "[05-16-2026][Critical][sanitize-html 2.12.1] No available fix. Apostrophe has default XSS via `xmp` raw-text passthrough in `sanitize-html"
|
|
4
|
+
|
|
7
5
|
output:
|
|
8
6
|
- table
|
|
9
7
|
- json
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
24.15.0
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [18.37.0](https://github.com/mojaloop/central-services-shared/compare/v18.36.1...v18.37.0) (2026-06-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* make maxsocket configurable so it doesnt use infinite number of sockets ([#4482](https://github.com/mojaloop/central-services-shared/issues/4482)) ([#522](https://github.com/mojaloop/central-services-shared/issues/522)) ([46e9e16](https://github.com/mojaloop/central-services-shared/commit/46e9e16cef638824bd17b8adfe95da27a12fc3cf))
|
|
11
|
+
|
|
5
12
|
### [18.36.1](https://github.com/mojaloop/central-services-shared/compare/v18.36.0...v18.36.1) (2026-05-18)
|
|
6
13
|
|
|
7
14
|
|
package/README.md
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
Shared code for central services
|
|
9
9
|
|
|
10
|
+
## Configuration
|
|
11
|
+
|
|
12
|
+
### Outbound HTTP agent
|
|
13
|
+
|
|
14
|
+
The shared `sendRequest` helper (`src/util/request.js`) configures the default outbound `http.Agent` with a **bounded** connection pool to prevent outbound connection churn under sustained high load. The pool is configurable via the following environment variables:
|
|
15
|
+
|
|
16
|
+
| Variable | Default | Description |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `HTTP_AGENT_KEEP_ALIVE` | `true` | Whether to keep sockets alive for reuse. Set to `false` to disable. |
|
|
19
|
+
| `HTTP_AGENT_MAX_SOCKETS` | `512` | Maximum number of sockets per host. Bounds the pool so bursty concurrency queues requests instead of opening unbounded sockets (node's default is `Infinity`). |
|
|
20
|
+
| `HTTP_AGENT_MAX_FREE_SOCKETS` | `512` | Maximum number of idle (free) sockets kept warm per host (node's default is `256`). |
|
|
21
|
+
| `HTTP_AGENT_KEEP_ALIVE_MSECS` | `30000` | Initial delay (ms) for TCP Keep-Alive packets on kept-alive sockets. |
|
|
22
|
+
|
|
23
|
+
Numeric values are parsed with `Number(...)` and fall back to the defaults above when unset or non-numeric.
|
|
24
|
+
|
|
10
25
|
## CI/CD
|
|
11
26
|
|
|
12
27
|
This repository uses the [mojaloop/build](https://github.com/mojaloop/ci-config-orb-build) CircleCI orb for standardized CI/CD workflows, including automated Grype vulnerability scanning for source code security.
|
package/audit-ci.jsonc
CHANGED
|
@@ -4,10 +4,5 @@
|
|
|
4
4
|
// Only use one of ["low": true, "moderate": true, "high": true, "critical": true]
|
|
5
5
|
"moderate": true,
|
|
6
6
|
"allowlist": [ // NOTE: Please add as much information as possible to any items added to the allowList
|
|
7
|
-
/**
|
|
8
|
-
[05-16-2026] - No fix available for this vulnerability
|
|
9
|
-
Apostrophe has default XSS via `xmp` raw-text passthrough in `sanitize-html`
|
|
10
|
-
**/
|
|
11
|
-
"GHSA-rpr9-rxv7-x643"
|
|
12
7
|
]
|
|
13
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojaloop/central-services-shared",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.37.0",
|
|
4
4
|
"description": "Shared code for mojaloop central services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ModusBox",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"lint",
|
|
33
33
|
"test",
|
|
34
34
|
"dep:check",
|
|
35
|
-
"audit:check"
|
|
35
|
+
"audit:check",
|
|
36
|
+
"license:check"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
39
|
"pretest": "npm run lint",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"audit:check": "npx audit-ci --config ./audit-ci.jsonc",
|
|
62
63
|
"dep:check": "npx ncu -e 2",
|
|
63
64
|
"dep:update": "npx ncu -u",
|
|
65
|
+
"license:check": "npx @mojaloop/license-scanner-tool .",
|
|
64
66
|
"release": "npx standard-version --no-verify --releaseCommitMessageFormat 'chore(release): {{currentTag}} [skip ci]'",
|
|
65
67
|
"snapshot": "npx standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
|
|
66
68
|
},
|
|
@@ -73,14 +75,14 @@
|
|
|
73
75
|
"@opentelemetry/api": "1.9.1",
|
|
74
76
|
"async-exit-hook": "2.0.1",
|
|
75
77
|
"async-retry": "1.3.3",
|
|
76
|
-
"axios": "1.
|
|
78
|
+
"axios": "1.17.0",
|
|
77
79
|
"clone": "2.1.2",
|
|
78
80
|
"convict": "6.2.5",
|
|
79
81
|
"dotenv": "17.4.2",
|
|
80
82
|
"env-var": "7.5.0",
|
|
81
83
|
"event-stream": "4.0.1",
|
|
82
84
|
"fast-safe-stringify": "2.1.1",
|
|
83
|
-
"immutable": "5.1.
|
|
85
|
+
"immutable": "5.1.6",
|
|
84
86
|
"ioredis": "5.6.1",
|
|
85
87
|
"joi": "18.2.1",
|
|
86
88
|
"lodash": "4.18.1",
|
|
@@ -100,7 +102,8 @@
|
|
|
100
102
|
"@mojaloop/central-services-logger": "11.10.4",
|
|
101
103
|
"@mojaloop/central-services-metrics": "12.8.5",
|
|
102
104
|
"@mojaloop/event-sdk": "14.8.4",
|
|
103
|
-
"@opentelemetry/auto-instrumentations-node": "^0.
|
|
105
|
+
"@opentelemetry/auto-instrumentations-node": "^0.77.0",
|
|
106
|
+
"@mojaloop/license-scanner-tool": "^1.0.0",
|
|
104
107
|
"@types/hapi__joi": "17.1.15",
|
|
105
108
|
"ajv": "8.20.0",
|
|
106
109
|
"ajv-formats": "^3.0.1",
|
|
@@ -108,7 +111,7 @@
|
|
|
108
111
|
"audit-ci": "7.1.0",
|
|
109
112
|
"base64url": "3.0.1",
|
|
110
113
|
"chance": "1.1.13",
|
|
111
|
-
"npm-check-updates": "22.2.
|
|
114
|
+
"npm-check-updates": "22.2.3",
|
|
112
115
|
"nyc": "18.0.0",
|
|
113
116
|
"portfinder": "1.0.38",
|
|
114
117
|
"pre-commit": "2.0.0",
|
|
@@ -120,23 +123,23 @@
|
|
|
120
123
|
"standard-version": "9.5.0",
|
|
121
124
|
"tap-spec": "5.0.0",
|
|
122
125
|
"tap-xunit": "2.4.1",
|
|
123
|
-
"tape": "5.
|
|
126
|
+
"tape": "5.10.1",
|
|
124
127
|
"tapes": "4.1.0"
|
|
125
128
|
},
|
|
126
129
|
"overrides": {
|
|
127
|
-
"
|
|
128
|
-
"
|
|
130
|
+
"@grpc/grpc-js": "1.14.4",
|
|
131
|
+
"axios": "1.17.0",
|
|
132
|
+
"qs": "6.15.2",
|
|
129
133
|
"brace-expansion": "1.1.13",
|
|
130
134
|
"form-data": "4.0.5",
|
|
131
135
|
"convict": "6.2.5",
|
|
132
136
|
"nanoid": "3.3.11",
|
|
133
|
-
"
|
|
137
|
+
"protobufjs": "8.2.0",
|
|
134
138
|
"shins": {
|
|
135
139
|
"ejs": "3.1.10",
|
|
136
|
-
"sanitize-html": "2.
|
|
140
|
+
"sanitize-html": "2.17.5",
|
|
137
141
|
"jsonpointer": "5.0.0",
|
|
138
|
-
"markdown-it": "12.3.2"
|
|
139
|
-
"postcss": "8.4.31"
|
|
142
|
+
"markdown-it": "12.3.2"
|
|
140
143
|
},
|
|
141
144
|
"widdershins": {
|
|
142
145
|
"markdown-it": "12.3.2",
|
|
@@ -152,7 +155,7 @@
|
|
|
152
155
|
"lodash": "4.18.1",
|
|
153
156
|
"lodash-es": "4.18.1",
|
|
154
157
|
"undici": "6.25.0",
|
|
155
|
-
"@hapi/content": "6.0.
|
|
158
|
+
"@hapi/content": "6.0.2",
|
|
156
159
|
"replace": {
|
|
157
160
|
"minimatch": "3.1.4"
|
|
158
161
|
},
|
package/src/config.js
CHANGED
|
@@ -20,6 +20,30 @@ const config = convict({
|
|
|
20
20
|
format: Number,
|
|
21
21
|
default: 20000,
|
|
22
22
|
env: 'SHARED_HTTP_REQUEST_TIMEOUT_MS'
|
|
23
|
+
},
|
|
24
|
+
httpAgentKeepAlive: {
|
|
25
|
+
doc: 'Enable HTTP keep-alive for the shared outbound http agent.',
|
|
26
|
+
format: Boolean,
|
|
27
|
+
default: true,
|
|
28
|
+
env: 'HTTP_AGENT_KEEP_ALIVE'
|
|
29
|
+
},
|
|
30
|
+
httpAgentMaxSockets: {
|
|
31
|
+
doc: 'Maximum number of sockets per host for the shared outbound http agent.',
|
|
32
|
+
format: Number,
|
|
33
|
+
default: 512,
|
|
34
|
+
env: 'HTTP_AGENT_MAX_SOCKETS'
|
|
35
|
+
},
|
|
36
|
+
httpAgentMaxFreeSockets: {
|
|
37
|
+
doc: 'Maximum number of idle sockets kept open for the shared outbound http agent.',
|
|
38
|
+
format: Number,
|
|
39
|
+
default: 512,
|
|
40
|
+
env: 'HTTP_AGENT_MAX_FREE_SOCKETS'
|
|
41
|
+
},
|
|
42
|
+
httpAgentKeepAliveMsecs: {
|
|
43
|
+
doc: 'Keep-alive interval in milliseconds for the shared outbound http agent.',
|
|
44
|
+
format: Number,
|
|
45
|
+
default: 30000,
|
|
46
|
+
env: 'HTTP_AGENT_KEEP_ALIVE_MSECS'
|
|
23
47
|
}
|
|
24
48
|
})
|
|
25
49
|
|
package/src/util/request.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
'use strict'
|
|
31
31
|
|
|
32
32
|
const http = require('node:http')
|
|
33
|
+
const https = require('node:https')
|
|
33
34
|
const request = require('axios')
|
|
34
35
|
const stringify = require('fast-safe-stringify')
|
|
35
36
|
const EventSdk = require('@mojaloop/event-sdk')
|
|
@@ -47,12 +48,24 @@ const MISSING_FUNCTION_PARAMETERS = 'Missing parameters for function'
|
|
|
47
48
|
// By default it would insert `"Accept":"application/json, text/plain, */*"`.
|
|
48
49
|
delete request.defaults.headers.common.Accept
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// http.Agent defaults to maxSockets: Infinity / maxFreeSockets: 256,
|
|
52
|
+
// which churns sockets open/close under sustained load).
|
|
53
|
+
const keepAlive = config.get('httpAgentKeepAlive')
|
|
54
|
+
const maxSockets = config.get('httpAgentMaxSockets')
|
|
55
|
+
const maxFreeSockets = config.get('httpAgentMaxFreeSockets')
|
|
56
|
+
const keepAliveMsecs = config.get('httpAgentKeepAliveMsecs')
|
|
57
|
+
|
|
58
|
+
const agentOpts = { keepAlive, maxSockets, maxFreeSockets, keepAliveMsecs }
|
|
51
59
|
logger.verbose('http keepAlive:', { keepAlive })
|
|
60
|
+
logger.verbose('http agent options:', agentOpts)
|
|
52
61
|
|
|
53
|
-
// Enable keepalive for http
|
|
54
|
-
|
|
62
|
+
// Enable keepalive with a bounded connection pool for both http and https outbound
|
|
63
|
+
// requests, so https:// callback URLs get the same socket pooling as http:// ones
|
|
64
|
+
// (axios otherwise falls back to a default https.Agent with maxSockets: Infinity).
|
|
65
|
+
request.defaults.httpAgent = new http.Agent(agentOpts)
|
|
55
66
|
request.defaults.httpAgent.toJSON = () => ({})
|
|
67
|
+
request.defaults.httpsAgent = new https.Agent(agentOpts)
|
|
68
|
+
request.defaults.httpsAgent.toJSON = () => ({})
|
|
56
69
|
|
|
57
70
|
/**
|
|
58
71
|
* @function sendRequest
|
|
@@ -626,6 +626,64 @@ Test('ParticipantEndpoint Model Test', modelTest => {
|
|
|
626
626
|
test.end()
|
|
627
627
|
})
|
|
628
628
|
|
|
629
|
+
const configStub = (overrides = {}) => ({
|
|
630
|
+
'@noCallThru': true,
|
|
631
|
+
get: (key) => ({
|
|
632
|
+
httpAgentKeepAlive: true,
|
|
633
|
+
httpAgentMaxSockets: 512,
|
|
634
|
+
httpAgentMaxFreeSockets: 512,
|
|
635
|
+
httpAgentKeepAliveMsecs: 30000,
|
|
636
|
+
httpRequestTimeoutMs: 20000,
|
|
637
|
+
...overrides
|
|
638
|
+
})[key]
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
getEndpointTest.test('create bounded http and https agents from the config defaults', async (test) => {
|
|
642
|
+
request = sandbox.stub().returns(Helper.getEndPointsResponse)
|
|
643
|
+
Model = proxyquire('../../../src/util/request', { axios: request, '../config': configStub() })
|
|
644
|
+
|
|
645
|
+
for (const [name, agent] of [['http', request.defaults.httpAgent], ['https', request.defaults.httpsAgent]]) {
|
|
646
|
+
test.equal(agent.maxSockets, 512, `${name} maxSockets defaults to 512 (not Infinity)`)
|
|
647
|
+
test.equal(agent.maxFreeSockets, 512, `${name} maxFreeSockets defaults to 512`)
|
|
648
|
+
test.equal(agent.keepAliveMsecs, 30000, `${name} keepAliveMsecs defaults to 30000`)
|
|
649
|
+
test.ok(agent.keepAlive, `${name} keepAlive is enabled by default`)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
test.end()
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
getEndpointTest.test('honor config values for the http and https agent socket pools', async (test) => {
|
|
656
|
+
request = sandbox.stub().returns(Helper.getEndPointsResponse)
|
|
657
|
+
Model = proxyquire('../../../src/util/request', {
|
|
658
|
+
axios: request,
|
|
659
|
+
'../config': configStub({
|
|
660
|
+
httpAgentMaxSockets: 1000,
|
|
661
|
+
httpAgentMaxFreeSockets: 256,
|
|
662
|
+
httpAgentKeepAliveMsecs: 60000,
|
|
663
|
+
httpAgentKeepAlive: false
|
|
664
|
+
})
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
for (const [name, agent] of [['http', request.defaults.httpAgent], ['https', request.defaults.httpsAgent]]) {
|
|
668
|
+
test.equal(agent.maxSockets, 1000, `${name} maxSockets honors httpAgentMaxSockets`)
|
|
669
|
+
test.equal(agent.maxFreeSockets, 256, `${name} maxFreeSockets honors httpAgentMaxFreeSockets`)
|
|
670
|
+
test.equal(agent.keepAliveMsecs, 60000, `${name} keepAliveMsecs honors httpAgentKeepAliveMsecs`)
|
|
671
|
+
test.notOk(agent.keepAlive, `${name} keepAlive honors httpAgentKeepAlive`)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
test.end()
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
getEndpointTest.test('redact both the http and https agents from serialized request options', async (test) => {
|
|
678
|
+
request = sandbox.stub().returns(Helper.getEndPointsResponse)
|
|
679
|
+
Model = proxyquire('../../../src/util/request', { axios: request, '../config': configStub() })
|
|
680
|
+
|
|
681
|
+
test.same(request.defaults.httpAgent.toJSON(), {}, 'httpAgent.toJSON() is redacted to {}')
|
|
682
|
+
test.same(request.defaults.httpsAgent.toJSON(), {}, 'httpsAgent.toJSON() is redacted to {}')
|
|
683
|
+
|
|
684
|
+
test.end()
|
|
685
|
+
})
|
|
686
|
+
|
|
629
687
|
getEndpointTest.end()
|
|
630
688
|
})
|
|
631
689
|
|