@mojaloop/sdk-scheme-adapter 18.0.0 → 18.0.2

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 (119) hide show
  1. package/.dockerignore +18 -0
  2. package/.eslintignore +2 -0
  3. package/.ncurc.yaml +7 -0
  4. package/CHANGELOG.md +20 -0
  5. package/CODEOWNERS +31 -1
  6. package/README.md +1 -0
  7. package/audit-resolve.json +35 -0
  8. package/package.json +13 -13
  9. package/test/__mocks__/@mojaloop/sdk-standard-components.js +0 -151
  10. package/test/__mocks__/javascript-state-machine.js +0 -21
  11. package/test/__mocks__/redis.js +0 -78
  12. package/test/__mocks__/uuidv4.js +0 -16
  13. package/test/config/integration.env +0 -146
  14. package/test/integration/lib/Outbound/data/quotesPostRequest.json +0 -52
  15. package/test/integration/lib/Outbound/data/transfersPostRequest.json +0 -24
  16. package/test/integration/lib/Outbound/parties.test.js +0 -31
  17. package/test/integration/lib/Outbound/quotes.test.js +0 -62
  18. package/test/integration/lib/Outbound/simpleTransfers.test.js +0 -70
  19. package/test/integration/lib/cache.test.js +0 -79
  20. package/test/integration/testEnv.js +0 -4
  21. package/test/unit/ControlClient.test.js +0 -69
  22. package/test/unit/ControlServer/events.js +0 -41
  23. package/test/unit/ControlServer/index.js +0 -227
  24. package/test/unit/ControlServer.test.js +0 -66
  25. package/test/unit/InboundServer.test.js +0 -443
  26. package/test/unit/TestServer.test.js +0 -392
  27. package/test/unit/api/accounts/accounts.test.js +0 -128
  28. package/test/unit/api/accounts/data/postAccountsBody.json +0 -7
  29. package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +0 -33
  30. package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +0 -19
  31. package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +0 -31
  32. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +0 -34
  33. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +0 -39
  34. package/test/unit/api/accounts/utils.js +0 -79
  35. package/test/unit/api/proxy/data/proxyConfig.yaml +0 -82
  36. package/test/unit/api/proxy/data/requestBody.json +0 -22
  37. package/test/unit/api/proxy/data/requestHeaders.json +0 -5
  38. package/test/unit/api/proxy/data/requestQuery.json +0 -6
  39. package/test/unit/api/proxy/data/responseBody.json +0 -21
  40. package/test/unit/api/proxy/data/responseHeaders.json +0 -5
  41. package/test/unit/api/proxy/proxy.test.js +0 -220
  42. package/test/unit/api/proxy/utils.js +0 -79
  43. package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +0 -24
  44. package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +0 -18
  45. package/test/unit/api/transfers/data/postQuotesBody.json +0 -52
  46. package/test/unit/api/transfers/data/postTransfersBadBody.json +0 -17
  47. package/test/unit/api/transfers/data/postTransfersBody.json +0 -24
  48. package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +0 -62
  49. package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +0 -48
  50. package/test/unit/api/transfers/data/postTransfersSimpleBody.json +0 -26
  51. package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +0 -128
  52. package/test/unit/api/transfers/data/putPartiesBody.json +0 -20
  53. package/test/unit/api/transfers/data/putQuotesBody.json +0 -37
  54. package/test/unit/api/transfers/data/putTransfersBody.json +0 -17
  55. package/test/unit/api/transfers/transfers.test.js +0 -191
  56. package/test/unit/api/transfers/utils.js +0 -264
  57. package/test/unit/api/utils.js +0 -86
  58. package/test/unit/config.test.js +0 -119
  59. package/test/unit/data/commonHttpHeaders.json +0 -7
  60. package/test/unit/data/defaultConfig.json +0 -70
  61. package/test/unit/data/postQuotesBody.json +0 -52
  62. package/test/unit/data/putParticipantsBody.json +0 -12
  63. package/test/unit/data/putPartiesBody.json +0 -20
  64. package/test/unit/data/testFile.json +0 -29
  65. package/test/unit/data/testFile.yaml +0 -14
  66. package/test/unit/inboundApi/data/mockArguments.json +0 -117
  67. package/test/unit/inboundApi/data/mockTransactionRequest.json +0 -42
  68. package/test/unit/inboundApi/handlers.test.js +0 -786
  69. package/test/unit/index.test.js +0 -88
  70. package/test/unit/lib/cache.test.js +0 -145
  71. package/test/unit/lib/model/AccountsModel.test.js +0 -124
  72. package/test/unit/lib/model/InboundTransfersModel.test.js +0 -889
  73. package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +0 -253
  74. package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +0 -247
  75. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +0 -166
  76. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +0 -245
  77. package/test/unit/lib/model/OutboundTransfersModel.test.js +0 -1579
  78. package/test/unit/lib/model/PartiesModel.test.js +0 -478
  79. package/test/unit/lib/model/QuotesModel.test.js +0 -477
  80. package/test/unit/lib/model/TransfersModel.test.js +0 -481
  81. package/test/unit/lib/model/common/PersistentStateMachine.test.js +0 -178
  82. package/test/unit/lib/model/data/authorizationsResponse.json +0 -13
  83. package/test/unit/lib/model/data/bulkQuoteRequest.json +0 -27
  84. package/test/unit/lib/model/data/bulkQuoteResponse.json +0 -35
  85. package/test/unit/lib/model/data/bulkTransferFulfil.json +0 -13
  86. package/test/unit/lib/model/data/bulkTransferRequest.json +0 -29
  87. package/test/unit/lib/model/data/defaultConfig.json +0 -59
  88. package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +0 -42
  89. package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +0 -22
  90. package/test/unit/lib/model/data/getTransfersBackendResponse.json +0 -34
  91. package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +0 -17
  92. package/test/unit/lib/model/data/mockArguments.json +0 -188
  93. package/test/unit/lib/model/data/mockTxnRequestsArguments.json +0 -63
  94. package/test/unit/lib/model/data/notificationAbortedToPayee.json +0 -10
  95. package/test/unit/lib/model/data/notificationReservedToPayee.json +0 -10
  96. package/test/unit/lib/model/data/notificationToPayee.json +0 -10
  97. package/test/unit/lib/model/data/payeeParty.json +0 -18
  98. package/test/unit/lib/model/data/putQuotesResponse.json +0 -33
  99. package/test/unit/lib/model/data/putTransfersResponse.json +0 -5
  100. package/test/unit/lib/model/data/quoteResponse.json +0 -42
  101. package/test/unit/lib/model/data/requestToPayRequest.json +0 -20
  102. package/test/unit/lib/model/data/requestToPayTransferRequest.json +0 -27
  103. package/test/unit/lib/model/data/transactionRequestResponse.json +0 -18
  104. package/test/unit/lib/model/data/transferFulfil.json +0 -10
  105. package/test/unit/lib/model/data/transferRequest.json +0 -26
  106. package/test/unit/lib/model/mockedLibRequests.js +0 -74
  107. package/test/unit/mockLogger.js +0 -39
  108. package/test/unit/outboundApi/data/bulkQuoteRequest.json +0 -28
  109. package/test/unit/outboundApi/data/bulkTransferRequest.json +0 -28
  110. package/test/unit/outboundApi/data/mockBulkQuoteError.json +0 -45
  111. package/test/unit/outboundApi/data/mockBulkTransferError.json +0 -48
  112. package/test/unit/outboundApi/data/mockError.json +0 -41
  113. package/test/unit/outboundApi/data/mockGetPartiesError.json +0 -4
  114. package/test/unit/outboundApi/data/mockRequestToPayError.json +0 -32
  115. package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +0 -39
  116. package/test/unit/outboundApi/data/requestToPay.json +0 -21
  117. package/test/unit/outboundApi/data/requestToPayTransferRequest.json +0 -20
  118. package/test/unit/outboundApi/data/transferRequest.json +0 -21
  119. package/test/unit/outboundApi/handlers.test.js +0 -887
