@mojaloop/sdk-scheme-adapter 12.2.2 → 13.0.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.
Files changed (76) hide show
  1. package/.env.example +3 -0
  2. package/CHANGELOG.md +26 -0
  3. package/audit-resolve.json +71 -1
  4. package/docker/ml-testing-toolkit/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +2 -2
  5. package/docker/ml-testing-toolkit/spec_files/rules_callback/default.json +7 -7
  6. package/docker/ml-testing-toolkit/spec_files/rules_response/default.json +16 -16
  7. package/docker/ml-testing-toolkit/spec_files/rules_response/default_pisp_rules.json +5 -5
  8. package/docker/ml-testing-toolkit/spec_files/rules_validation/default.json +10 -10
  9. package/package.json +4 -1
  10. package/src/ControlAgent/index.js +2 -3
  11. package/src/ControlServer/index.js +2 -2
  12. package/src/InboundServer/handlers.js +114 -52
  13. package/src/InboundServer/index.js +7 -7
  14. package/src/InboundServer/middlewares.js +2 -2
  15. package/src/OutboundServer/api.yaml +54 -3
  16. package/src/OutboundServer/api_interfaces/openapi.d.ts +24 -3
  17. package/src/OutboundServer/api_template/components/schemas/accountsResponse.yaml +9 -0
  18. package/src/OutboundServer/api_template/components/schemas/transferRequest.yaml +3 -0
  19. package/src/OutboundServer/api_template/components/schemas/transferResponse.yaml +28 -2
  20. package/src/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +8 -1
  21. package/src/OutboundServer/handlers.js +4 -1
  22. package/src/OutboundServer/index.js +10 -11
  23. package/src/config.js +29 -12
  24. package/src/index.js +198 -10
  25. package/src/lib/cache.js +110 -52
  26. package/src/lib/metrics.js +148 -0
  27. package/src/lib/model/AccountsModel.js +17 -12
  28. package/src/lib/model/Async2SyncModel.js +4 -1
  29. package/src/lib/model/InboundTransfersModel.js +170 -25
  30. package/src/lib/model/OutboundBulkQuotesModel.js +4 -1
  31. package/src/lib/model/OutboundBulkTransfersModel.js +4 -1
  32. package/src/lib/model/OutboundRequestToPayModel.js +9 -7
  33. package/src/lib/model/OutboundRequestToPayTransferModel.js +6 -3
  34. package/src/lib/model/OutboundTransfersModel.js +318 -53
  35. package/src/lib/model/PartiesModel.js +1 -1
  36. package/src/lib/model/ProxyModel/index.js +4 -2
  37. package/src/lib/model/common/BackendError.js +28 -4
  38. package/src/lib/model/common/index.js +2 -1
  39. package/src/lib/validate.js +2 -2
  40. package/test/__mocks__/@mojaloop/sdk-standard-components.js +3 -2
  41. package/test/__mocks__/redis.js +4 -0
  42. package/test/config/integration.env +5 -0
  43. package/test/integration/lib/Outbound/parties.test.js +1 -1
  44. package/test/unit/ControlServer/index.js +3 -3
  45. package/test/unit/InboundServer.test.js +10 -10
  46. package/test/unit/TestServer.test.js +11 -13
  47. package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +11 -3
  48. package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +14 -0
  49. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +13 -0
  50. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +18 -0
  51. package/test/unit/api/accounts/utils.js +15 -1
  52. package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +18 -15
  53. package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +1 -0
  54. package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +9 -0
  55. package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +1 -0
  56. package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +74 -47
  57. package/test/unit/api/transfers/utils.js +85 -4
  58. package/test/unit/api/utils.js +4 -1
  59. package/test/unit/config.test.js +2 -2
  60. package/test/unit/data/commonHttpHeaders.json +1 -0
  61. package/test/unit/data/defaultConfig.json +23 -7
  62. package/test/unit/inboundApi/handlers.test.js +45 -14
  63. package/test/unit/index.test.js +95 -4
  64. package/test/unit/lib/model/AccountsModel.test.js +9 -6
  65. package/test/unit/lib/model/InboundTransfersModel.test.js +210 -30
  66. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +1 -1
  67. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -3
  68. package/test/unit/lib/model/OutboundTransfersModel.test.js +863 -158
  69. package/test/unit/lib/model/data/defaultConfig.json +25 -10
  70. package/test/unit/lib/model/data/mockArguments.json +97 -40
  71. package/test/unit/lib/model/data/payeeParty.json +13 -11
  72. package/test/unit/lib/model/data/quoteResponse.json +36 -25
  73. package/test/unit/lib/model/data/transferFulfil.json +5 -3
  74. package/src/lib/api/index.js +0 -12
  75. package/src/lib/randomphrase/index.js +0 -21
  76. package/src/lib/randomphrase/words.json +0 -3397