@@ -1,24 +0,0 @@
1
- {
2
- "transferId": "00000000-0000-1000-8000-000000000005",
3
- "payeeFsp": "sim",
4
- "payerFsp": "mojaloop-sdk",
5
- "amount": {
6
- "amount": "100",
7
- "currency": "USD"
8
- },
9
- "ilpPacket": "AYIBgQAAAAAAAASwNGxldmVsb25lLmRmc3AxLm1lci45T2RTOF81MDdqUUZERmZlakgyOVc4bXFmNEpLMHlGTFGCAUBQU0svMS4wCk5vbmNlOiB1SXlweUYzY3pYSXBFdzVVc05TYWh3CkVuY3J5cHRpb246IG5vbmUKUGF5bWVudC1JZDogMTMyMzZhM2ItOGZhOC00MTYzLTg0NDctNGMzZWQzZGE5OGE3CgpDb250ZW50LUxlbmd0aDogMTM1CkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbgpTZW5kZXItSWRlbnRpZmllcjogOTI4MDYzOTEKCiJ7XCJmZWVcIjowLFwidHJhbnNmZXJDb2RlXCI6XCJpbnZvaWNlXCIsXCJkZWJpdE5hbWVcIjpcImFsaWNlIGNvb3BlclwiLFwiY3JlZGl0TmFtZVwiOlwibWVyIGNoYW50XCIsXCJkZWJpdElkZW50aWZpZXJcIjpcIjkyODA2MzkxXCJ9IgA",
10
- "condition": "f5sqb7tBTWPd5Y8BDFdMm9BJR_MNI4isf8p8n4D5pHA",
11
- "expiration": "2020-01-20T11:31:49.325Z",
12
- "extensionList": {
13
- "extension": [
14
- {
15
- "key": "qreqkey1",
16
- "value": "qreqvalue1"
17
- },
18
- {
19
- "key": "qreqkey2",
20
- "value": "qreqvalue2"
21
- }
22
- ]
23
- }
24
- }
@@ -1,31 +0,0 @@
1
- 'use strict';
2
-
3
- const axios = require('axios');
4
- const env = require('../../testEnv');
5
- const { SDKStateEnum } = require('../../../../src/lib/model/common');
6
-
7
- jest.dontMock('redis');
8
-
9
- describe('/parties', () => {
10
-
11
- test('get - happy flow', async () => {
12
- const getPartiesURI = `${env.OutboundHostURI}/parties/MSISDN/1234567890`;
13
- const res = await axios.get(getPartiesURI);
14
-
15
- expect(res.status).toEqual(200);
16
- expect(res.data.currentState).toEqual(SDKStateEnum.COMPLETED);
17
- expect(typeof res.data.party).toEqual('object');
18
- expect(typeof res.data.party.body).toEqual('object');
19
- expect(typeof res.data.party.headers).toEqual('object');
20
- });
21
-
22
- test('get - timeout', (done) => {
23
- const getPartiesURI = `${env.OutboundHostURI}/parties/MSISDN/0987654321`;
24
- axios.get(getPartiesURI).catch(err => {
25
- expect(err.response.status).toEqual(500);
26
- expect(err.response.data.message).toEqual('Timeout');
27
- done();
28
- });
29
-
30
- });
31
- });
@@ -1,62 +0,0 @@
1
- 'use strict';
2
-
3
- const axios = require('axios');
4
- const { uuid } = require('uuidv4');
5
- const env = require('../../testEnv');
6
- const quotesPostRequest = require('./data/quotesPostRequest.json');
7
- const { SDKStateEnum } = require('../../../../src/lib/model/common');
8
-
9
-
10
- jest.dontMock('redis');
11
-
12
- describe('/quotes', () => {
13
-
14
- test('post - happy flow', async () => {
15
- const postQuotesURI = `${env.OutboundHostURI}/quotes`;
16
- const quoteId = uuid();
17
- const res = await axios({
18
- method: 'POST',
19
- url: postQuotesURI,
20
- data: {
21
- fspId: 'switch',
22
- quotesPostRequest: {
23
- ...quotesPostRequest,
24
- quoteId
25
- }
26
- },
27
- headers: {
28
- 'access-control-allow-origin': '*'
29
- }
30
- });
31
-
32
- expect(res.status).toEqual(200);
33
- expect(res.data.currentState).toEqual(SDKStateEnum.COMPLETED);
34
- expect(typeof res.data.quotes).toEqual('object');
35
- expect(typeof res.data.quotes.body).toEqual('object');
36
- expect(typeof res.data.quotes.headers).toEqual('object');
37
- });
38
-
39
- test('post - timeout', (done) => {
40
- const postQuotesURI = `${env.OutboundHostURI}/quotes`;
41
- const quoteId = uuid();
42
- axios({
43
- method: 'POST',
44
- url: postQuotesURI,
45
- data: {
46
- fspId: 'timeout-fsp-id',
47
- quotesPostRequest: {
48
- ...quotesPostRequest,
49
- quoteId
50
- }
51
- },
52
- headers: {
53
- 'access-control-allow-origin': '*'
54
- }
55
- }).catch(err => {
56
- expect(err.response.status).toEqual(500);
57
- expect(err.response.data.message).toEqual('Timeout');
58
- done();
59
- });
60
- });
61
-
62
- });
@@ -1,70 +0,0 @@
1
- 'use strict';
2
-
3
- const axios = require('axios');
4
- const { uuid } = require('uuidv4');
5
- const env = require('../../testEnv');
6
- const transfersPostRequest = require('./data/transfersPostRequest.json');
7
- const { SDKStateEnum } = require('../../../../src/lib/model/common');
8
-
9
- jest.dontMock('redis');
10
-
11
- /*
12
- "TRANSFERS_VALIDATION_WITH_PREVIOUS_QUOTES": false,
13
- "TRANSFERS_VALIDATION_ILP_PACKET": false,
14
- "TRANSFERS_VALIDATION_CONDITION": false,
15
-
16
- Ensure these values in the TTK `user_config.json` file are set to false.
17
- Since we are testing the /transfers endpoint in isolation without a prior
18
- quote and a fake `ilpPacket` and `condition`.
19
- */
20
- describe('/simpleTransfers', () => {
21
-
22
- test('post - happy flow', async () => {
23
- const postTransfersURI = `${env.OutboundHostURI}/simpleTransfers`;
24
- const transferId = uuid();
25
- const res = await axios({
26
- method: 'POST',
27
- url: postTransfersURI,
28
- data: {
29
- fspId: 'switch',
30
- transfersPostRequest: {
31
- ...transfersPostRequest,
32
- transferId
33
- }
34
- },
35
- headers: {
36
- 'access-control-allow-origin': '*'
37
- }
38
- });
39
-
40
- expect(res.status).toEqual(200);
41
- expect(res.data.currentState).toEqual(SDKStateEnum.COMPLETED);
42
- expect(typeof res.data.transfer).toEqual('object');
43
- expect(typeof res.data.transfer.body).toEqual('object');
44
- expect(typeof res.data.transfer.headers).toEqual('object');
45
- });
46
-
47
- test('post - timeout', async () => {
48
- const postTransfersURI = `${env.OutboundHostURI}/simpleTransfers`;
49
- const transferId = uuid();
50
- try {
51
- await axios({
52
- method: 'POST',
53
- url: postTransfersURI,
54
- data: {
55
- fspId: 'timeout-fsp-id-transfer',
56
- transfersPostRequest: {
57
- ...transfersPostRequest,
58
- transferId
59
- }
60
- },
61
- headers: {
62
- 'access-control-allow-origin': '*'
63
- }
64
- });
65
- } catch (err) {
66
- expect(err.response.status).toEqual(500);
67
- expect(err.response.data.message).toEqual('Timeout');
68
- }
69
- });
70
- });
@@ -1,79 +0,0 @@
1
- /*****
2
- License
3
- --------------
4
- Copyright © 2017 Bill & Melinda Gates Foundation
5
- The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
- http://www.apache.org/licenses/LICENSE-2.0
7
- Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
-
9
- Initial contribution
10
- --------------------
11
- The initial functionality and code base was donated by the Mowali project working in conjunction with MTN and Orange as service provides.
12
- * Project: Mowali
13
-
14
- Contributors
15
- --------------
16
- This is the official list of the Mojaloop project contributors for this file.
17
- Names of the original copyright holders (individuals or organizations)
18
- should be listed with a '*' in the first column. People who have
19
- contributed from an organization can be listed under the organization
20
- that actually holds the copyright for their contributions (see the
21
- Gates Foundation organization for an example). Those individuals should have
22
- their names indented and be marked with a '-'. Email address can be added
23
- optionally within square brackets <email>.
24
- * Gates Foundation
25
- - Name Surname <name.surname@gatesfoundation.com>
26
-
27
- * Crosslake
28
- - Lewis Daly <lewisd@crosslaketech.com>
29
- --------------
30
- ******/
31
- 'use strict';
32
-
33
- jest.dontMock('redis');
34
-
35
- const Cache = require('~/lib/cache');
36
- const { Logger } = require('@mojaloop/sdk-standard-components');
37
- const env = require('../testEnv');
38
-
39
- const defaultCacheConfig = {
40
- cacheUrl: env.redisUrl,
41
- logger: null
42
- };
43
-
44
- const createCache = async (config) => {
45
- config.logger = new Logger.Logger({
46
- context: {
47
- app: 'mojaloop-sdk-inboundCache'
48
- },
49
- stringify: Logger.buildStringify({ space: 4 }),
50
- });
51
- const cache = new Cache(config);
52
- await cache.connect();
53
-
54
- return cache;
55
- };
56
-
57
- describe('Cache', () => {
58
- let cache;
59
-
60
- beforeEach(async () => {
61
- cache = await createCache(defaultCacheConfig);
62
- });
63
-
64
- afterEach(async () => {
65
- await cache.disconnect();
66
- });
67
-
68
- test('Sets and retrieves an object in the cache', async () => {
69
- // Arrange
70
- const value = {test: true};
71
-
72
- // Act
73
- await cache.set('keyA', JSON.stringify(value));
74
- const result = await cache.get('keyA');
75
-
76
- // Assert
77
- expect(result).toStrictEqual(value);
78
- });
79
- });
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- OutboundHostURI: 'http://localhost:4001',
3
- redisUrl: 'redis://localhost:6379',
4
- };
@@ -1,69 +0,0 @@
1
-
2
- const ControlAgent = require('~/ControlAgent');
3
- const TestControlServer = require('./ControlServer');
4
- const { Logger } = require('@mojaloop/sdk-standard-components');
5
-
6
- jest.mock('~/lib/cache');
7
-
8
- // TODO:
9
- // - diff against master to determine what else needs testing
10
- // - especially look for assertions in the code
11
- // - err.. grep the code for TODO
12
-
13
- describe('ControlAgent', () => {
14
- it('exposes a valid message API', () => {
15
- expect(Object.keys(ControlAgent.build).sort()).toEqual(
16
- Object.keys(ControlAgent.MESSAGE).sort(),
17
- 'The API exposed by the builder object must contain as top-level keys all of the message types exposed in the MESSAGE constant. Check that ControlAgent.MESSAGE has the same keys as ControlAgent.build.'
18
- );
19
- Object.entries(ControlAgent.build).forEach(([messageType, builders]) => {
20
- expect(Object.keys(ControlAgent.VERB)).toEqual(
21
- expect.arrayContaining(Object.keys(builders)),
22
- `For message type '${messageType}' every builder must correspond to a verb. Check that ControlAgent.build.${messageType} has the same keys as ControlAgent.VERB.`
23
- );
24
- });
25
- expect(Object.keys(ControlAgent.build.ERROR.NOTIFY).sort()).toEqual(
26
- Object.keys(ControlAgent.ERROR).sort(),
27
- 'ControlAgent.ERROR.NOTIFY should contain the same keys as ControlAgent.ERROR'
28
- );
29
- });
30
-
31
- describe('API', () => {
32
- let server, logger, client;
33
- const appConfig = { control: { port: 4005 }, what: 'ever' };
34
- const changedConfig = { ...appConfig, some: 'thing' };
35
-
36
- beforeEach(async () => {
37
- logger = new Logger.Logger({ stringify: () => '' });
38
- server = new TestControlServer.Server({ logger, appConfig });
39
- client = await ControlAgent.Client.Create({
40
- address: 'localhost',
41
- port: server.address().port,
42
- logger,
43
- appConfig
44
- });
45
- });
46
-
47
- afterEach(async () => {
48
- await client.stop();
49
- await server.stop();
50
- });
51
-
52
- it('receives config when requested', async () => {
53
- await client.send(ControlAgent.build.CONFIGURATION.READ());
54
- const response = await client.receive();
55
- expect(response).toEqual({
56
- ...JSON.parse(ControlAgent.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
57
- });
58
- });
59
-
60
- it('emits new config when received', async () => {
61
- const newConfigEvent = new Promise(
62
- (resolve) => client.on(ControlAgent.EVENT.RECONFIGURE, resolve)
63
- );
64
- server.broadcastConfigChange(changedConfig);
65
- const newConfEventData = await newConfigEvent;
66
- expect(newConfEventData).toEqual(changedConfig);
67
- });
68
- });
69
- });
@@ -1,41 +0,0 @@
1
- /**************************************************************************
2
- * (C) Copyright ModusBox Inc. 2020 - All rights reserved. *
3
- * *
4
- * This file is made available under the terms of the license agreement *
5
- * specified in the corresponding source code repository. *
6
- * *
7
- * ORIGINAL AUTHOR: *
8
- * Steven Oderayi - steven.oderayi@modusbox.com *
9
- **************************************************************************/
10
-
11
- const { EventEmitter } = require('events');
12
-
13
- /**************************************************************************
14
- * Internal events received by the control server via the exposed internal
15
- * event emitter.
16
- *************************************************************************/
17
- const INTERNAL_EVENTS = {
18
- SERVER: {
19
- BROADCAST_CONFIG_CHANGE: 'BROADCAST_CONFIG_CHANGE',
20
- }
21
- };
22
- const internalEventEmitter = new EventEmitter();
23
-
24
- /**************************************************************************
25
- * getInternalEventEmitter
26
- *
27
- * Returns an EventEmmitter that can be used to exchange internal events with
28
- * either the control server or the client from other modules within this service.
29
- * This prevents the need to pass down references to either the server or the client
30
- * from one module to another in order to use their interfaces.
31
- *
32
- * @returns {events.EventEmitter}
33
- *************************************************************************/
34
- const getInternalEventEmitter = () => {
35
- return internalEventEmitter;
36
- };
37
-
38
- module.exports = {
39
- getInternalEventEmitter,
40
- INTERNAL_EVENTS
41
- };
@@ -1,227 +0,0 @@
1
- /**************************************************************************
2
- * (C) Copyright ModusBox Inc. 2020 - All rights reserved. *
3
- * *
4
- * This file is made available under the terms of the license agreement *
5
- * specified in the corresponding source code repository. *
6
- * *
7
- * ORIGINAL AUTHOR: *
8
- * Matt Kingston - matt.kingston@modusbox.com *
9
- **************************************************************************/
10
- 'use strict';
11
-
12
- const ws = require('ws');
13
- const jsonPatch = require('fast-json-patch');
14
- const { generateSlug } = require('random-word-slugs');
15
- const { getInternalEventEmitter, INTERNAL_EVENTS } = require('./events');
16
-
17
- const ControlServerEventEmitter = getInternalEventEmitter();
18
-
19
-
20
- /**************************************************************************
21
- * The message protocol messages, verbs, and errors
22
- *************************************************************************/
23
- const MESSAGE = {
24
- CONFIGURATION: 'CONFIGURATION',
25
- ERROR: 'ERROR',
26
- };
27
-
28
- const VERB = {
29
- READ: 'READ',
30
- NOTIFY: 'NOTIFY',
31
- PATCH: 'PATCH'
32
- };
33
-
34
- const ERROR = {
35
- UNSUPPORTED_MESSAGE: 'UNSUPPORTED_MESSAGE',
36
- UNSUPPORTED_VERB: 'UNSUPPORTED_VERB',
37
- JSON_PARSE_ERROR: 'JSON_PARSE_ERROR',
38
- };
39
-
40
- /**************************************************************************
41
- * Private convenience functions
42
- *************************************************************************/
43
- const serialise = JSON.stringify;
44
- const deserialise = (msg) => {
45
- //reviver function
46
- return JSON.parse(msg.toString(), (k, v) => {
47
- if (
48
- v !== null &&
49
- typeof v === 'object' &&
50
- 'type' in v &&
51
- v.type === 'Buffer' &&
52
- 'data' in v &&
53
- Array.isArray(v.data)) {
54
- return new Buffer(v.data);
55
- }
56
- return v;
57
- });
58
- };
59
-
60
-
61
- const buildMsg = (verb, msg, data, id = generateSlug(4)) => serialise({
62
- verb,
63
- msg,
64
- data,
65
- id,
66
- });
67
-
68
- const buildPatchConfiguration = (oldConf, newConf, id) => {
69
- const patches = jsonPatch.compare(oldConf, newConf);
70
- return buildMsg(VERB.PATCH, MESSAGE.CONFIGURATION, patches, id);
71
- };
72
-
73
- /**************************************************************************
74
- * build
75
- *
76
- * Public object exposing an API to build valid protocol messages.
77
- * It is not the only way to build valid messages within the protocol.
78
- *************************************************************************/
79
- const build = {
80
- CONFIGURATION: {
81
- PATCH: buildPatchConfiguration,
82
- READ: (id) => buildMsg(VERB.READ, MESSAGE.CONFIGURATION, {}, id),
83
- NOTIFY: (config, id) => buildMsg(VERB.NOTIFY, MESSAGE.CONFIGURATION, config, id),
84
- },
85
- ERROR: {
86
- NOTIFY: {
87
- UNSUPPORTED_MESSAGE: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.UNSUPPORTED_MESSAGE, id),
88
- UNSUPPORTED_VERB: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.UNSUPPORTED_VERB, id),
89
- JSON_PARSE_ERROR: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.JSON_PARSE_ERROR, id),
90
- }
91
- },
92
- };
93
-
94
- /**************************************************************************
95
- * Server
96
- *
97
- * The Control Server. Exposes a websocket control API.
98
- * Used to hot-restart the SDK.
99
- *
100
- * logger - Logger- see SDK logger used elsewhere
101
- * port - HTTP port to host on
102
- * appConfig - The configuration for the entire application- supplied here as this class uses it to
103
- * validate reconfiguration requests- it is not used for configuration here, however
104
- * server - optional HTTP/S server on which to serve the websocket
105
- *************************************************************************/
106
- class Server extends ws.Server {
107
- constructor({ logger, appConfig = {} }) {
108
- super({ clientTracking: true, port: appConfig.control.port });
109
-
110
- this._logger = logger;
111
- this._port = appConfig.control.port;
112
- this._appConfig = appConfig;
113
- this._clientData = new Map();
114
-
115
- this.on('error', err => {
116
- this._logger.push({ err })
117
- .log('Unhandled websocket error occurred. Shutting down.');
118
- process.exit(1);
119
- });
120
-
121
- this.on('connection', (socket, req) => {
122
- const logger = this._logger.push({
123
- url: req.url,
124
- ip: 'localhost',
125
- remoteAddress: req.socket.remoteAddress,
126
- });
127
- logger.log('Websocket connection received');
128
- this._clientData.set(socket, { ip: req.connection.remoteAddress, logger });
129
-
130
- socket.on('close', (code, reason) => {
131
- logger.push({ code, reason }).log('Websocket connection closed');
132
- this._clientData.delete(socket);
133
- });
134
-
135
- socket.on('message', this._handle(socket, logger));
136
- });
137
- this._logger.push(this.address()).log('running on');
138
- }
139
-
140
- // Close the server then wait for all the client sockets to close
141
- async stop() {
142
- const closing = new Promise(resolve => this.close(resolve));
143
- for (const client of this.clients) {
144
- client.terminate();
145
- }
146
- await closing;
147
- this._logger.log('Control server shutdown complete');
148
- }
149
-
150
- _handle(client, logger) {
151
- return (data) => {
152
- // TODO: json-schema validation of received message- should be pretty straight-forward
153
- // and will allow better documentation of the API
154
- let msg;
155
- try {
156
- msg = deserialise(data);
157
- } catch (err) {
158
- logger.push({ data }).log('Couldn\'t parse received message');
159
- client.send(build.ERROR.NOTIFY.JSON_PARSE_ERROR());
160
- }
161
- logger.push({ msg }).log('Handling received message');
162
- switch (msg.msg) {
163
- case MESSAGE.CONFIGURATION:
164
- switch (msg.verb) {
165
- case VERB.READ:
166
- (async () => {
167
- const jwsCerts = await this.populateConfig();
168
- client.send(build.CONFIGURATION.NOTIFY(jwsCerts, msg.id));
169
- })();
170
- break;
171
- default:
172
- client.send(build.ERROR.NOTIFY.UNSUPPORTED_VERB(msg.id));
173
- break;
174
- }
175
- break;
176
- default:
177
- client.send(build.ERROR.NOTIFY.UNSUPPORTED_MESSAGE(msg.id));
178
- break;
179
- }
180
- };
181
- }
182
-
183
- async populateConfig(){
184
- return this._appConfig;
185
- }
186
-
187
-
188
- /**
189
- * Register this server instance to receive internal server messages
190
- * from other modules.
191
- */
192
- registerInternalEvents() {
193
- ControlServerEventEmitter.on(INTERNAL_EVENTS.SERVER.BROADCAST_CONFIG_CHANGE, (params) => this.broadcastConfigChange(params));
194
- }
195
-
196
- /**
197
- * Broadcast configuration change to all connected clients.
198
- *
199
- * @param {object} params Updated configuration
200
- */
201
- broadcastConfigChange(updatedConfig) {
202
- const updateConfMsg = build.CONFIGURATION.PATCH({}, updatedConfig, generateSlug(4));
203
- this.broadcast(updateConfMsg);
204
- }
205
-
206
- /**
207
- * Broadcasts a protocol message to all connected clients.
208
- *
209
- * @param {string} msg
210
- */
211
- broadcast(msg) {
212
- this.clients.forEach((client) => {
213
- if (client.readyState === ws.WebSocket.OPEN) {
214
- client.send(msg);
215
- }
216
- });
217
- }
218
- }
219
-
220
-
221
- module.exports = {
222
- Server,
223
- build,
224
- MESSAGE,
225
- VERB,
226
- ERROR,
227
- };
@@ -1,66 +0,0 @@
1
-
2
- const ControlServer = require('~/ControlServer');
3
- const { Logger } = require('@mojaloop/sdk-standard-components');
4
-
5
- jest.mock('~/lib/cache');
6
-
7
- // TODO:
8
- // - diff against master to determine what else needs testing
9
- // - especially look for assertions in the code
10
- // - err.. grep the code for TODO
11
-
12
- describe('ControlServer', () => {
13
- it('exposes a valid message API', () => {
14
- expect(Object.keys(ControlServer.build).sort()).toEqual(
15
- Object.keys(ControlServer.MESSAGE).sort(),
16
- 'The API exposed by the builder object must contain as top-level keys all of the message types exposed in the MESSAGE constant. Check that ControlServer.MESSAGE has the same keys as ControlServer.build.'
17
- );
18
- Object.entries(ControlServer.build).forEach(([messageType, builders]) => {
19
- expect(Object.keys(ControlServer.VERB)).toEqual(
20
- expect.arrayContaining(Object.keys(builders)),
21
- `For message type '${messageType}' every builder must correspond to a verb. Check that ControlServer.build.${messageType} has the same keys as ControlServer.VERB.`
22
- );
23
- });
24
- expect(Object.keys(ControlServer.build.ERROR.NOTIFY).sort()).toEqual(
25
- Object.keys(ControlServer.ERROR).sort(),
26
- 'ControlServer.ERROR.NOTIFY should contain the same keys as ControlServer.ERROR'
27
- );
28
- });
29
-
30
- describe('API', () => {
31
- let server, logger, client;
32
- const appConfig = { what: 'ever' };
33
- const changedConfig = { ...appConfig, some: 'thing' };
34
-
35
- beforeEach(async () => {
36
- logger = new Logger.Logger({ stringify: () => '' });
37
- server = new ControlServer.Server({ logger, appConfig });
38
- client = await ControlServer.Client.Create({
39
- address: 'localhost',
40
- port: server.address().port,
41
- logger
42
- });
43
- });
44
-
45
- afterEach(async () => {
46
- await server.stop();
47
- });
48
-
49
- it('supplies config when requested', async () => {
50
- await client.send(ControlServer.build.CONFIGURATION.READ());
51
- const response = await client.receive();
52
- expect(response).toEqual({
53
- ...JSON.parse(ControlServer.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
54
- });
55
- });
56
-
57
- it('emits new config when received', async () => {
58
- const newConfigEvent = new Promise(
59
- (resolve) => server.on(ControlServer.EVENT.RECONFIGURE, resolve)
60
- );
61
- await client.send(ControlServer.build.CONFIGURATION.PATCH(appConfig, changedConfig));
62
- const newConfEventData = await newConfigEvent;
63
- expect(newConfEventData).toEqual(changedConfig);
64
- });
65
- });
66
- });