@@ -28,18 +28,19 @@ const middlewares = require('./middlewares');
28
28
  const endpointRegex = /\/.*/g;
29
29
 
30
30
  class OutboundApi extends EventEmitter {
31
- constructor(conf, logger, cache, validator) {
31
+ constructor(conf, logger, cache, validator, metricsClient) {
32
32
  super({ captureExceptions: true });
33
33
  this._logger = logger;
34
34
  this._api = new Koa();
35
35
  this._conf = conf;
36
36
  this._cache = cache;
37
+ this._metricsClient = metricsClient;
37
38
 
38
39
  this._wso2 = {
39
40
  auth: new WSO2Auth({
40
41
  ...this._conf.wso2.auth,
41
42
  logger: this._logger,
42
- tlsCreds: this._conf.mutualTLS.outboundRequests.enabled && this._conf.mutualTLS.outboundRequests.creds,
43
+ tlsCreds: this._conf.outbound.tls.mutualTLS.enabled && this._conf.outbound.tls.creds,
43
44
  }),
44
45
  retryWso2AuthFailureTimes: conf.wso2.requestAuthFailureRetryTimes,
45
46
  };
@@ -54,7 +55,7 @@ class OutboundApi extends EventEmitter {
54
55
  this._api.use(middlewares.createErrorHandler(this._logger));
55
56
  this._api.use(middlewares.createRequestIdGenerator());
56
57
  this._api.use(koaBody()); // outbound always expects application/json
57
- this._api.use(middlewares.applyState({ cache, wso2: this._wso2, conf }));
58
+ this._api.use(middlewares.applyState({ cache, wso2: this._wso2, conf, metricsClient }));
58
59
  this._api.use(middlewares.createLogger(this._logger));
59
60
 
60
61
  //Note that we strip off any path on peerEndpoint config after the origin.
@@ -67,7 +68,7 @@ class OutboundApi extends EventEmitter {
67
68
  proxyConfig: conf.proxyConfig,
68
69
  logger: this._logger,
69
70
  wso2Auth: this._wso2.auth,
70
- tls: conf.mutualTLS.outboundRequests,
71
+ tls: conf.outbound.tls,
71
72
  }));
72
73
  }
73
74
 
@@ -91,7 +92,7 @@ class OutboundApi extends EventEmitter {
91
92
  }
92
93
 
93
94
  class OutboundServer extends EventEmitter {
94
- constructor(conf, logger, cache) {
95
+ constructor(conf, logger, cache, metricsClient) {
95
96
  super({ captureExceptions: true });
96
97
  this._validator = new Validate();
97
98
  this._conf = conf;
@@ -101,7 +102,8 @@ class OutboundServer extends EventEmitter {
101
102
  conf,
102
103
  this._logger.push({ component: 'api' }),
103
104
  cache,
104
- this._validator
105
+ this._validator,
106
+ metricsClient
105
107
  );
106
108
  this._api.on('error', (...args) => {
107
109
  this.emit('error', ...args);
@@ -111,14 +113,11 @@ class OutboundServer extends EventEmitter {
111
113
 
112
114
  async start() {
113
115
  await this._api.start();
114
-
115
116
  const specPath = path.join(__dirname, 'api.yaml');
116
117
  const apiSpecs = yaml.load(fs.readFileSync(specPath));
117
118
  await this._validator.initialise(apiSpecs);
118
-
119
- await new Promise((resolve) => this._server.listen(this._conf.outboundServerPort, resolve));
120
-
121
- this._logger.log(`Serving outbound API on port ${this._conf.outboundServerPort}`);
119
+ await new Promise((resolve) => this._server.listen(this._conf.outbound.port, resolve));
120
+ this._logger.log(`Serving outbound API on port ${this._conf.outbound.port}`);
122
121
  }
123
122
 
124
123
  async stop() {
package/src/config.js CHANGED
@@ -58,17 +58,29 @@ const env = from(process.env, {
58
58
 
59
59
  module.exports = {
60
60
  __parseResourceVersion: parseResourceVersions,
61
- mutualTLS: {
62
- inboundRequests: {
63
- enabled: env.get('INBOUND_MUTUAL_TLS_ENABLED').default('false').asBool(),
61
+ control: {
62
+ mgmtAPIWsUrl: env.get('MGMT_API_WS_URL').default('127.0.0.1').asString(),
63
+ mgmtAPIWsPort: env.get('MGMT_API_WS_PORT').default('4005').asPortNumber()
64
+ },
65
+ inbound: {
66
+ port: env.get('INBOUND_LISTEN_PORT').default('4000').asPortNumber(),
67
+ tls: {
68
+ mutualTLS: {
69
+ enabled: env.get('INBOUND_MUTUAL_TLS_ENABLED').default('false').asBool(),
70
+ },
64
71
  creds: {
65
72
  ca: env.get('IN_CA_CERT_PATH').asFileListContent(),
66
73
  cert: env.get('IN_SERVER_CERT_PATH').asFileContent(),
67
74
  key: env.get('IN_SERVER_KEY_PATH').asFileContent(),
68
75
  },
69
76
  },
70
- outboundRequests: {
71
- enabled: env.get('OUTBOUND_MUTUAL_TLS_ENABLED').default('false').asBool(),
77
+ },
78
+ outbound: {
79
+ port: env.get('OUTBOUND_LISTEN_PORT').default('4001').asPortNumber(),
80
+ tls: {
81
+ mutualTLS: {
82
+ enabled: env.get('OUTBOUND_MUTUAL_TLS_ENABLED').default('false').asBool(),
83
+ },
72
84
  creds: {
73
85
  ca: env.get('OUT_CA_CERT_PATH').asFileListContent(),
74
86
  cert: env.get('OUT_CLIENT_CERT_PATH').asFileContent(),
@@ -76,9 +88,9 @@ module.exports = {
76
88
  },
77
89
  },
78
90
  },
79
- inboundServerPort: env.get('INBOUND_LISTEN_PORT').default('4000').asPortNumber(),
80
- outboundServerPort: env.get('OUTBOUND_LISTEN_PORT').default('4001').asPortNumber(),
81
- testServerPort: env.get('TEST_LISTEN_PORT').default('4002').asPortNumber(),
91
+ test: {
92
+ port: env.get('TEST_LISTEN_PORT').default('4002').asPortNumber(),
93
+ },
82
94
  peerEndpoint: env.get('PEER_ENDPOINT').required().asString(),
83
95
  alsEndpoint: env.get('ALS_ENDPOINT').asString(),
84
96
  quotesEndpoint: env.get('QUOTES_ENDPOINT').asString(),
@@ -93,6 +105,9 @@ module.exports = {
93
105
  checkIlp: env.get('CHECK_ILP').default('true').asBool(),
94
106
  expirySeconds: env.get('EXPIRY_SECONDS').default('60').asIntPositive(),
95
107
 
108
+ multiplePartiesResponse: env.get('MULTIPLE_PARTIES_RESPONSE').default('false').asBool(),
109
+ multiplePartiesResponseSeconds: env.get('MULTIPLE_PARTIES_RESPONSE_SECONDS').default('30').asIntPositive(),
110
+
96
111
  autoAcceptQuotes: env.get('AUTO_ACCEPT_QUOTES').default('true').asBool(),
97
112
  autoAcceptParty: env.get('AUTO_ACCEPT_PARTY').default('true').asBool(),
98
113
  autoAcceptR2PBusinessQuotes: env.get('AUTO_ACCEPT_R2P_BUSINESS_QUOTES').default('false').asBool(),
@@ -153,17 +168,19 @@ module.exports = {
153
168
 
154
169
  proxyConfig: env.get('PROXY_CONFIG_PATH').asYamlConfig(),
155
170
  reserveNotification: env.get('RESERVE_NOTIFICATION').default('false').asBool(),
171
+ sendFinalNotificationIfRequested: env.get('SEND_FINAL_NOTIFICATION_IF_REQUESTED').default('false').asBool(),
172
+
156
173
  // resourceVersions config should be string in format: "resourceOneName=1.0,resourceTwoName=1.1"
157
174
  resourceVersions: env.get('RESOURCE_VERSIONS').default('').asResourceVersions(),
158
175
 
176
+ metrics: {
177
+ port: env.get('METRICS_SERVER_LISTEN_PORT').default('4004').asPortNumber()
178
+ },
179
+
159
180
  // in 3PPI DFSP's generate their own `transferId` which is associated with
160
181
  // a transactionRequestId. this option decodes the ilp packet for
161
182
  // the `transactionId` to retrieve the quote from cache
162
183
  allowDifferentTransferTransactionId: env.get('ALLOW_DIFFERENT_TRANSFER_TRANSACTION_ID').default('false').asBool(),
163
184
 
164
185
  pm4mlEnabled: env.get('PM4ML_ENABLED').default('false').asBool(),
165
- control: {
166
- mgmtAPIWsUrl: env.get('MGMT_API_WS_URL').default('127.0.0.1').asString(),
167
- mgmtAPIWsPort: env.get('MGMT_API_WS_PORT').default('4005').asPortNumber()
168
- },
169
186
  };
package/src/index.js CHANGED
@@ -10,14 +10,18 @@
10
10
 
11
11
  'use strict';
12
12
 
13
+ const assert = require('assert/strict');
13
14
  const { hostname } = require('os');
14
15
  const config = require('./config');
15
16
  const EventEmitter = require('events');
17
+ const _ = require('lodash');
16
18
 
17
19
  const InboundServer = require('./InboundServer');
18
20
  const OutboundServer = require('./OutboundServer');
19
21
  const OAuthTestServer = require('./OAuthTestServer');
20
22
  const TestServer = require('./TestServer');
23
+ const { MetricsServer, MetricsClient } = require('./lib/metrics');
24
+ const ControlAgent = require('./ControlAgent');
21
25
 
22
26
  // import things we want to expose e.g. for unit tests and users who dont want to use the entire
23
27
  // scheme adapter as a service
@@ -25,10 +29,20 @@ const InboundServerMiddleware = require('./InboundServer/middlewares.js');
25
29
  const OutboundServerMiddleware = require('./OutboundServer/middlewares.js');
26
30
  const Router = require('./lib/router');
27
31
  const Validate = require('./lib/validate');
28
- const RandomPhrase = require('./lib/randomphrase');
29
32
  const Cache = require('./lib/cache');
33
+ const check = require('./lib/check');
30
34
  const { Logger } = require('@mojaloop/sdk-standard-components');
31
35
 
36
+ const LOG_ID = {
37
+ INBOUND: { app: 'mojaloop-connector-inbound-api' },
38
+ OUTBOUND: { app: 'mojaloop-connector-outbound-api' },
39
+ TEST: { app: 'mojaloop-connector-test-api' },
40
+ OAUTHTEST: { app: 'mojaloop-connector-oauth-test-server' },
41
+ CONTROL: { app: 'mojaloop-connector-control-client' },
42
+ METRICS: { app: 'mojaloop-connector-metrics' },
43
+ CACHE: { component: 'cache' },
44
+ };
45
+
32
46
  /**
33
47
  * Class that creates and manages http servers that expose the scheme adapter APIs.
34
48
  */
@@ -39,14 +53,22 @@ class Server extends EventEmitter {
39
53
  this.logger = logger;
40
54
  this.cache = new Cache({
41
55
  ...conf.cacheConfig,
42
- logger: this.logger.push({ component: 'cache' }),
56
+ logger: this.logger.push(LOG_ID.CACHE),
43
57
  enableTestFeatures: conf.enableTestFeatures,
44
58
  });
45
59
 
60
+ this.metricsClient = new MetricsClient();
61
+
62
+ this.metricsServer = new MetricsServer({
63
+ port: this.conf.metrics.port,
64
+ logger: this.logger.push(LOG_ID.METRICS)
65
+ });
66
+
46
67
  this.inboundServer = new InboundServer(
47
68
  this.conf,
48
- this.logger.push({ app: 'mojaloop-sdk-inbound-api' }),
49
- this.cache
69
+ this.logger.push(LOG_ID.INBOUND),
70
+ this.cache,
71
+ this.metricsClient
50
72
  );
51
73
  this.inboundServer.on('error', (...args) => {
52
74
  this.logger.push({ args }).log('Unhandled error in Inbound Server');
@@ -55,8 +77,9 @@ class Server extends EventEmitter {
55
77
 
56
78
  this.outboundServer = new OutboundServer(
57
79
  this.conf,
58
- this.logger.push({ app: 'mojaloop-sdk-outbound-api' }),
59
- this.cache
80
+ this.logger.push(LOG_ID.OUTBOUND),
81
+ this.cache,
82
+ this.metricsClient
60
83
  );
61
84
  this.outboundServer.on('error', (...args) => {
62
85
  this.logger.push({ args }).log('Unhandled error in Outbound Server');
@@ -67,16 +90,140 @@ class Server extends EventEmitter {
67
90
  clientKey: this.conf.oauthTestServer.clientKey,
68
91
  clientSecret: this.conf.oauthTestServer.clientSecret,
69
92
  port: this.conf.oauthTestServer.listenPort,
70
- logger: this.logger.push({ app: 'mojaloop-sdk-oauth-test-server' }),
93
+ logger: this.logger.push(LOG_ID.OAUTHTEST),
71
94
  });
72
95
 
73
96
  this.testServer = new TestServer({
74
- port: this.conf.testServerPort,
75
- logger: this.logger.push({ app: 'mojaloop-sdk-test-api' }),
97
+ port: this.conf.test.port,
98
+ logger: this.logger.push(LOG_ID.TEST),
76
99
  cache: this.cache,
77
100
  });
78
101
  }
79
102
 
103
+ async restart(newConf) {
104
+ // Figuring out what config is necessary in each server and component is a pretty big job
105
+ // that we'll have to save for later. For now, when the config changes, we'll restart
106
+ // more than we might have to.
107
+ // We'll do this by:
108
+ // 0. creating a new instance of the logger, if necessary
109
+ // 1. creating a new instance of the cache, if necessary
110
+ // 2. calling the async reconfigure method of each of the servers as necessary- this will
111
+ // return a synchronous function that we can call to swap over the server events and
112
+ // object properties to the new ones. It will:
113
+ // 1. remove the `request` listener for each of the HTTP servers
114
+ // 2. add the new appropriate `request` listener
115
+ // This results in a completely synchronous listener changeover to the new config and
116
+ // therefore hopefully avoids any concurrency issues arising from restarting different
117
+ // servers or components concurrently.
118
+ // TODO: in the sense of being able to reason about the code, it would make some sense to
119
+ // turn the config items or object passed to each server into an event emitter, or pass an
120
+ // additional event emitter to the server constructor for the server to listen to and act
121
+ // on changes. Before this, however, it's probably necessary to ensure each server gets
122
+ // _only_ the config it needs, not the entire config object.
123
+ // Further: it might be possible to use Object.observe for this functionality.
124
+ // TODO: what happens if this is run concurrently? I.e. if it is called twice in rapid
125
+ // succession. This question probably needs to be asked of the reconfigure message on every
126
+ // server.
127
+ // Note that it should be possible to reconfigure ports on a running server by reassigning
128
+ // servers, e.g.
129
+ // this.inboundServer._server = createHttpServer();
130
+ // this.inboundServer._server.listen(newPort);
131
+ // If there are conflicts, for example if the new configuration specifies the new inbound
132
+ // port to be the same value as the old outbound port, this will require either
133
+ // 1. some juggling of HTTP servers, e.g.
134
+ // const oldInboundServer = this.inboundServer._server;
135
+ // this.inboundServer._server = this.outboundServer._server;
136
+ // .. etc.
137
+ // 2. some juggling of sockets between servers, if possible
138
+ // 3. rearchitecting of the servers, perhaps splitting the .start() method on the servers
139
+ // to an .init() and .listen() methods, with the latter optionally taking an HTTP server
140
+ // as argument
141
+ // This _might_ introduce some confusion/complexity for existing websocket clients, but as
142
+ // the event handlers _should_ not be modified this shouldn't be a problem. A careful
143
+ // analysis of this will be necessary.
144
+ assert(newConf.inbound.port === this.conf.inbound.port
145
+ && newConf.outbound.port === this.conf.outbound.port
146
+ && newConf.test.port === this.conf.test.port
147
+ && newConf.oauthTestServer.listenPort === this.conf.oauthTestServer.listenPort
148
+ && newConf.control.mgmtAPIWsPort === this.conf.control.mgmtAPIWsPort,
149
+ 'Cannot reconfigure ports on running server');
150
+ const doNothing = () => {};
151
+ const updateLogger = check.notDeepEqual(newConf.logIndent, this.conf.logIndent);
152
+ if (updateLogger) {
153
+ this.logger = new Logger.Logger({
154
+ context: {
155
+ // If we're running from a Mojaloop helm chart deployment, we'll have a SIM_NAME
156
+ simulator: process.env['SIM_NAME'],
157
+ hostname: hostname(),
158
+ },
159
+ stringify: Logger.buildStringify({ space: this.conf.logIndent }),
160
+ });
161
+ }
162
+ let oldCache;
163
+ const updateCache = (
164
+ updateLogger ||
165
+ check.notDeepEqual(this.conf.cacheConfig, newConf.cacheConfig) ||
166
+ check.notDeepEqual(this.conf.enableTestFeatures, newConf.enableTestFeatures)
167
+ );
168
+ if (updateCache) {
169
+ oldCache = this.cache;
170
+ this.cache = new Cache({
171
+ ...newConf.cacheConfig,
172
+ logger: this.logger.push(LOG_ID.CACHE),
173
+ enableTestFeatures: newConf.enableTestFeatures,
174
+ });
175
+ await this.cache.connect();
176
+ }
177
+ const confChanged = !check.deepEqual(newConf, this.conf);
178
+ // TODO: find better naming than "restart", because that's not really what's happening.
179
+ const [restartInboundServer, restartOutboundServer, restartControlClient] = confChanged
180
+ ? await Promise.all([
181
+ this.inboundServer.reconfigure(newConf, this.logger.push(LOG_ID.INBOUND), this.cache),
182
+ this.outboundServer.reconfigure(newConf, this.logger.push(LOG_ID.OUTBOUND), this.cache, this.metricsClient),
183
+ this.controlClient.reconfigure({
184
+ logger: this.logger.push(LOG_ID.CONTROL),
185
+ port: newConf.control.mgmtAPIWsPort,
186
+ appConfig: newConf
187
+ }),
188
+ ])
189
+ : [doNothing, doNothing, doNothing];
190
+ const updateOAuthTestServer = (
191
+ updateLogger || check.notDeepEqual(newConf.oauthTestServer, this.conf.oauthTestServer)
192
+ );
193
+ const restartOAuthTestServer = updateOAuthTestServer
194
+ ? await this.oauthTestServer.reconfigure({
195
+ clientKey: this.conf.oauthTestServer.clientKey,
196
+ clientSecret: this.conf.oauthTestServer.clientSecret,
197
+ port: this.conf.oauthTestServer.listenPort,
198
+ logger: this.logger.push(LOG_ID.OAUTHTEST),
199
+ })
200
+ : doNothing;
201
+ const updateTestServer = (
202
+ updateLogger || updateCache || check.notDeepEqual(newConf.test.port, this.conf.test.port)
203
+ );
204
+ const restartTestServer = updateTestServer
205
+ ? await this.testServer.reconfigure({
206
+ port: newConf.test.port,
207
+ logger: this.logger.push(LOG_ID.TEST),
208
+ cache: this.cache,
209
+ })
210
+ : doNothing;
211
+ // You may not return an async restart function. Perform any required async activity in the
212
+ // reconfigure function and return a sync restart function. See the note at the top of this
213
+ // file.
214
+ [restartTestServer, restartOAuthTestServer, restartInboundServer, restartOutboundServer, restartControlClient]
215
+ .map(f => assert(Promise.resolve(f) !== f, 'Restart functions must be synchronous'));
216
+ restartTestServer();
217
+ restartOAuthTestServer();
218
+ restartInboundServer();
219
+ restartOutboundServer();
220
+ restartControlClient();
221
+ this.conf = newConf;
222
+ await Promise.all([
223
+ oldCache && oldCache.disconnect(),
224
+ ]);
225
+ }
226
+
80
227
  async start() {
81
228
  await this.cache.connect();
82
229
 
@@ -84,9 +231,25 @@ class Server extends EventEmitter {
84
231
  const startOauthTestServer = this.conf.oauthTestServer.enabled
85
232
  ? this.oauthTestServer.start()
86
233
  : null;
234
+
235
+ // We only start the control client if we're running within Mojaloop Payment Manager.
236
+ // The control server is the Payment Manager Management API Service.
237
+ // We only start the client to connect to and listen to the Management API service for
238
+ // management protocol messages e.g configuration changes, certificate updates etc.
239
+ if (this.conf.pm4mlEnabled) {
240
+ this.controlClient = await ControlAgent.Client.Create({
241
+ address: this.conf.control.mgmtAPIWsUrl,
242
+ port: this.conf.control.mgmtAPIWsPort,
243
+ logger: this.logger.push(LOG_ID.CONTROL),
244
+ appConfig: this.conf,
245
+ });
246
+ this.controlClient.on(ControlAgent.EVENT.RECONFIGURE, this.restart.bind(this));
247
+ }
248
+
87
249
  await Promise.all([
88
250
  this.inboundServer.start(),
89
251
  this.outboundServer.start(),
252
+ this.metricsServer.start(),
90
253
  startTestServer,
91
254
  startOauthTestServer,
92
255
  ]);
@@ -98,10 +261,23 @@ class Server extends EventEmitter {
98
261
  this.outboundServer.stop(),
99
262
  this.oauthTestServer.stop(),
100
263
  this.testServer.stop(),
264
+ this.controlClient.stop(),
265
+ this.metricsServer.stop(),
101
266
  ]);
102
267
  }
103
268
  }
104
269
 
270
+ /*
271
+ * Call the Connector Manager in Management API to get the updated config
272
+ */
273
+ async function _GetUpdatedConfigFromMgmtAPI(conf, logger, client) {
274
+ logger.log(`Getting updated config from Management API at ${conf.control.mgmtAPIWsUrl}:${conf.control.mgmtAPIWsPort}...`);
275
+ const clientSendResponse = await client.send(ControlAgent.build.CONFIGURATION.READ());
276
+ logger.log('client send returned:: ', clientSendResponse);
277
+ const responseRead = await client.receive();
278
+ logger.log('client receive returned:: ', responseRead);
279
+ return responseRead.data;
280
+ }
105
281
 
106
282
  if(require.main === module) {
107
283
  (async () => {
@@ -115,6 +291,18 @@ if(require.main === module) {
115
291
  },
116
292
  stringify: Logger.buildStringify({ space: config.logIndent }),
117
293
  });
294
+ if(config.pm4mlEnabled) {
295
+ const controlClient = await ControlAgent.Client.Create({
296
+ address: config.control.mgmtAPIWsUrl,
297
+ port: config.control.mgmtAPIWsPort,
298
+ logger: logger,
299
+ appConfig: config,
300
+ });
301
+ const updatedConfigFromMgmtAPI = await _GetUpdatedConfigFromMgmtAPI(config, logger, controlClient);
302
+ logger.log(`updatedConfigFromMgmtAPI: ${JSON.stringify(updatedConfigFromMgmtAPI)}`);
303
+ _.merge(config, updatedConfigFromMgmtAPI);
304
+ controlClient.terminate();
305
+ }
118
306
  const svr = new Server(config, logger);
119
307
  svr.on('error', (err) => {
120
308
  logger.push({ err }).log('Unhandled server error');
@@ -140,9 +328,9 @@ if(require.main === module) {
140
328
  // scheme adapter as a service
141
329
  module.exports = {
142
330
  Cache,
331
+ ControlAgent,
143
332
  InboundServerMiddleware,
144
333
  OutboundServerMiddleware,
145
- RandomPhrase,
146
334
  Router,
147
335
  Server,
148
336
  Validate,