Compare commits

..
250 Commits
Author SHA1 Message Date
Chris Troutner 7a620892d7 Merge pull request #57 from Permissionless-Software-Foundation/ct-unstable
Updating helia-coord
2024-07-21 15:54:31 -07:00
Chris Troutner 4a1524629c Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-05-21 20:33:37 -07:00
Chris Troutner f7aaba71d1 Merge pull request #153 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.8
2024-05-04 09:38:28 -07:00
Chris Troutner 418a2e2ad2 fix(helia-coord): Updating to v1.5.8 2024-05-04 09:37:10 -07:00
Chris Troutner 74248ea0c7 Merge pull request #56 from Permissionless-Software-Foundation/ct-unstable
Improvements from production testing
2024-04-28 20:40:25 -07:00
Chris Troutner 70a69e98bb fix(pollForBchServices()): filtering for only wallet-services, not consumers 2024-04-28 16:53:34 -07:00
Chris Troutner 8cffd7fae3 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-27 13:22:07 -07:00
Chris Troutner a1023e8224 Merge pull request #152 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Setting memory limit at 4GB
2024-04-27 13:20:20 -07:00
Chris Troutner a32ed63d47 fix(Docker): Setting memory limit at 4GB 2024-04-27 13:17:57 -07:00
Chris Troutner 934fb46ffc Merge pull request #151 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Removing Volume statement
2024-04-26 14:40:41 -07:00
Chris Troutner b27136d03b fix(Docker): Removing Volume statement 2024-04-26 14:39:18 -07:00
Chris Troutner 9837d6a7fb Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:15:57 -07:00
Chris Troutner 60a3d67c0b Merge pull request #150 from Permissionless-Software-Foundation/ct-unstable
fix(config): web2Api can be set with env var
2024-04-13 07:11:20 -07:00
Chris Troutner a03c5d8c91 fix(config): web2Api can be set with env var 2024-04-13 07:09:45 -07:00
Chris Troutner 0e7afb5f51 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:07:44 -07:00
Chris Troutner bcc90da49b Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
fix(config): Adding web2Api property to jsonLd config
2024-04-13 07:04:02 -07:00
Chris Troutner b473a3803c fix(config): Adding web2Api property to jsonLd config 2024-04-13 07:02:32 -07:00
Chris Troutner 38ed9a8dac Merge pull request #55 from Permissionless-Software-Foundation/ct-unstable
fix(getPins): Adding getPins endpoint for File Pin Service
2024-04-12 10:46:01 -07:00
Chris Troutner 0ca46df43e fix(getPins): Adding getPins endpoint for File Pin Service 2024-04-12 10:44:03 -07:00
Chris Troutner b5f09fbab9 Sycing with upstream ipfs-service-provider 2024-04-12 08:35:49 -07:00
Chris Troutner e3e928b33d Adding getPins endpoint for File Pin Service 2024-04-12 08:34:39 -07:00
Chris Troutner 9614f186b4 Merge pull request #148 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.7
2024-04-12 08:32:13 -07:00
Chris Troutner 0390f2d956 fix(helia-coord): Updating to v1.5.7 2024-04-12 08:30:14 -07:00
Chris Troutner 43025f0265 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Adding endpoints needed for metrics
2024-04-11 05:36:56 -07:00
Chris Troutner 7ea17fb57c Removing example timer controller 2024-04-11 05:34:25 -07:00
Chris Troutner efb29104d7 feat(/ipfs/file-info/:cid): GET endpoint to get CID pin metadata from ipfs-file-pin-server over JSON RPC 2024-04-10 19:41:43 -07:00
Chris Troutner 971474d493 linting 2024-04-10 11:05:55 -07:00
Chris Troutner 71a6de03b3 feat(/bch/service): GET endpoint to get ipfs-bch-wallet-service consumed by this app 2024-04-10 11:05:46 -07:00
Chris Troutner 98dd904fa5 feat(/ipfs/service): GET endpoint to retrieve File Pin service used by this app 2024-04-10 10:52:07 -07:00
Chris Troutner b5c7e2efb3 Using filename from file metadata 2024-04-09 22:00:58 -07:00
Chris Troutner 3c9bbb097e Successfully recieving result from file pin JSON RPC call 2024-04-09 20:56:40 -07:00
Chris Troutner 8956968027 Commenting out downloadCid2() 2024-04-09 08:08:53 -07:00
Chris Troutner 14c724bf9c Prototyping IPFS file download 2024-04-09 06:36:23 -07:00
Chris Troutner a9f7e609e9 Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Changing ports to comply with cashstack.info
2024-04-07 14:41:49 -07:00
Chris Troutner fee68e0f59 fix(docker): Changing ports to comply with cashstack.info 2024-04-07 14:38:40 -07:00
Chris Troutner 86c005c205 Merge pull request #52 from Permissionless-Software-Foundation/ct-unstable
fix(jsonLd): Updating jsonLd details passed to other nodes
2024-04-03 09:36:10 -07:00
Chris Troutner d4d5b2eb68 fix(jsonLd): Updating jsonLd details passed to other nodes 2024-04-03 09:34:43 -07:00
Chris Troutner 32a651b7a8 Merge pull request #51 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream
2024-04-03 08:19:26 -07:00
Chris Troutner 7da5c5b4d6 Fixing merge conflicts 2024-04-03 08:16:52 -07:00
Chris Troutner ba5ead998d Merge pull request #147 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Tweaking some defaults
2024-04-03 06:02:17 -07:00
Chris Troutner 409a0f1049 fix(docker): Tweaking some defaults 2024-04-03 05:54:24 -07:00
Chris Troutner bd2f27bb64 Merge pull request #146 from Permissionless-Software-Foundation/ct-unstable
fix(incrementNextAddress): fixing bug
2024-03-29 12:44:43 -07:00
Chris Troutner 55d8d131cf Removing commented code 2024-03-29 12:43:15 -07:00
Chris Troutner 5a6e3247e8 fix(incrementNextAddress): fixing bug 2024-03-29 12:43:02 -07:00
Chris Troutner 2a522775eb Adding wallet.json to gitignore 2024-03-29 11:36:58 -07:00
Chris Troutner d677c83795 Removing wallet.json file 2024-03-29 11:36:35 -07:00
Chris Troutner 6cfaceb99e Merge pull request #50 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream, fixing flaky tests
2024-03-24 08:25:20 -07:00
Chris Troutner b12cc194f4 fix(tests): Fixed fragile unit tests 2024-03-24 08:24:08 -07:00
Chris Troutner 7cffd9f377 Merge pull request #145 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.6, auto-retry of bootstrap list
2024-03-24 07:44:46 -07:00
Chris Troutner 0a406cb9a4 fix(helia-coord): Updating to v1.5.6, auto-retry of bootstrap list 2024-03-24 07:43:34 -07:00
Chris Troutner f172a51016 Merge pull request #144 from Permissionless-Software-Foundation/ct-unstable
Integrating minimal-slp-wallet. Wallet creation at startup
2024-03-23 17:46:55 -07:00
Chris Troutner 00d53659b1 Updating README 2024-03-23 17:44:41 -07:00
Chris Troutner b0797d92f5 Increased test coverage to 100% 2024-03-23 17:42:59 -07:00
Chris Troutner 48484ed7af Fixing typo with web3 wallet 2024-03-23 17:23:07 -07:00
Chris Troutner 6a4aadde8a Refactoring config file 2024-03-23 17:14:26 -07:00
Chris Troutner 79e3aba26e Adding default startup wallet 2024-03-23 16:18:10 -07:00
Chris Troutner bf61359bc7 feat(wallet): Added adapter library for consuming minimal-slp-wallet 2024-03-23 11:16:45 -07:00
Chris Troutner 1911991511 linting 2024-03-23 08:18:47 -07:00
Chris Troutner ef0179fb3f fix(README): Adding config variables to README 2024-03-23 08:18:32 -07:00
Chris Troutner 38bdfe306b Merge pull request #49 from Permissionless-Software-Foundation/helia
v3.0: Helia
2024-03-22 13:31:33 -07:00
Chris Troutner 5c674f4dc5 Merge branch 'master' into helia 2024-03-22 13:10:30 -07:00
Chris Troutner 80155393d9 Bumping to v3.0, helia, synced with ipfs-service-provider 2024-03-22 12:58:16 -07:00
Chris Troutner 232417b8b1 linting 2024-03-22 12:42:41 -07:00
Chris Troutner 36cb9c5734 Updating libp2p to v0.46.16 2024-03-22 12:37:56 -07:00
Chris Troutner a79d152993 Merge pull request #143 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.5
2024-03-12 08:32:05 -07:00
Chris Troutner ce7c3a0e2f fix(helia-coord): Updating to v1.5.5 2024-03-12 08:30:32 -07:00
Chris Troutner e6b0580209 Merge pull request #142 from Permissionless-Software-Foundation/ct-unstable
Back to 100% unit test coverage
2024-03-10 07:44:19 -07:00
Chris Troutner 1f3c639100 fix(tests): Back to 100% unit test coverage 2024-03-10 07:43:06 -07:00
Chris Troutner e6efc4f49b Prototyped /ipfs/download 2024-03-07 21:27:06 -08:00
Chris Troutner dbbd321029 fix(/ipfs/node): Added unit tests for API controller 2024-03-07 18:55:09 -08:00
Chris Troutner 439dbc54ae fix(ipfs): Increased adapter to 100% unit test coverage 2024-03-07 10:22:25 -08:00
Chris Troutner 547e59f52f fix(Docker): Adding .env file 2024-03-04 10:47:08 -08:00
Chris Troutner a0d385de73 Merge pull request #141 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.4, fixes minor bug
2024-03-03 06:46:38 -08:00
Chris Troutner bec136009f fix(helia-coord): Updating to v1.5.4, fixes minor bug 2024-03-03 06:44:24 -08:00
Chris Troutner 98b7fa7734 Merge pull request #140 from Permissionless-Software-Foundation/ct-unstable
Removing Circuit Relay for nodes configured for direct connection
2024-02-05 06:41:01 -08:00
Chris Troutner d7bb476858 fix(circuit-relay): Removing CR transport if node is configured for direct connection 2024-02-05 06:39:01 -08:00
Chris Troutner 18c71c383f fix(webrtc): Upgrading from v3.2.11 to v4.0.17 2024-02-05 06:26:01 -08:00
Chris Troutner de85a933c4 fix(webrtc): Adding @libp2p/webrtc 2024-02-04 20:43:34 -08:00
Chris Troutner 16247372f1 linting 2024-02-04 20:35:34 -08:00
Chris Troutner c6c8b6418c Prototyping download REST API 2024-02-04 20:35:22 -08:00
Chris Troutner 10c4e7380f fix(webrtc): Removing webrtc library 2024-01-31 09:38:18 -08:00
Chris Troutner 4933dce472 Merge pull request #139 from Permissionless-Software-Foundation/ct-unstable
fix(webRTC): Explicitly adding @libp2p/webrtc package
2024-01-31 08:50:43 -08:00
Chris Troutner 80c2822716 fix(webRTC): Explicitly adding @libp2p/webrtc package 2024-01-31 08:42:38 -08:00
Chris Troutner f48ff9a67e Merge pull request #138 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.3
2024-01-30 19:45:57 -08:00
Chris Troutner 4bd2385359 fix(helia-coord): Updating to v1.5.3 2024-01-30 19:44:29 -08:00
Chris Troutner be71492773 Merge pull request #137 from Permissionless-Software-Foundation/ct-unstable
fix(createNode()): Updating RSA to Ed25519 keys
2024-01-29 07:58:49 -08:00
Chris Troutner bcb70edf2b fix(createNode()): Updating RSA to Ed25519 keys 2024-01-29 07:57:32 -08:00
Chris Troutner 07071032bc Merge pull request #136 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Updated new node.js installation
2024-01-28 19:57:45 -08:00
Chris Troutner f476e119a2 fix(Docker): Updated new node.js installation 2024-01-28 19:54:21 -08:00
Chris Troutner 0fed232428 Merge pull request #135 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Switching to ct-unstable branch
2024-01-28 19:42:02 -08:00
Chris Troutner 0273510485 fix(Docker): Switching to ct-unstable branch 2024-01-28 19:38:42 -08:00
Chris Troutner a62dee37f4 Merge pull request #134 from Permissionless-Software-Foundation/v1.0.10
libp2p v1.2.1
2024-01-28 19:36:24 -08:00
Chris Troutner 8d6af3e1b7 fix(helia-coord): Updating to v1.4.3 2024-01-28 15:17:01 -08:00
Chris Troutner ee0f2da641 fix(docker): Building container rather than using image 2024-01-28 15:05:34 -08:00
Chris Troutner 44499d9652 Updating Dockerfile to use v1.0.10 branch 2024-01-28 15:00:30 -08:00
Chris Troutner 365a5dcd4b Updated to libp2p 1.2.1 2024-01-28 14:59:25 -08:00
Chris Troutner 7f5b456018 linting 2024-01-28 14:13:42 -08:00
Chris Troutner c7217486bd fix(getSeed()): Generates seed for IPFS private key 2024-01-28 12:49:18 -08:00
Chris Troutner 050141d31e Creating todo for seed 2024-01-28 08:22:55 -08:00
Chris Troutner b697f37199 fix(keys): Creating persistent keystore for libp2p v1.0.x 2024-01-28 08:19:38 -08:00
Chris Troutner ceb58caba1 linting 2024-01-28 08:04:02 -08:00
Chris Troutner 2724b500ee Got keychain working for first time 2024-01-28 08:03:41 -08:00
Chris Troutner 1b84e7beb8 Merge pull request #133 from Permissionless-Software-Foundation/v0.46.21
fix(pubsub): Removing chat handler causing screen spam
2024-01-27 17:48:29 -08:00
Chris Troutner 383900d8b5 fix(pubsub): Removing chat handler causing screen spam 2024-01-27 17:45:23 -08:00
Chris Troutner ba806d86b5 Merge pull request #132 from Permissionless-Software-Foundation/v0.46.21
Updated to helia-coord v1.4.1
2024-01-27 15:02:57 -08:00
Chris Troutner 4d7c7350a0 linting 2024-01-27 15:01:01 -08:00
Chris Troutner adc6500aed Adding config options for V2 Circuit Relay server 2024-01-27 13:17:34 -08:00
Chris Troutner eb9fce73eb Updated helia-coord to v1.4.1 2024-01-27 10:45:44 -08:00
Chris Troutner ec3ed165e4 feat(GET /ipfs/node): Retrieves thisNode object 2024-01-27 09:26:50 -08:00
Chris Troutner d41d20cc81 prototyping keychain example 2024-01-26 19:52:55 -08:00
Chris Troutner 5f965e7146 Switching back to libp2p v0.46.21 2024-01-11 12:26:16 -08:00
Chris Troutner f382bb4578 Merge pull request #131 from Permissionless-Software-Foundation/ct-unstable
Helia v1.2.24 - recommended peers from gist
2023-12-18 20:56:26 -08:00
Chris Troutner ba37f87f80 fix(helia-coord): updating to v1.2.24, adding recommended peers 2023-12-18 20:55:25 -08:00
Chris Troutner eaa9c23f55 Updating helia-coord ot v1.2.23 2023-12-16 06:42:12 -08:00
Chris Troutner ac3730dbd9 Merge pull request #130 from Permissionless-Software-Foundation/libp2p-v1
Libp2p v1
2023-12-13 19:46:43 -08:00
Chris Troutner 581b8eb626 fix(libp2p): Updating to v1.0.10 2023-12-13 19:44:58 -08:00
Chris Troutner cd4ec2711e Updated circuit-relay library 2023-12-06 10:32:59 -08:00
Chris Troutner e9e3dbad0d Updated to libp2p 1.0.9, added @libp2p/identify 2023-12-06 10:20:48 -08:00
Chris Troutner 295b361431 Merge pull request #129 from Permissionless-Software-Foundation/ct-unstable
Updating helia-coord to v1.2.21
2023-12-04 08:53:58 -08:00
Chris Troutner ad60df9d51 Updating helia-coord to v1.2.21 2023-12-04 08:52:16 -08:00
Chris Troutner 6ae635c21d Merge pull request #128 from Permissionless-Software-Foundation/ct-unstable
fix(bootstrap): Loading bootstrap nodes from gist only
2023-12-04 07:27:14 -08:00
Chris Troutner 61807a2f3d fix(bootstrap): Loading bootstrap nodes from gist only 2023-12-04 07:25:32 -08:00
Chris Troutner 16ba5a8980 Merge pull request #127 from Permissionless-Software-Foundation/ct-unstable
fix(deps): Updating helia-coord and helia
2023-12-03 16:43:34 -08:00
Chris Troutner 99b817549e fix(deps): Updating helia-coord and helia 2023-12-03 16:42:32 -08:00
Chris Troutner 60a8c335c8 Merge pull request #126 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updated to v1.2.18
2023-12-03 09:42:52 -08:00
Chris Troutner 1783768c32 fix(helia-coord): Updated to v1.2.18 2023-12-03 09:41:42 -08:00
Chris Troutner b3630eaa06 Merge pull request #125 from Permissionless-Software-Foundation/ct-unstable
fix(helia): Upgrade to v0.46.21
2023-12-03 09:04:01 -08:00
Chris Troutner d691016911 fix(helia): Upgrade to v0.46.21 2023-12-03 09:01:03 -08:00
Chris Troutner 6506aead8c Merge pull request #124 from Permissionless-Software-Foundation/ct-unstable
fix(webrtc): Adding webrtc as a transport
2023-12-03 07:37:11 -08:00
Chris Troutner ddfc83d026 fix(webrtc): Adding webrtc as a transport 2023-12-03 07:35:47 -08:00
Chris Troutner fe42c1e0e3 Merge pull request #123 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Autodetecting ip addr and adding to multiaddr
2023-12-03 07:30:15 -08:00
Chris Troutner 928a211a63 fix(ipfs): Autodetecting ip addr and adding to multiaddr 2023-12-03 07:28:31 -08:00
Chris Troutner 8bff69a4ca Merge pull request #122 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.2.17
2023-12-02 17:12:47 -08:00
Chris Troutner 5fced7d501 fix(helia-coord): Updating to v1.2.17 2023-12-02 17:11:05 -08:00
Chris Troutner 7cdf622ad6 Merge pull request #121 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating helida-coord to v1.2.16
2023-12-02 14:13:43 -08:00
Chris Troutner afd8ceac7b fix(helia-coord): Updating helida-coord to v1.2.16 2023-12-02 14:12:32 -08:00
Chris Troutner 5d63db3662 Merge pull request #120 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.2.5
2023-11-04 08:47:34 -07:00
Chris Troutner 9f6cd7dbf1 fix(helia-coord): Updating to v1.2.5 2023-11-04 08:45:46 -07:00
Chris Troutner 0725aa3ada Merge pull request #119 from Permissionless-Software-Foundation/ct-unstable
fix(peers): Adding connectionAddr to /ipfs/peers
2023-11-03 14:42:28 -07:00
Chris Troutner 3eb88d4076 fix(peers): Adding connectionAddr to /ipfs/peers 2023-11-03 14:33:58 -07:00
Chris Troutner 1c61f150b1 fix(v1 relays): Discerning between v1 and v2 circuit relays 2023-11-03 12:07:43 -07:00
Chris Troutner 4c74b287d8 Merge pull request #118 from Permissionless-Software-Foundation/ct-unstable
feat(/ipfs/connect): Adding REST API endpoint to connect to an IPFS peer
2023-11-01 14:18:51 -07:00
Chris Troutner 264d762f74 feat(/ipfs/connect): Adding REST API endpoint to connect to an IPFS peer 2023-11-01 14:17:24 -07:00
Chris Troutner 3c02461c6b Merge pull request #117 from Permissionless-Software-Foundation/ct-unstable
fix(circuit relay): Adding ability to creating and consume circuit re…
2023-10-29 16:41:52 -07:00
Chris Troutner a30f2be51f fix(circuit relay): Adding ability to creating and consume circuit relays 2023-10-29 16:39:35 -07:00
Chris Troutner 0690a9fb65 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(README): Adding link to video demo
2023-10-28 13:57:55 -07:00
Chris Troutner 4fa103765e Merge branch 'master' into ct-unstable 2023-10-28 13:51:35 -07:00
Chris Troutner b0d6ce40a9 fix(README): Adding link to video demo 2023-10-28 13:50:46 -07:00
Chris Troutner c57a3d935b Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
Updating Docker container for production
2023-10-28 11:32:24 -07:00
Chris Troutner 82b5943601 Adding collaborative coinjoin project to README 2023-10-28 11:30:57 -07:00
Chris Troutner 41b76967f5 Adding production section back to README 2023-10-28 11:27:06 -07:00
Chris Troutner e4c2ee44c1 fix(production): Updating docker container 2023-10-28 11:24:40 -07:00
Chris Troutner c05f022995 Bumping to v5.0.0 2023-10-28 10:32:45 -07:00
Chris Troutner 7723c3a201 Merge pull request #114 from Permissionless-Software-Foundation/helia
Helia refactor. v4.0.0
2023-10-28 10:24:20 -07:00
Chris Troutner 8e338818ca Updating READMe 2023-10-28 10:19:59 -07:00
Chris Troutner ea51aa420c Updating README 2023-10-28 10:14:42 -07:00
Chris Troutner bc410678b4 Updating README and package.json 2023-10-28 10:11:04 -07:00
Chris Troutner a42c15c969 Updating package.json 2023-10-28 09:55:41 -07:00
Chris Troutner a3102a0278 Cleaned up comments 2023-10-28 09:50:12 -07:00
Chris Troutner f4f142dc48 fix(tests): Increased test coverage to 100% 2023-10-28 09:17:48 -07:00
Chris Troutner e03a70c9a3 fix(get-peers): finalizing REST endpoint behavior 2023-10-27 17:39:24 -07:00
Chris Troutner d47b8797d4 fix(libp2p): Updated to v0.46.16 2023-10-25 22:13:35 -07:00
Chris Troutner 56ba0aad02 Adding IPFS REST API endpoints 2023-10-23 20:28:17 -07:00
Chris Troutner 825bc6d2de fix(ipfs): Creating block & data store directories if they don't exit 2023-10-23 09:16:06 -07:00
Chris Troutner bf206107f8 fixed merge conflicts with helia branch 2023-10-23 08:36:08 -07:00
Chris Troutner b1100c21bd Merge pull request #48 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2023-10-22 18:31:03 -07:00
Chris Troutner 10d50a6776 Commenting out admin stuff from users e2e test 2023-10-22 18:22:43 -07:00
Chris Troutner 18e7ff90aa linting 2023-10-22 18:12:54 -07:00
Chris Troutner dfe390f9c3 Commented out failing tests 2023-10-22 18:12:44 -07:00
Chris Troutner 268035f13b fixed merge conflicts. Some tests are broken 2023-10-22 17:43:28 -07:00
Chris Troutner 18fe63a5ad fix(tests): Fixed broken tests 2023-10-22 17:35:10 -07:00
Chris Troutner d6e0e7b903 got helia working 2023-10-22 07:51:45 -07:00
Chris Troutner 8759e1af5e Merge pull request #113 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to v9.1.13
2023-08-29 16:38:56 -07:00
Chris Troutner b78f3fb08b fix(ipfs-coord-esm): Updating to v9.1.13 2023-08-29 16:34:41 -07:00
Chris Troutner 5fa0b2e1c8 Merge pull request #112 from Permissionless-Software-Foundation/ct-unstable
fix(license): Updating license file
2023-06-25 06:15:14 -07:00
Chris Troutner 6a88d0559a fix(license): Updating license file 2023-06-25 06:13:22 -07:00
Chris Troutner 314fdc2bb0 fix(minimal-slp-wallet): Updating to v5.11.1 2023-06-25 06:11:45 -07:00
Chris Troutner 077c60bd34 Merge pull request #111 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to v9.1.9
2023-05-13 07:06:26 -07:00
Chris Troutner 6485537207 Turning down debug logs 2023-05-13 07:03:49 -07:00
Chris Troutner 286f079679 fix(ipfs-coord-esm): Updating to v9.1.9 2023-05-13 06:59:25 -07:00
Chris Troutner 075818d551 Adding test coverage reporting for just unit tests 2023-05-12 15:25:58 -07:00
Chris Troutner d59415999f Merge pull request #47 from Permissionless-Software-Foundation/ct-unstable
fix(deps): Updating dependencies
2023-05-12 08:25:11 -07:00
Chris Troutner 80d6a879d2 Merge branch 'master' into ct-unstable 2023-05-12 08:24:06 -07:00
Chris Troutner 1b3681eb41 fix(deps): Updating dependencies 2023-05-12 08:22:59 -07:00
Chris Troutner 9de2caba94 Merge pull request #110 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to 9.1.7
2023-05-09 09:00:04 -07:00
Chris Troutner a45fb3ad08 fix(ipfs-coord-esm): Updating to 9.1.7 2023-05-09 08:56:26 -07:00
Chris Troutner 666f954529 Merge pull request #109 from Permissionless-Software-Foundation/ct-unstable
Minor Fixes
2023-05-09 08:07:55 -07:00
Chris Troutner 7d2e02fe64 fix(timer-controller): Adding unit tests for startTimers() 2023-05-09 08:06:57 -07:00
Chris Troutner 9912ed7af7 fix(json-files): Rejecting promise rather than throwing an error 2023-05-09 08:00:30 -07:00
Chris Troutner 441d898ae7 fix(timer controller): Preventing duplicate timer controllers 2023-05-08 08:01:02 -07:00
Chris Troutner 6d1d0c5823 Merge pull request #46 from Permissionless-Software-Foundation/ct-unstable
fix(README): Updating README
2023-04-29 07:34:44 -07:00
Chris Troutner 9530a17835 Adding video link to README 2023-04-29 07:13:47 -07:00
Chris Troutner d0de2569e9 fix(README): Updating README 2023-04-29 07:12:34 -07:00
Chris Troutner 3fc384909e Merge pull request #45 from Permissionless-Software-Foundation/ct-unstable
fix(preferredProvider): fixing logic issue
2023-04-27 19:40:43 -07:00
Chris Troutner 9a8d0a6ec1 fix(preferredProvider): fixing logic issue 2023-04-27 19:39:42 -07:00
Chris Troutner 9e14e24eb4 Merge pull request #44 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2023-04-27 19:17:31 -07:00
Chris Troutner 553a7ff86a Merge pull request #108 from Permissionless-Software-Foundation/ct-unstable
feat(ipfs): Allowing disabling IPFS features with config flag
2023-04-10 09:22:11 -07:00
Chris Troutner 349d070273 feat(ipfs): Allowing disabling IPFS features with config flag 2023-04-10 09:19:51 -07:00
Chris Troutner 1eb68c9ea5 fix(deps): Updating dependencies 2023-04-06 06:33:23 -07:00
Chris Troutner c26ad11290 adding curl example for refreshing token cache 2023-04-06 04:55:59 -07:00
Chris Troutner b6dcb9c177 Updating link to clean architecture 2023-03-22 17:10:51 -07:00
Chris Troutner 8aff3a6568 Merge pull request #107 from Permissionless-Software-Foundation/ct-unstable
feat(timer-controllers): Added template for timer-based controllers
2023-02-25 06:10:53 -08:00
Chris Troutner 9e885b8bc3 feat(timer-controllers): Added template for timer-based controllers 2023-02-25 06:09:56 -08:00
Chris Troutner d6b924a5f4 Merge pull request #106 from Permissionless-Software-Foundation/ct-unstable
Updating ipfs-coord-esm for bch-coinjoin pubsub
2023-02-24 13:50:17 -08:00
Chris Troutner 97691a0dd9 fix(minimal-slp-wallet): Updated to v5.8.5 2023-02-24 13:47:22 -08:00
Chris Troutner 3781af6305 fix(ipfs-coord-esm): Updating to v9.1.5 2023-02-24 13:45:48 -08:00
Chris Troutner cb5d5a603d fixing typo in user REST controller 2023-02-23 08:36:47 -08:00
Chris Troutner 00a7b78fde Removing debug statements 2023-02-13 07:31:19 -08:00
Chris Troutner 9d80341357 debugging preferred provider 2023-02-13 07:22:29 -08:00
Chris Troutner 00b76226ec Merge pull request #43 from Permissionless-Software-Foundation/ct-unstable
fixing improper array
2023-02-13 06:56:46 -08:00
Chris Troutner f0552bce32 fix(utxos): Fixing typo 2023-02-13 06:55:37 -08:00
Chris Troutner 4aa7de253d fixing improper array 2023-02-08 15:06:07 -08:00
Chris Troutner abd01e647a Merge pull request #42 from Permissionless-Software-Foundation/ct-unstable
feat(utxoBulk): Adding REST API endpoint for UTXO bulk queries
2023-02-08 12:45:55 -08:00
Chris Troutner 8237fec8b7 Updating package-lock 2023-02-08 12:40:03 -08:00
Chris Troutner 0d06b2eaaa feat(utxoBulk): Adding REST API endpoint for UTXO bulk queries 2023-02-08 12:17:01 -08:00
Chris Troutner 82ea90c45c fix(getTokenData2()): Writing value of updateCache to console 2023-01-27 07:27:17 -08:00
Chris Troutner fbe7bf9742 Merge pull request #41 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData2()): Adding argument for updateCache
2023-01-26 18:09:53 -08:00
Chris Troutner 4447c2224e fix(getTokenData2()): Adding argument for updateCache 2023-01-26 18:08:51 -08:00
Chris Troutner 81713bfebe Updating README 2023-01-16 05:37:52 -08:00
Chris Troutner 0f8a6b9299 Merge pull request #103 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to v5.5.5
2022-12-05 13:53:19 -08:00
Chris Troutner 4f2c87bbd4 fix(minimal-slp-wallet): Updating to v5.5.5 2022-12-05 13:51:41 -08:00
Chris Troutner dd7db3814a Merge pull request #102 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to v5.5.3
2022-11-30 15:06:29 -08:00
Chris Troutner fdb3b9c3ac fix(minimal-slp-wallet): Updating to v5.5.3 2022-11-30 14:59:54 -08:00
Chris Troutner db72e0c95e Merge pull request #40 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v6.5.3
2022-11-02 05:01:33 -07:00
Chris Troutner 9c9c3e7770 fix(bch-js): Updating to v6.5.3 2022-11-02 04:59:33 -07:00
Chris Troutner 2ef0130559 Merge pull request #39 from Permissionless-Software-Foundation/ct-unstable
Locking perferred provider set in env var
2022-10-25 15:41:35 -07:00
Chris Troutner 610ca4ec8b linting 2022-10-25 15:38:17 -07:00
Chris Troutner 1a32b941df fix(preferred provider): Throwing error if provider set in env var and clinet tries to change it 2022-10-25 15:38:07 -07:00
Chris Troutner 433931111c Merge pull request #38 from Permissionless-Software-Foundation/ct-unstable
feat(XEC price): Adding endpoint to retrieve XEC price
2022-10-17 10:20:20 -07:00
Chris Troutner 3edc945487 feat(XEC price): Adding endpoint to retrieve XEC price 2022-10-17 10:18:52 -07:00
Chris Troutner c13a5d1593 Merge pull request #101 from Permissionless-Software-Foundation/ct-unstable
Increased test coverage to 100%
2022-09-17 15:24:26 -07:00
Chris Troutner 6d7b3db370 Using latest version of ipfs-coord-esm 2022-09-17 15:19:37 -07:00
Chris Troutner 33d06e99e8 fix(tests): got ipfs-coord lib to 100% test coverage 2022-09-17 14:51:57 -07:00
Chris Troutner 3203e56a4d fix(tests): Added user e2e tests back in 2022-09-17 14:47:17 -07:00
Chris Troutner ff47f1b356 fix(test): Got use cases to 100% test coverage 2022-09-17 14:23:46 -07:00
Chris Troutner d9377ea78b fix(tests): Increased coverage of REST API validators 2022-09-17 14:18:16 -07:00
Chris Troutner d266bcc54b fix(deps): Updating dependencies 2022-09-17 10:10:39 -07:00
Chris Troutner a033fb1042 fix(tests): Replacing nyc with c8, for test coverage of ESM 2022-09-16 08:38:53 -07:00
Chris Troutner 569dcbd576 Merge pull request #100 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to new version to fix bugs
2022-09-08 09:32:01 -07:00
Chris Troutner b0a2d73540 fix(ipfs-coord): Updating to new version to fix bugs 2022-09-08 09:30:21 -07:00
Chris Troutner 0d4c8cd56e Merge pull request #99 from Permissionless-Software-Foundation/ct-unstable
Breaking change: Converted from CommonJS to ESM
2022-09-08 09:00:19 -07:00
Chris Troutner b37dae60d1 fix(ESM): Converted from CommonJS to ESM 2022-09-08 08:59:03 -07:00
Chris Troutner 448b028bb4 fixed linting issue 2022-09-08 08:58:39 -07:00
Chris Troutner da637df44a Merge pull request #98 from Permissionless-Software-Foundation/ct-unstable
Using bandwidth limited IPFS Docker container
2022-08-27 13:43:51 -07:00
Chris Troutner 9315e6f166 fix(production): Updating docker image 2022-08-27 13:39:06 -07:00
Chris Troutner 7e965e8415 Adding comments 2022-08-27 12:59:20 -07:00
Chris Troutner a5916c95f9 fix(production): Using bandwidth limited IPFS Docker container 2022-08-27 12:58:41 -07:00
Chris Troutner 708f97d107 More dependency updates 2022-08-27 12:55:24 -07:00
Chris Troutner 637738bfb9 Updated npm deps 2022-08-27 12:53:43 -07:00
Chris Troutner d8e82c5ad2 fix(ipfs-coord): Updating to v8.0.5 2022-08-27 12:52:16 -07:00
134 changed files with 29226 additions and 20022 deletions
+3
View File
@@ -68,5 +68,8 @@ ipfsdata
.ipfsdata
run-dev.sh
data/
run-dev.sh
!README.md
wallet.json
+8
View File
@@ -0,0 +1,8 @@
[
{
"srcDir": "",
"destDir": "",
"files": "**/*.js",
"command": "npm run lint"
}
]
+93
View File
@@ -0,0 +1,93 @@
# Permissionless Software Foundation Community Contributing Guide 1.0
This document describes a very simple process suitable for most projects under
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
Projects are encouraged to adopt this whether they
are hosted under the PSF or not.
The goal of this document is to create a contribution process that:
* Encourages new contributions.
* Encourages contributors to remain involved.
* Avoids unnecessary processes and bureaucracy whenever possible.
* Creates a transparent decision making process which makes it clear how
contributors can be involved in decision making.
This document is based on much prior art in the Node.js community, io.js,
and the Node.js project.
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
## Vocabulary
* A **Contributor** is any individual creating or commenting on an issue or pull request.
* A **Committer** is a subset of contributors who have been given write access to the repository.
* A **TC (Technical Committee)** is a group of committers representing the required technical
expertise to resolve rare disputes.
# Logging Issues
Log an issue for any question or problem you might have. When in doubt, log an issue,
any additional policies about what to include will be provided in the responses. The only
exception is security disclosures which should be sent privately.
Committers may direct you to another repository, ask for additional clarifications, and
add appropriate metadata before the issue is addressed.
Please be courteous, respectful, and every participant is expected to follow the
project's Code of Conduct.
# Contributions
Any change to resources in this repository must be through pull requests. This applies to all changes
to documentation, code, binary files, etc. Even long term committers and TC members must use
pull requests.
No pull request can be merged without being reviewed.
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
contributors in other timezones have time to review. Consideration should also be given to
weekends and other holiday periods to ensure active committers all have reasonable time to
become involved in the discussion and review process if they wish.
The default for each contribution is that it is accepted once no committer has an objection.
During review committers may also request that a specific contributor who is most versed in a
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
process for contributions to land. Once all issues brought by committers are addressed it can
be landed by any committer.
In the case of an objection being raised in a pull request by another committer, all involved
committers should seek to arrive at a consensus by way of addressing concerns being expressed
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
If a contribution is controversial and committers cannot agree about how to get it to land
or if it should land then it should be escalated to the TC. TC members should regularly
discuss pending contributions in order to find a resolution. It is expected that only a
small minority of issues be brought to the TC for resolution and that discussion and
compromise among committers be the default resolution mechanism.
# Becoming a Committer
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
and added as a committer, and be given write access to the repository.
Committers are expected to follow this policy and continue to send pull requests, go through
proper review, and have other committers merge their pull requests.
# TC Process
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
The group tries to find a resolution that has no open objections among TC members.
If a consensus cannot be reached that has no objections then a majority wins vote
is called. It is also expected that the majority of decisions made by the TC are via
a consensus seeking process and that voting is only used as a last-resort.
Resolution may involve returning the issue to committers with suggestions on how to
move forward towards a consensus. It is not expected that a meeting of the TC
will resolve all issues on its agenda during that meeting and may prefer to continue
the discussion happening among the committers.
Members can be added to the TC at any time. Any committer can nominate another committer
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
not to add this new member. Members who do not participate consistently at the level of
a majority of the other members are expected to resign.
+1 -1
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2022 Permissionless Software Foundation
Copyright (c) 2021-2024 Permissionless Software Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+3
View File
@@ -0,0 +1,3 @@
# Pedigree
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
+29 -36
View File
@@ -6,10 +6,14 @@ This is a REST API server based on [Koa](https://koajs.com/). It's essentially a
When started, this web server starts an [IPFS](https://ipfs.io) node and connects to an ipfs-bch-wallet-service server over the IPFS network. It then pipes that connection over its own localized REST API.
More details are available at [CashStack.info](https://CashStack.info).
- [Video: Using ipfs-bch-wallet-consumer](https://youtu.be/7ntMPuqAX64)
## Requirements
- node **^14.18.2**
- npm **^8.3.0**
- node **^16.20.2**
- npm **^8.19.4**
- Docker **^20.10.8**
- Docker Compose **^1.27.4**
@@ -17,20 +21,18 @@ When started, this web server starts an [IPFS](https://ipfs.io) node and connect
### Development Environment
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
To run this software, you must have an instance of go-ipfs running in daemon mode, with the `--enable-pubsub-experiment` flag set. [Here are instruction for installing go-ipfs](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd#install-ipfs).
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
- [Step-by-step installation instructions](https://gist.github.com/christroutner/3304a71d4c12a3a3e1664a438f64d9d0)
Clone this repository and install npm dependencies. You'll also need to install MongoDB if you don't already have it installed:
```bash
git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
cd ipfs-service-provider
git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
cd ipfs-bch-wallet-consumer
./install-mongo-sh
sudo npm install -g node-pre-gyp
npm install
./ipfs-service-provider.sh
cd shell-script
./local-external-ipfs-node.sh
```
### Production Environment
@@ -42,53 +44,44 @@ docker-compose pull
docker-compose up -d
```
You can bring the containers back up with `docker-compose up -d`.
- You can bring the containers down with `docker-compose down`
- You can bring the containers back up with `docker-compose up -d`.
### Operation Notes
## Configuration
- There is a memory leak in the version of js-ipfs. The app is currently configured to shut down every 8 hours to flush memory. It relies on a process manager like pm2, Docker, or systemd to restart the app after it shuts down, in order to ensure continuous operation.
This program is intended to started with a Bash shell script. In that script, you can set the following environment variables:
- The PSF network operates as a private network. It does not connect or interact with the wider PSF network, relying instead on gateways to bridge the two networks, when they need to share content. This improves the performance and experience for everyone in the PSF network. To join the network, you'll need to add the [swarm.key](./swarm.key) file to the IPFS data folder.
```bash
# The IPFS ID of the service running ipfs-bch-wallet-service
export PREFERRED_P2WDB_PROVIDER=12D3KooWCNpwHbHmkNSJsqu3CQVVe3iW8g6e1gn3kWTPahj2igiy
```
- [Instructions on setting up IPFS private networks.](https://github.com/ipfs/go-ipfs/blob/master/docs/experimental-features.md#private-networks)
- For external installations, the swarm.key file will typically go in `~/.ipfs/swarm.key`
- For production Docker containers, the key would go in `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`
### Configuration
## Structure
This app is intended to be started via a bash shell script. See the environment variables used to configure this app in the [config/env/common.js file](./config/env/common.js).
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
## File Structure
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture).
## Usage
- `npm start` Start server on live mode
- `npm run docs` Generate API documentation
- `npm test` Run mocha tests
- `docker-compose build` Build a 'production' Docker container
- `docker-compose up` Run the docker container
## Documentation
API documentation is written inline and generated by [apidoc](http://apidocjs.com/).
API documentation is written inline and generated by [apidoc](http://apidocjs.com/). Docs can be generated with this command:
- `npm run docs`
Visit `http://localhost:5000/docs/` to view docs
Visit `http://localhost:5020/` to view docs
There is additional developer documentation in the [dev-docs directory](./dev-docs).
## Dependencies
- [koa2](https://github.com/koajs/koa/tree/v2.x)
- [koa-router](https://github.com/alexmingoia/koa-router)
- [koa-bodyparser](https://github.com/koajs/bodyparser)
- [koa-generic-session](https://github.com/koajs/generic-session)
- [koa-logger](https://github.com/koajs/logger)
- [MongoDB](http://mongodb.org/)
- [Mongoose](http://mongoosejs.com/)
- [Passport](http://passportjs.org/)
- [Nodemon](http://nodemon.io/)
- [Mocha](https://mochajs.org/)
- [apidoc](http://apidocjs.com/)
- [ESLint](http://eslint.org/)
- [ipfs-coord](https://www.npmjs.com/package/ipfs-coord)
This repository is forked from [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider).
## IPFS
+21 -17
View File
@@ -8,23 +8,26 @@
*/
// npm libraries
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const convert = require('koa-convert')
const logger = require('koa-logger')
const mongoose = require('mongoose')
const session = require('koa-generic-session')
const passport = require('koa-passport')
const mount = require('koa-mount')
const serve = require('koa-static')
const cors = require('kcors')
import Koa from 'koa'
import bodyParser from 'koa-bodyparser'
import convert from 'koa-convert'
import logger from 'koa-logger'
import mongoose from 'mongoose'
import session from 'koa-generic-session'
import passport from 'koa-passport'
import mount from 'koa-mount'
import serve from 'koa-static'
import cors from 'kcors'
// Local libraries
const config = require('../config') // this first.
const AdminLib = require('../src/adapters/admin')
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
const { wlogger } = require('../src/adapters/wlogger')
const Controllers = require('../src/controllers')
import config from '../config/index.js' // this first.
import AdminLib from '../src/adapters/admin.js'
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
import wlogger from '../src/adapters/wlogger.js'
import Controllers from '../src/controllers/index.js'
import { applyPassportMods } from '../config/passport.js'
class Server {
constructor () {
@@ -70,7 +73,8 @@ class Server {
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
// User Authentication
require('../config/passport')
// require('../config/passport')
applyPassportMods(passport)
app.use(passport.initialize())
app.use(passport.session())
@@ -131,4 +135,4 @@ class Server {
}
}
module.exports = Server
export default Server
+89 -21
View File
@@ -1,21 +1,33 @@
/*
This file is used to store unsecure, application-specific data common to all
environments.
Additional Environent Variables:
- CONNECT_PREF: should have a value of 'cr' (default), or 'direct'. This is
used by helia-coord to select a connection preference between peers. Servers
with an ip4 or ip6 address should use 'direct'.
*/
/* eslint no-unneeded-ternary:0 */
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
// Get the version from the package.json file.
const pkgInfo = require('../../package.json')
import { readFileSync } from 'fs'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../package.json`))
const version = pkgInfo.version
const ipfsCoordName = process.env.COORD_NAME
? process.env.COORD_NAME
: 'ipfs-bch-wallet-service'
module.exports = {
export default {
// Configure TCP port.
port: process.env.PORT || 5005,
port: process.env.PORT || 5015,
// Password for HTML UI that displays logs.
logPass: 'test',
@@ -31,14 +43,43 @@ module.exports = {
? process.env.EMAILPASS
: 'emailpassword',
// BEGIN WALLET CONFIGURATION
// BCH Mnemonic for generating encryption keys and payment address
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
// (Optional) use a wallet file generated by psf-bch-wallet.
walletFile: process.env.WALLET_FILE
? process.env.WALLET_FILE
: `${__dirname.toString()}/../../wallet.json`,
// Wallet Configuration
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
// Wallet API Server
// Default web2 server: https://api.fullstack.cash/v5/
// Default web3 server: https://free-bch.fullstack.cash/
apiServer: process.env.WALLET_INTERFACE === 'web2'
? (
process.env.APISERVER
? process.env.APISERVER
: 'https://api.fullstack.cash/v5/'
)
: 'https://free-bch.fullstack.cash/',
// Basic Authentication Password for connecting to bch-api through a web 2
// REST API over HTTP.
authPass: process.env.WALLET_AUTH_PASS
? process.env.WALLET_AUTH_PASS
: '',
// FullStack.cash account information, used for automatic JWT handling.
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
authServer: process.env.AUTHSERVER
? process.env.AUTHSERVER
: 'https://auth.fullstack.cash',
apiServer: process.env.APISERVER
? process.env.APISERVER
: 'https://api.fullstack.cash/v5/',
fullstackLogin: process.env.FULLSTACKLOGIN
? process.env.FULLSTACKLOGIN
: 'demo@demo.com',
@@ -46,7 +87,17 @@ module.exports = {
? process.env.FULLSTACKPASS
: 'demo',
// IPFS settings.
// END WALLET CONFIGURATION
// BEGIN IPFS CONFIGURATION
// Debug verbosity level of helia-coord.
// 0 = no debug logs. 3 = maximum logs.
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
// Enable/Disable the IPFS node at startup. Enabled by default.
useIpfs: process.env.DISABLE_IPFS ? false : true,
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
// SSL domain used for websocket connection via browsers.
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
@@ -60,30 +111,50 @@ module.exports = {
'@type': 'WebAPI',
name: ipfsCoordName,
version,
protocol: 'generic-service',
protocol: 'ipfs-bch-wallet-consumer',
description:
'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
documentation: 'https://ipfs-service-provider.fullstack.cash/',
'REST API gateway for accessing BCH blockchain services over the web3 Cash Stack (https://CashStack.info)',
documentation: 'https://ipfs-bch-wallet-consumer.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
},
// If this node has an IP4 address or domain name used to provide a REST API.
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
},
// IPFS Ports
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4101,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4103,
// BCH Mnemonic for generating encryption keys and payment address
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
// Settings for production, using external go-ipfs node.
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
: 5001,
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
chatPubSubChan: 'psf-ipfs-chat-001',
// This can add specific Circuit Relay v2 servers to connect to.
bootstrapRelays: [
// v2 Circuit Relay (Token Tiger)
// '/ip4/137.184.93.145/tcp/8001/p2p/12D3KooWGMEKkdJfyZbwdH9EafZbRTtMn7FnhWPrE4MhRty2763g',
// v2 Circuit Relay server (FullStack.cash)
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
],
// END IPFS CONFIGURATION
// Preferred provider running ipfs-bch-wallet-service for BCH wallet.
preferredProvider: process.env.PREFERRED_PROVIDER
? process.env.PREFERRED_PROVIDER
: '',
// If true, users will not be able to change the provider via a REST API call.
freezeProvider: process.env.FREEZE_PROVIDER ? true : false,
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
// Used for development
@@ -93,12 +164,9 @@ module.exports = {
? process.env.PREFERRED_P2WDB_PROVIDER
: '',
// Settings for production, using external go-ipfs node.
isProduction: process.env.CONSUMER_ENV === 'production' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
: 5001,
// Preferred P2WDB provider
preferredIpfsFileProvider: process.env.PREFERRED_IPFS_FILE_PROVIDER
? process.env.PREFERRED_IPFS_FILE_PROVIDER
: ''
chatPubSubChan: 'psf-ipfs-chat-001'
}
+1 -1
View File
@@ -4,7 +4,7 @@
specified.
*/
module.exports = {
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-bch-consumer-dev',
+1 -1
View File
@@ -7,7 +7,7 @@
so as not to conflict with the default host port of 27017 for MongoDB.
*/
module.exports = {
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
+1 -1
View File
@@ -4,7 +4,7 @@
This is the environment run by the test suite.
*/
module.exports = {
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-bch-consumer-test',
+15 -4
View File
@@ -1,6 +1,17 @@
const common = require('./env/common')
import common from './env/common.js'
const env = process.env.CONSUMER_ENV || 'development'
const config = require(`./env/${env}`)
import development from './env/development.js'
import production from './env/production.js'
import test from './env/test.js'
module.exports = Object.assign({}, common, config)
const env = process.env.SVC_ENV || 'development'
console.log(`Loading config for this environment: ${env}`)
let config = development
if (env === 'test') {
config = test
} else if (env === 'prod') {
config = production
}
export default Object.assign({}, common, config)
+32 -29
View File
@@ -1,30 +1,6 @@
const passport = require('koa-passport')
const User = require('../src/adapters/localdb/models/users')
const Strategy = require('passport-local')
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id, '-password')
done(null, user)
} catch (err) {
done(err)
}
})
passport.use(
'local',
new Strategy(
{
usernameField: 'email',
passwordField: 'password'
},
passportCallback
)
)
// import passport from 'koa-passport';
import User from '../src/adapters/localdb/models/users.js'
import Strategy from 'passport-local'
async function passportCallback (email, password, done) {
try {
@@ -49,5 +25,32 @@ async function passportCallback (email, password, done) {
}
}
// For testing
module.exports = { passport, passportCallback }
function applyPassportMods (passport) {
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id, '-password')
done(null, user)
} catch (err) {
done(err)
}
})
passport.use(
'local',
new Strategy(
{
usernameField: 'email',
passwordField: 'password'
},
passportCallback
)
)
return true
}
export { applyPassportMods, passportCallback }
+1 -1
View File
@@ -1,4 +1,4 @@
const Server = require('./bin/server.js')
import Server from './bin/server.js'
const server = new Server()
server.startServer()
+24731 -19021
View File
File diff suppressed because it is too large Load Diff
+26 -17
View File
@@ -1,19 +1,20 @@
{
"name": "ipfs-bch-wallet-consumer",
"version": "1.0.0",
"version": "3.1.1",
"description": "A (local) REST API that proxies BCH wallet services delivered over IPFS (global).",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"test": "npm run test:all",
"test:all": "export CONSUMER_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:all": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export CONSUMER_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
"coverage": "c8 report --reporter=text-lcov | coveralls",
"coverage:report": "export CONSUMER_ENV=test && c8 --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
},
"author": "Chris Troutner <chris.troutner@gmail.com>",
"license": "MIT",
@@ -23,13 +24,24 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
"dependencies": {
"@psf/bch-js": "6.5.1",
"@chainsafe/libp2p-gossipsub": "11.0.1",
"@chainsafe/libp2p-noise": "14.1.0",
"@chainsafe/libp2p-yamux": "6.0.1",
"@helia/unixfs": "3.0.1",
"@libp2p/bootstrap": "10.0.7",
"@libp2p/circuit-relay-v2": "1.0.13",
"@libp2p/identify": "1.0.6",
"@libp2p/keychain": "4.0.7",
"@libp2p/logger": "4.0.5",
"@libp2p/tcp": "9.0.7",
"@libp2p/webrtc": "4.0.17",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"blockstore-fs": "1.1.8",
"datastore-fs": "9.1.6",
"glob": "7.1.6",
"ipfs": "0.62.3",
"ipfs-coord": "8.0.5",
"ipfs-http-client": "55.0.0",
"helia": "2.1.0",
"helia-coord": "1.5.8",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -44,33 +56,30 @@
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"libp2p": "1.2.1",
"line-reader": "0.4.0",
"mime-types": "2.1.35",
"minimal-slp-wallet": "5.11.2",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"public-ip": "6.0.1",
"semver": "7.3.5",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
"devDependencies": {
"apidoc": "0.51.1",
"c8": "7.12.0",
"chai": "4.3.0",
"coveralls": "3.1.0",
"eslint": "7.19.0",
"eslint-config-prettier": "7.2.0",
"eslint-config-standard": "16.0.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "4.3.8",
"lodash.clonedeep": "4.5.0",
"mocha": "10.0.0",
"nyc": "15.1.0",
"semantic-release": "19.0.3",
"sinon": "9.2.4",
"standard": "16.0.3",
"standard": "17.0.0",
"uuid": "8.3.2"
},
"release": {
+8 -7
View File
@@ -12,9 +12,12 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
RUN sudo apt-get install -y ca-certificates curl gnupg
RUN sudo mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
RUN sudo apt-get update
RUN sudo apt-get install -y nodejs build-essential
#Create the user 'safeuser' and add them to the sudo group.
#RUN useradd -ms /bin/bash safeuser
@@ -34,7 +37,7 @@ WORKDIR /home/safeuser
# Update to the latest version of npm.
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
#RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
@@ -63,10 +66,8 @@ RUN npm install
# Generate the API docs
RUN npm run docs
VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
EXPOSE 5010
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
+7 -36
View File
@@ -7,57 +7,28 @@ services:
image: mongo:4.2.0
container_name: mongo-bch-consumer
ports:
- '5555:27017' # <host port>:<container port>
- '5558:27017' # <host port>:<container port>
volumes:
- ../data/database:/data/db
command: mongod --logpath=/dev/null # -- quiet
restart: always
ipfs-bch-consumer:
image: ipfs/go-ipfs:v0.14.0
build: .
#image: christroutner/ipfs-service-provider:v5.0.1
container_name: ipfs-bch-consumer
environment:
MY_ENV_VAR: 'placeholder'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 2000mb
ports:
- 4001:4001
- 4003:4003
- 172.17.0.1:5001:5001
- 172.17.0.1:8080:8080
volumes:
- ../data/go-ipfs/data:/data/ipfs
# - ../../swarm.key:/data/ipfs/swarm.key
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
command:
[
'daemon',
'--migrate=true',
'--agent-version-suffix=docker',
'--enable-pubsub-experiment'
]
restart: always
bch-consumer:
#build: .
image: christroutner/ipfs-bch-wallet-consumer:v1.8.4
container_name: bch-consumer
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 500mb
mem_limit: 4000mb
links:
- mongo-bch-consumer
- ipfs-bch-consumer
ports:
- '5010:5010' # <host port>:<container port>
- '5015:5015' # <host port>:<container port>
- '4101:4101'
- '4103:4103'
volumes:
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
+9 -8
View File
@@ -16,23 +16,24 @@ export COORD_NAME=bch-consumer-generic
# and you must forward that subdomain to the IPFS_WS_PORT.
#export CR_DOMAIN=subdomain.yourdomain.com
# Debug level. 0 = minimal info. 2 = max info.
export DEBUG_LEVEL=1
# END: Optional configuration settings
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
export DBURL=mongodb://172.17.0.1:5558/ipfs-bch-consumer-prod
# Configure REST API port
export PORT=5010
export PORT=5015
# Production settings using external go-ipfs node.
export CONSUMER_ENV=production
export SVC_ENV=prod
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
export IPFS_TCP_PORT=4101
export IPFS_WS_PORT=4103
# Set the debug level for helia-coord. 0-3.
# 0 = no debug logs. 3 = maximum debug logs.
export DEBUG_LEVEL=0
npm start
+4 -1
View File
@@ -13,7 +13,7 @@ export COORD_NAME=trout-dev-bch-wallet-consumer
export MNEMONIC="stomach mean catch march census author cousin size south romance pumpkin trap"
# 0 = less verbose. 3 = most verbose
export DEBUG_LEVEL=1
export DEBUG_LEVEL=0
# Production settings that use external IPFS node.
# https://github.com/christroutner/docker-ipfs
@@ -30,4 +30,7 @@ export DBURL=mongodb://localhost:27017/ipfs-service-dev
export CONSUMER_ENV=production
# Lock the consumer into a preferred service provider.
#export PREFERRED_PROVIDER=12D3KooWEzE3HNH86WCb2Xocu6PgBCnSGUD7myJmuTHvoedMwp7K
npm start
+18 -8
View File
@@ -13,16 +13,24 @@
in the Adapter directory.
*/
'use strict'
const axios = require('axios').default
const mongoose = require('mongoose')
const User = require('../adapters/localdb/models/users')
const config = require('../../config')
const JsonFiles = require('../adapters/json-files')
// Global npm libraries
import axios from 'axios'
import mongoose from 'mongoose'
// Local libraries
import User from '../adapters/localdb/models/users.js'
import config from '../../config/index.js'
import JsonFiles from '../adapters/json-files.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const jsonFiles = new JsonFiles()
const JSON_FILE = `system-user-${config.env}.json`
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}`
const LOCALHOST = `http://localhost:${config.port}`
const context = {}
@@ -82,6 +90,8 @@ class Admin {
// applications like the Task Manager and the test scripts can access.
await jsonFiles.writeJSON(context, JSON_PATH)
// console.log('context: ', context)
// console.log('JSON_PATH: ', JSON_PATH)
return context
} catch (err) {
@@ -169,4 +179,4 @@ class Admin {
}
}
module.exports = Admin
export default Admin
+111 -15
View File
@@ -4,11 +4,11 @@
*/
// Public npm libraries
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
import { v4 as uid } from 'uuid'
import jsonrpc from 'jsonrpc-lite'
// Local libraries
const { wlogger } = require('../wlogger')
import wlogger from '../wlogger.js'
let _this
@@ -102,7 +102,7 @@ class BchAdapter {
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getStatus()')
wlogger.error('Error in adapters/bch.js/getStatus()')
throw err
}
}
@@ -115,7 +115,7 @@ class BchAdapter {
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getStatus()')
wlogger.error('Error in adapters/bch.js/getStatus()')
throw err
}
}
@@ -160,13 +160,17 @@ class BchAdapter {
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getBalances()')
wlogger.error('Error in adapters/bch.js/getBalances()')
throw err
}
}
async getUtxos (addr) {
try {
if (!addr) {
throw new Error('addr required when calling getUtxos')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -203,13 +207,71 @@ class BchAdapter {
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getUtxos()')
wlogger.error('Error in adapters/bch.js/getUtxos()')
throw err
}
}
// Query the UTXOs associated with an array of up to 20 addresses.
async getUtxosBulk (addrs) {
try {
// console.log('getUtxosBulk() addrs: ', addrs)
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
// Input validation
if (!Array.isArray(addrs)) {
throw new Error('addresses parameter must be an array')
}
if (addrs.length > 20) {
throw new Error('addresses parameter must not exceed 20 elements')
}
const rpcData = {
endpoint: 'utxosBulk',
addresses: addrs
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log(`getUtxosBulk() data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/getUtxosBulk()')
throw err
}
}
async broadcast (hex) {
try {
if (!hex) {
throw new Error('hex required when calling broadcast')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -255,6 +317,10 @@ class BchAdapter {
try {
console.log('Executing getTransaction() bch adapter')
if (!address) {
throw new Error('address required when calling getTransactions()')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -265,7 +331,7 @@ class BchAdapter {
const rpcData = {
endpoint: 'txHistory',
address: address,
address,
sortOrder,
page
}
@@ -300,6 +366,10 @@ class BchAdapter {
// Get details on an array of TXIDs.
async getTransaction (txids) {
try {
if (!txids) {
throw new Error('txids required when calling getTransaction()')
}
console.log(
`Getting txData on these txids: ${JSON.stringify(txids, null, 2)}`
)
@@ -347,6 +417,10 @@ class BchAdapter {
async getPubKey (address) {
try {
if (!address) {
throw new Error('address required when calling getPubKey()')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -389,6 +463,10 @@ class BchAdapter {
async utxoIsValid (utxo) {
try {
if (!utxo) {
throw new Error('utxo required when calling utxoIsValid()')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -431,6 +509,10 @@ class BchAdapter {
async getTokenData (tokenId, withTxHistory = false) {
try {
if (!tokenId) {
throw new Error('tokenId required when calling getTokenData()')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -473,8 +555,12 @@ class BchAdapter {
}
// Get token icon and other media associated with the token.
async getTokenData2 (tokenId) {
async getTokenData2 (tokenId, updateCache) {
try {
if (!tokenId) {
throw new Error('tokenId required when calling getTokenData2()')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -485,7 +571,8 @@ class BchAdapter {
const rpcData = {
endpoint: 'getTokenData2',
tokenId
tokenId,
updateCache
}
// Generate a UUID for the call.
@@ -518,6 +605,10 @@ class BchAdapter {
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
if (!rpcId) {
throw new Error('rpcId can not be false or undefined')
}
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
@@ -553,21 +644,26 @@ class BchAdapter {
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
await this.sleep(2000)
// await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
console.error('Error in waitForRPCResponse(): ', err)
throw err
}
}
sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
module.exports = BchAdapter
// module.exports = BchAdapter
export default BchAdapter
+5 -6
View File
@@ -3,13 +3,12 @@
*/
/* eslint-disable no-useless-escape */
import config from '../../config/index.js'
import NodeMailer from '../adapters/nodemailer.js'
import wlogger from '../adapters/wlogger.js'
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
const config = require('../../config')
const NodeMailer = require('../adapters/nodemailer')
const nodemailer = new NodeMailer()
const { wlogger } = require('../adapters/wlogger')
let _this
@@ -60,4 +59,4 @@ class ContactLib {
}
}
}
module.exports = ContactLib
export default ContactLib
+4 -3
View File
@@ -5,8 +5,9 @@
for this file.
*/
const JwtLib = require('jwt-bch-lib')
const BCHJS = require('@psf/bch-js')
import JwtLib from 'jwt-bch-lib'
import BCHJS from '@psf/bch-js'
class FullStackJWT {
constructor (localConfig = {}) {
@@ -98,4 +99,4 @@ class FullStackJWT {
}
}
module.exports = FullStackJWT
export default FullStackJWT
+32 -15
View File
@@ -5,21 +5,23 @@
*/
// Public NPM libraries
const BCHJS = require('@psf/bch-js')
import BCHJS from '@psf/bch-js'
// Load individual adapter libraries.
const IPFSAdapter = require('./ipfs')
const LocalDB = require('./localdb')
const LogsAPI = require('./logapi')
const Passport = require('./passport')
const Nodemailer = require('./nodemailer')
// const { wlogger } = require('./wlogger')
const JSONFiles = require('./json-files')
const FullStackJWT = require('./fullstack-jwt')
const BCH = require('./bch')
const P2WDB = require('./p2wdb')
import IPFSAdapter from './ipfs/index.js'
import LocalDB from './localdb/index.js'
import LogsAPI from './logapi.js'
import Passport from './passport.js'
import Nodemailer from './nodemailer.js'
import BCH from './bch/index.js'
import P2WDB from './p2wdb/index.js'
import IpfsFiles from './ipfs-files/index.js'
const config = require('../../config')
// const { wlogger } = require('./wlogger')
import JSONFiles from './json-files.js'
import FullStackJWT from './fullstack-jwt.js'
import config from '../../config/index.js'
import Wallet from './wallet.adapter.js'
class Adapters {
constructor (localConfig = {}) {
@@ -43,6 +45,8 @@ class Adapters {
this.config = config
this.bch = new BCH(localConfig)
this.p2wdb = new P2WDB(localConfig)
this.wallet = new Wallet(localConfig)
this.ipfsFiles = new IpfsFiles(localConfig)
// Get a valid JWT API key and instance bch-js.
this.fullStackJwt = new FullStackJWT(config)
@@ -50,18 +54,31 @@ class Adapters {
async start () {
try {
let apiToken
if (this.config.getJwtAtStartup) {
// Get a JWT token and instantiate bch-js with it. Then pass that instance
// to all the rest of the apps controllers and adapters.
await this.fullStackJwt.getJWT()
apiToken = await this.fullStackJwt.getJWT()
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
this.bchjs = await this.fullStackJwt.instanceBchjs()
}
// Create a default instance of minimal-slp-wallet without initializing it
// (without retrieving the wallets UTXOs). This instance will be overwritten
// if the operator has configured BCH payments.
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
this.bchjs = this.wallet.bchWallet.bchjs
// Start the IPFS node.
// Do not start these adapters if this is an e2e test.
if (this.config.env !== 'test') {
await this.ipfs.start()
if (this.config.useIpfs) {
await this.ipfs.start()
}
} else {
// These lines are here to ensure code coverage hits 100%.
console.log('Not starting IPFS node since this is an e2e test.')
}
console.log('Async Adapters have been started.')
@@ -74,4 +91,4 @@ class Adapters {
}
}
module.exports = Adapters
export default Adapters
+274
View File
@@ -0,0 +1,274 @@
/*
This library contains code for interfacing to the ipfs-file-pin-service using
JSON RPC over IPFS. Most of these functions are called by the /files REST API
endpoints and the IPFS Use Cases library.
*/
// Public npm libraries
import { v4 as uid } from 'uuid'
import jsonrpc from 'jsonrpc-lite'
// Local libraries
import wlogger from '../wlogger.js'
// let _this
class IpfsFilesAdapter {
constructor (localConfig = {}) {
// console.log('BCH localConfig: ', localConfig)
this.ipfs = localConfig.ipfs
if (!this.ipfs) {
throw new Error(
'An instance of IPFS must be passed when instantiating the IPFS Files Adapter library.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Connect the RPC handler when the event fires with new data.
// this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
this.jsonrpc = jsonrpc
// A queue for holding RPC data that has arrived.
this.rpcDataQueue = []
// _this = this // Global handle on instance of this Class.
// Bind 'this' object to all subfunctions for this Class
this.rpcHandler = this.rpcHandler.bind(this)
this.getStatus = this.getStatus.bind(this)
this.selectProvider = this.selectProvider.bind(this)
}
// This handler is triggered when RPC data comes in over IPFS.
// Handle RPC input, and add the response to the RPC queue.
// Once in the queue, it will get processed by waitForRPCResponse()
rpcHandler (data) {
try {
// Convert string input into an object.
// const jsonData = JSON.parse(data)
// console.log(`JSON RPC response for ID ${data.payload.id} received.`)
this.rpcDataQueue.push(data)
} catch (err) {
console.error('Error in files/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
}
}
// Get the status of BCH wallet services this node can talk to. Returns an
// array of BCH wallet service providers.
async getStatus () {
try {
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
// const status = {
// state: this.ipfs.ipfsCoordAdapter.state
// }
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
const providerData = peerData.filter((x) => x.from === thisProvider)
if (providerData.length) {
const provObj = {
ipfsId: thisProvider,
name: providerData[0].data.jsonLd.name
}
serviceProviders.push(provObj)
}
}
// console.log(
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
// )
const outObj = {
serviceProviders,
selectedProvider: this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
}
// console.log('outObj: ', outObj)
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/getStatus()')
throw err
}
}
// Choose the ipfs-file-pin-service to use.
async selectProvider (providerId) {
try {
this.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider = providerId
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/getStatus()')
throw err
}
}
// Given a CID, query the ipfs-file-pin-service to get the file metadata, like
// the file name.
async getFileMetadata (inObj = {}) {
try {
const { cid } = inObj
// Input validation.
if (!cid || typeof cid !== 'string') {
throw new Error('getFileMetadata() cid input hash must be a string.')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const rpcData = {
endpoint: 'getFileMetadata',
cid
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getFileMetadata()')
throw err
}
}
// Get an array of the latest 20 pinned files.
async getPins (inObj = {}) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const rpcData = {
endpoint: 'getPins'
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getPins()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
// Default return value, if the remote computer does not respond in time.
let data = {
success: false,
message: 'request timed out',
data: ''
}
// Loop that waits for a response from the service provider.
do {
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
for (let i = 0; i < this.rpcDataQueue.length; i++) {
const rawData = this.rpcDataQueue[i]
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
if (rawData.payload.id === rpcId) {
dataFound = true
// console.log('data was found in the queue')
data = rawData.payload.result.value
// Remove the data from the queue
this.rpcDataQueue.splice(i, 1)
break
}
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfs.ipfsCoordAdapter.wallet.bchjs.Util.sleep(2000)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
throw err
}
}
}
// module.exports = P2wdbAdapter
export default IpfsFilesAdapter
+39 -28
View File
@@ -3,9 +3,10 @@
*/
// Local libraries
const IpfsAdapter = require('./ipfs')
const IpfsCoordAdapter = require('./ipfs-coord')
const config = require('../../../config')
import IpfsAdapter from './ipfs.js'
import IpfsCoordAdapter from './ipfs-coord.js'
import config from '../../../config/index.js'
class IPFS {
constructor (localConfig = {}) {
@@ -67,13 +68,10 @@ class IPFS {
}
// Get the status of this IPFS node.
// Create an object with different metrics about this IPFS node, then return
// the object.
getStatus () {
try {
// console.log(
// 'this.ipfsCoordAdapter.ipfsCoord.thisNode: ',
// this.ipfsCoordAdapter.ipfsCoord.thisNode
// )
const statusObj = {
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
@@ -104,25 +102,30 @@ class IPFS {
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
// console.log('filtered ipfsPeers: ', ipfsPeers)
const peerDataOut = []
// Loop through each IPFS peer and hydrate it with data from the peerData.
for (let i = 0; i < ipfsPeers.length; i++) {
const thisPeer = ipfsPeers[i]
if (!showAll) {
// Delete properties that don't contain good info.
delete thisPeer.muxer
delete thisPeer.latency
delete thisPeer.streams
const thisPeer = {
peer: ipfsPeers[i]
}
// if (!showAll) {
// // Delete properties that don't contain good info.
// delete thisPeer.muxer
// delete thisPeer.latency
// delete thisPeer.streams
// }
// Get the ipfs-coord peer data for this peer.
let thisPeerData = peerData.filter((x) =>
x.from.includes(thisPeer.peer)
)
thisPeerData = thisPeerData[0]
// console.log('thisPeerData: ', thisPeerData)
// Skip if peerData for this IPFS peer could not be found.
if (!thisPeerData) continue
// if (!thisPeerData) continue
try {
// console.log('thisPeerData: ', thisPeerData)
@@ -131,25 +134,36 @@ class IPFS {
thisPeer.name = thisPeerData.data.jsonLd.name
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
thisPeer.version = thisPeerData.data.jsonLd.version
thisPeer.connectionAddr = thisPeerData.data.connectionAddr
if (showAll) {
// Add all the peer data.
thisPeer.peerData = thisPeerData
}
} catch (err) {
console.log(
`Error trying to hydrate peer ${thisPeer.peer}: ${err.message}`
console.warn(
`warning: ${thisPeer.peer}: ${err.message}`
)
}
peerDataOut.push(thisPeer)
}
return ipfsPeers
return peerDataOut
} catch (err) {
console.error('Error in getPeers(): ', err)
throw err
}
}
// Expects an array of peers and returns an array of peers with duplicates
// removed.
// This function is used by getPeers()
_removeDuplicatePeers (arr) {
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
return arr.filter((v, i, a) => a.findIndex((t) => t === v) === i)
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
getRelays () {
try {
@@ -170,25 +184,22 @@ class IPFS {
// If the peer couldn't be found, skip.
if (!thisPeer.length) {
thisRelay.name = ''
thisRelay.description = ''
continue
}
thisRelay.name = thisPeer[0].data.jsonLd.name
thisRelay.description = thisPeer[0].data.jsonLd.description
}
return relayData
const v1Relays = this.config.v1Relays
return { v2Relays: relayData, v1Relays }
} catch (err) {
console.error('Error in getRelays(): ', err)
throw err
}
}
// Expects an array of peers and returns an array of peers with duplicates
// removed.
_removeDuplicatePeers (arr) {
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
return arr.filter((v, i, a) => a.findIndex((t) => t.peer === v.peer) === i)
}
}
module.exports = IPFS
export default IPFS
+143 -19
View File
@@ -5,20 +5,26 @@
*/
// Global npm libraries
const IpfsCoord = require('ipfs-coord')
const BCHJS = require('@psf/bch-js')
const publicIp = require('public-ip')
const semver = require('semver')
// import IpfsCoord from 'ipfs-coord-esm'
import IpfsCoord from 'helia-coord'
// import BCHJS from '@psf/bch-js'
import SlpWallet from 'minimal-slp-wallet'
import semver from 'semver'
import { publicIpv4 } from 'public-ip'
// Local libraries
const config = require('../../../config')
import config from '../../../config/index.js'
// const JSONRPC = require('../../controllers/json-rpc/')
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
const MIN_BCH_WALLET_VERSION = '1.11.11'
const WALLET_PROTOCOL = 'bch-wallet'
const WALLET_PROTOCOL = 'bch-wallet-service'
const MIN_P2WDB_VERSION = '1.4.0'
const P2WDB_PROTOCOL = 'p2wdb'
const MIN_FILE_PIN_VERSION = '1.0.0'
const FILE_PIN_PROTOCOL = 'ipfs-file-pin-service'
let _this
@@ -41,19 +47,24 @@ class IpfsCoordAdapter {
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
this.ipfsCoord = {}
this.bchjs = new BCHJS()
// this.bchjs = new BCHJS()
this.wallet = new SlpWallet()
this.config = config
this.publicIp = publicIp
this.semver = semver
this.publicIp = publicIpv4
// Properties of this class instance.
this.isReady = false
// Periodically poll services for available wallet service providers.
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
this.pollIpfsFileServiceInterval = setInterval(
this.pollForIpfsFileServices,
11000
)
this.pollP2wdbServiceInterval = setInterval(
this.pollForP2wdbServices,
11000
12000
)
// State object. TODO: Make this more robust.
@@ -61,7 +72,9 @@ class IpfsCoordAdapter {
serviceProviders: [],
selectedServiceProvider: '',
p2wdbProviders: [],
selectedP2wdbProvider: ''
selectedP2wdbProvider: '',
ipfsFileProviders: [],
selectedIpfsFileProvider: ''
}
_this = this
@@ -70,10 +83,13 @@ class IpfsCoordAdapter {
async start () {
const circuitRelayInfo = {}
// Wait for the BCH wallet to create the wallet.
await this.wallet.walletInfoPromise
// If configured as a Circuit Relay, get the public IP addresses for this node.
if (this.config.isCircuitRelay) {
try {
const ip4 = await this.publicIp.v4()
const ip4 = await this.publicIp()
// const ip6 = await publicIp.v6()
circuitRelayInfo.ip4 = ip4
@@ -87,23 +103,28 @@ class IpfsCoordAdapter {
}
}
const nullLog = () => {}
const ipfsCoordOptions = {
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
bchjs: this.bchjs,
privateLog: this.peerInputHandler,
wallet: this.wallet,
// privateLog: console.log, // Default to console.log
privateLog: nullLog,
isCircuitRelay: this.config.isCircuitRelay,
circuitRelayInfo,
apiInfo: this.config.apiInfo,
announceJsonLd: this.config.announceJsonLd,
debugLevel: this.config.debugLevel
debugLevel: this.config.debugLevel,
v1Relays: this.config.v1Relays,
tcpPort: this.config.ipfsTcpPort
}
// Production env uses external go-ipfs node.
if (this.config.isProduction) {
ipfsCoordOptions.nodeType = 'external'
}
// if (this.config.isProduction) {
// ipfsCoordOptions.nodeType = 'external'
// }
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
@@ -214,6 +235,8 @@ class IpfsCoordAdapter {
// providers. This method is called periodically by a timer-interval.
pollForBchServices () {
try {
// console.log('pollForBchServices() polling for BCH service')
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
@@ -255,15 +278,23 @@ class IpfsCoordAdapter {
console.log(`---->BCH wallet service selected: ${thisPeer}`)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredProvider &&
thisPeer === _this.config.preferredProvider
) {
if (_this.state.selectedServiceProvider !== thisPeer) {
console.log(`---->BCH wallet service switched to preferred peer: ${thisPeer}`)
}
_this.state.selectedServiceProvider = thisPeer
}
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.serviceProviders.filter(
(x) => x === thisPeer
@@ -289,6 +320,96 @@ class IpfsCoordAdapter {
}
}
// Poll the ipfs-coord coordination channel for available ipfs-file-pin-service
// providers. This method is called periodically by a timer-interval.
pollForIpfsFileServices () {
try {
// console.log('pollForIpfsFileServices() polling for BCH service')
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
// Array of objects. Each object is the IPFS ID of the peer and contains
// data about that peer.
const peerData = _this.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < peers.length; i++) {
const thisPeer = peers[i]
const thisData = peerData.filter((x) => x.from === thisPeer)
const thisPeerData = thisData[0]
// Create a 'fingerprint' that defines the wallet service.
const protocol = thisPeerData.data.jsonLd.protocol
const version = thisPeerData.data.jsonLd.version
// console.log(
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`,
// )
let versionMatches = false
if (version) {
// versionMatches = _this.semver.gt(version, MIN_FILE_PIN_VERSION)
versionMatches = _this.semver.satisfies(version, `>=${MIN_FILE_PIN_VERSION}`)
}
// Ignore any peers that don't match the fingerprint for a BCH wallet
// service.
if (protocol && protocol.includes(FILE_PIN_PROTOCOL) && versionMatches) {
// console.log('Matching peer: ', thisPeerData)
// Temporary business logic.
// Use the first available wallet service detected.
if (_this.state.ipfsFileProviders.length === 0) {
_this.state.selectedIpfsFileProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->IPFS File service selected: ${thisPeer}`)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredIpfsFileProvider &&
thisPeer === _this.config.preferredIpfsFileProvider
) {
if (_this.state.selectedIpfsFileProvider !== thisPeer) {
console.log(`---->IPFS File service switched to preferred peer: ${thisPeer}`)
}
_this.state.selectedIpfsFileProvider = thisPeer
}
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.ipfsFileProviders.filter(
(x) => x === thisPeer
)
// Add the peer to the list of serviceProviders if it doesn't already
// exist in the list.
if (!alreadyExists.length) {
_this.state.ipfsFileProviders.push(thisPeer)
}
}
}
} catch (err) {
// catch and handle known failure mode.
if (
err.message.includes("Cannot read property 'peerList' of undefined")
) {
return
}
console.error('Error in pollForIpfsFileServices(): ', err)
// Do not throw error. This is a top-level function.
}
}
// This method handles input coming in from other IPFS peers.
// It passes the data on to the REST API library by emitting an event.
// peerInputHandler (data) {
@@ -304,12 +425,15 @@ class IpfsCoordAdapter {
// Subscribe to the chat pubsub channel
async subscribeToChat () {
// TODO: Allow user to replace nullog with their own log handler at startup.
const nullLog = () => {}
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
this.config.chatPubSubChan,
console.log,
nullLog,
this.ipfsCoord.thisNode
)
}
}
module.exports = IpfsCoordAdapter
export default IpfsCoordAdapter
+235 -82
View File
@@ -9,94 +9,95 @@
*/
// Global npm libraries
// const IPFS = require('ipfs')
// const IPFS = require('@chris.troutner/ipfs')
const IPFSembedded = require('ipfs')
const IPFSexternal = require('ipfs-http-client')
const fs = require('fs')
const http = require('http')
import { createHelia } from 'helia'
import fs from 'fs'
import { FsBlockstore } from 'blockstore-fs'
import { FsDatastore } from 'datastore-fs'
import { createLibp2p } from 'libp2p'
import { tcp } from '@libp2p/tcp'
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
// import { bootstrap } from '@libp2p/bootstrap'
// import { identifyService } from 'libp2p/identify'
import { identify } from '@libp2p/identify'
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
import { webSockets } from '@libp2p/websockets'
import { publicIpv4 } from 'public-ip'
import { multiaddr } from '@multiformats/multiaddr'
import { webRTC } from '@libp2p/webrtc'
import { keychain } from '@libp2p/keychain'
import { defaultLogger } from '@libp2p/logger'
import { unixfs } from '@helia/unixfs'
// Local libraries
const config = require('../../../config')
import config from '../../../config/index.js'
import JsonFiles from '../json-files.js'
const IPFS_DIR = './.ipfsdata/ipfs'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// console.log('__dirname: ', __dirname)
const ROOT_DIR = `${__dirname}../../../`
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
this.config = config
// Choose the IPFS constructor based on the config settings.
this.IPFS = IPFSembedded // default
if (this.config.isProduction) {
this.IPFS = IPFSexternal
}
this.fs = fs
this.createLibp2p = createLibp2p
this.publicIp = publicIpv4
this.multiaddr = multiaddr
this.jsonFiles = new JsonFiles()
this.keychain = keychain
this.createHelia = createHelia
// Properties of this class instance.
this.isReady = false
this.fs = fs
// Bind 'this' object to all subfunctions
this.start = this.start.bind(this)
this.createNode = this.createNode.bind(this)
this.stop = this.stop.bind(this)
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
this.getSeed = this.getSeed.bind(this)
this.getKeychain = this.getKeychain.bind(this)
}
// Start an IPFS node.
async start () {
try {
// Ipfs Options
const ipfsOptionsEmbedded = {
repo: IPFS_DIR,
start: true,
config: {
relay: {
enabled: true, // enable circuit relay dialer and listener
hop: {
enabled: config.isCircuitRelay // enable circuit relay HOP (make this node a relay)
}
},
pubsub: true, // enable pubsub
Swarm: {
ConnMgr: {
HighWater: 30,
LowWater: 10
}
},
Addresses: {
Swarm: [
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
]
},
Datastore: {
StorageMax: '2GB',
StorageGCWatermark: 50,
GCPeriod: '15m'
}
}
}
// Ensure the directory structure exists that is needed by the IPFS node to store data.
this.ensureBlocksDir()
const ipfsOptionsExternal = {
host: this.config.ipfsHost,
port: this.config.ipfsApiPort,
agent: http.Agent({ keepAlive: true, maxSockets: 200 })
}
// Create an IPFS node
const ipfs = await this.createNode()
// console.log('ipfs: ', ipfs)
let ipfsOptions = ipfsOptionsEmbedded
if (this.config.isProduction) {
ipfsOptions = ipfsOptionsExternal
}
this.id = ipfs.libp2p.peerId.toString()
console.log('IPFS ID: ', this.id)
// Create a new IPFS node.
this.ipfs = await this.IPFS.create(ipfsOptions)
// Attempt to guess our ip4 IP address.
const ip4 = await this.publicIp()
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
// Set the 'server' profile so the node does not scan private networks.
await this.ipfs.config.profiles.apply('server')
// Get the multiaddrs for the node.
const multiaddrs = ipfs.libp2p.getMultiaddrs()
multiaddrs.push(detectedMultiaddr)
console.log('Multiaddrs: ', multiaddrs)
// Debugging: Display IPFS config settings.
// const configSettings = await this.ipfs.config.getAll()
// console.log(`configSettings: ${JSON.stringify(configSettings, null, 2)}`)
this.multiaddrs = multiaddrs
// Signal that this adapter is ready.
this.isReady = true
this.ipfs = ipfs
return this.ipfs
} catch (err) {
console.error('Error in ipfs.js/start()')
@@ -110,31 +111,183 @@ class IpfsAdapter {
}
}
async getKeychain (datastore) {
const keychainInit = {
pass: await this.getSeed()
}
const chain = this.keychain(keychainInit)({
datastore,
logger: defaultLogger()
})
return chain
}
// This function creates an IPFS node using Helia.
// It returns the node as an object.
async createNode () {
try {
// Create block and data stores.
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
// const keychainInit = {
// pass: await this.getSeed()
// }
// Create an identity
let peerId
// console.log('this.keychain: ', this.keychain)
// const chain = this.keychain(keychainInit)({
// datastore,
// logger: defaultLogger()
// })
const chain = await this.getKeychain(datastore)
try {
peerId = await chain.exportPeerId('myKey')
} catch (err) {
await chain.createKey('myKey', 'Ed25519', 4096)
peerId = await chain.exportPeerId('myKey')
}
// Configure services
const services = {
identify: identify(),
pubsub: gossipsub({ allowPublishToZeroPeers: true })
}
if (this.config.isCircuitRelay) {
console.log('Helia (IPFS) node IS configured as Circuit Relay')
services.relay = circuitRelayServer({ // makes the node function as a relay server
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
advertise: true,
reservations: {
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
}
})
} else {
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
}
// Configure transports
let transports
if (process.env.CONNECT_PREF === 'direct') {
transports = [
tcp(),
webSockets(),
webRTC()
]
} else {
transports = [
tcp(),
webSockets(),
circuitRelayTransport({
discoverRelays: 3,
reservationConcurrency: 3
}),
webRTC()
]
}
// libp2p is the networking layer that underpins Helia
const libp2p = await this.createLibp2p({
peerId,
datastore,
addresses: {
listen: [
'/ip4/127.0.0.1/tcp/0',
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
'/webrtc'
]
},
transports,
connectionEncryption: [
noise()
],
streamMuxers: [
yamux()
],
services
})
// create a Helia node
const helia = await this.createHelia({
blockstore,
datastore,
libp2p
})
// Attach IPFS file system.
const fs = unixfs(helia)
helia.fs = fs
return helia
} catch (err) {
console.error('Error creating Helia node: ', err)
throw err
}
}
async stop () {
await this.ipfs.stop()
return true
}
// Remove the '/blocks' directory that is used to store IPFS data.
// Dev Note: It's assumed this node is not pinning any data and that
// everything in this directory is transient. This folder will regularly
// fill up and prevent IPFS from starting.
// rmBlocksDir () {
// try {
// const dir = `${IPFS_DIR}/blocks`
// console.log(`Deleting ${dir} directory...`)
//
// this.fs.rmdirSync(dir, { recursive: true })
//
// console.log(`${dir} directory is deleted!`)
//
// return true // Signal successful execution.
// } catch (err) {
// console.log('Error in rmBlocksDir()')
// throw err
// }
// }
// Ensure that the directories exist to store blocks from the IPFS network.
// This function is called at startup, before the IPFS node is started.
ensureBlocksDir () {
try {
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
return true
} catch (err) {
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
throw err
}
}
// This function opens the seed used to generate the key for this IPFS peer.
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
async getSeed () {
try {
let seed
const filename = `${IPFS_DIR}/seed.json`
try {
// Try to read the JSON file containing the seed.
seed = await this.jsonFiles.readJSON(filename)
} catch (err) {
const seedNum = Math.floor(Math.random() * 1000000000000000000000)
seed = seedNum.toString()
// Save the newly generated seed
await this.jsonFiles.writeJSON(seed, filename)
}
// console.log('getSeed() seed: ', seed)
return seed
} catch (err) {
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
throw err
}
}
}
module.exports = IpfsAdapter
export default IpfsAdapter
+5 -4
View File
@@ -1,8 +1,8 @@
/*
A utility file for reading and writing JSON files.
*/
'use strict'
const fs = require('fs')
import fs from 'fs'
let _this
@@ -57,7 +57,8 @@ class JsonFiles {
console.log(`err: ${JSON.stringify(err, null, 2)}`)
}
throw err
// throw err
return reject(err)
}
const obj = JSON.parse(data)
@@ -72,4 +73,4 @@ class JsonFiles {
}
}
module.exports = JsonFiles
export default JsonFiles
+2 -2
View File
@@ -3,7 +3,7 @@
*/
// Load Mongoose models.
const Users = require('./models/users')
import Users from './models/users.js'
class LocalDB {
constructor () {
@@ -12,4 +12,4 @@ class LocalDB {
}
}
module.exports = LocalDB
export default LocalDB
+8 -5
View File
@@ -1,7 +1,10 @@
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const config = require('../../../../config')
const jwt = require('jsonwebtoken')
// Global npm libraries
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'
// Local libraries
import config from '../../../../config/index.js'
const User = new mongoose.Schema({
type: { type: String, default: 'user' },
@@ -51,4 +54,4 @@ User.methods.generateToken = function generateToken () {
}
// export default mongoose.model('user', User)
module.exports = mongoose.model('user', User)
export default mongoose.model('user', User)
+9 -4
View File
@@ -1,9 +1,14 @@
const lineReader = require('line-reader')
const fs = require('fs')
import lineReader from 'line-reader'
import fs from 'fs'
const config = require('../../config')
import config from '../../config/index.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
let _this
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
class LogsApi {
constructor () {
@@ -169,4 +174,4 @@ class LogsApi {
}
}
module.exports = LogsApi
export default LogsApi
+4 -5
View File
@@ -3,11 +3,10 @@
*/
'use strict'
const nodemailer = require('nodemailer')
import nodemailer from 'nodemailer'
const config = require('../../config')
const { wlogger } = require('./wlogger')
import config from '../../config/index.js'
import wlogger from './wlogger.js'
let _this
@@ -150,4 +149,4 @@ class NodeMailer {
}
}
module.exports = NodeMailer
export default NodeMailer
+5 -4
View File
@@ -4,11 +4,11 @@
*/
// Public npm libraries
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
import { v4 as uid } from 'uuid'
import jsonrpc from 'jsonrpc-lite'
// Local libraries
const { wlogger } = require('../wlogger')
import wlogger from '../wlogger.js'
let _this
@@ -264,4 +264,5 @@ class P2wdbAdapter {
}
}
module.exports = P2wdbAdapter
// module.exports = P2wdbAdapter
export default P2wdbAdapter
+2 -2
View File
@@ -2,7 +2,7 @@
koa-passport is an authorization library used for different authentication schemes.
*/
const passport = require('koa-passport')
import passport from 'koa-passport'
let _this
class Passport {
@@ -32,4 +32,4 @@ class Passport {
}
}
module.exports = Passport
export default Passport
+244
View File
@@ -0,0 +1,244 @@
/*
Wallet adapter. This library instantiates minimal-slp-wallet based on different
configuration settings. That single instance of minimal-slp-wallet is then
easily passed around the rest of this application.
*/
// Global npm libraries
import BchWallet from 'minimal-slp-wallet'
// Local libraries
import config from '../../config/index.js'
import JsonFiles from './json-files.js'
class Wallet {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.config = config
this.BchWallet = BchWallet
this.jsonFiles = new JsonFiles()
// Bind 'this' object to all subfunctions
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
}
// This is used for initializing the wallet, without waiting to update the wallet
// UTXOs from the blockchain.
// This is useful when the wallet is simply needed to make calls to the blockchain,
// and there is no need to hydrate it with UTXO data.
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
try {
// Use the apiToken from the config settings, if one is not passed-in at
// run-time.
if (!advancedConfig.apiToken) {
advancedConfig.apiToken = this.config.apiToken
}
// If an authentication password is passed in the config, add it.
if (this.config.authPass) {
advancedConfig.authPass = this.config.authPass
}
// Detect and configure different blockchain infrastructure settings.
if (this.config.walletInterface === 'web2') {
advancedConfig.interface = 'rest-api'
advancedConfig.restURL = this.config.apiServer
} else {
// By default use the web3 Cash Stack (https://cashstack.info)
advancedConfig.interface = 'consumer-api'
advancedConfig.restURL = this.config.apiServer
}
// console.log('advancedConfig setting when creating wallet: ', advancedConfig)
// Instantiate minimal-slp-wallet.
if (walletData.mnemonic) {
// Instance the wallet using the mnemonic passed in to this function.
console.log('Mnemonic provided to wallet library from calling function.')
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
} else if (this.config.mnemonic) {
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
console.log('Mnemonic retrieved from config file or environment variable.')
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
} else {
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
console.log('New mnemonic generated.')
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
}
// Wait for wallet to initialize.
await this.bchWallet.walletInfoPromise
console.log('BCH wallet initialized.')
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
return this.bchWallet
} catch (err) {
console.error('Error in instanceWalletWithoutInitialization()')
throw err
}
}
// This function is used for easier mocking of unit tests.
async _instanceWallet (mnemonic, config) {
const wallet = new this.BchWallet(mnemonic, config)
await wallet.walletInfoPromise
return wallet
}
// Open the wallet file, or create one if the file doesn't exist.
// Does not instance the wallet. The output of this function is expected to
// be passed to instanceWallet().
async openWallet (advancedConfig = {}) {
try {
let walletData
// Try to open the wallet.json file.
try {
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
walletData = walletData.wallet
} catch (err) {
// Create a new wallet file if one does not already exist.
console.log('Wallet file not found. Creating new wallet.json file.')
// Create a new wallet.
// No-Update flag creates wallet without making any network calls.
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
walletData = walletInstance.walletInfo
// Add the nextAddress property
walletData.nextAddress = 1
// Write the wallet data to the JSON file.
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
}
// console.log('walletData: ', walletData)
return walletData
} catch (err) {
console.error('Error in openWallet()')
throw err
}
}
// Create an instance of minimal-slp-wallet. Same as
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
// its UTXOs (wallet balance and tokens).
async instanceWallet (walletData = {}, advancedConfig = {}) {
try {
// Instance the wallet without initialization.
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
// Initialize the wallet
await this.bchWallet.initialize()
return this.bchWallet
} catch (err) {
console.error('Error in wallet.js/instanceWallet()')
throw err
}
}
// Increments the 'nextAddress' property in the wallet file. This property
// indicates the HD index that should be used to generate a key pair for
// storing funds for Offers.
// This function opens the wallet file, increments the nextAddress property,
// then saves the change to the wallet file.
async incrementNextAddress () {
try {
const walletData = await this.openWallet()
await this.instanceWalletWithoutInitialization(walletData)
walletData.nextAddress++
// console.log('walletData finish: ', walletData)
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
// Update the working instance of the wallet.
this.bchWallet.walletInfo.nextAddress++
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
return walletData.nextAddress
} catch (err) {
console.error('Error in incrementNextAddress()')
throw err
}
}
// This method returns an object that contains a private key WIF, public address,
// and the index of the HD wallet that the key pair was generated from.
// TODO: Allow input integer. If input is used, use that as the index. If no
// input is provided, then call incrementNextAddress().
async getKeyPair (hdIndex = 0) {
try {
if (!hdIndex) {
// Increment the HD index and generate a new key pair.
hdIndex = await this.incrementNextAddress()
}
const mnemonic = this.bchWallet.walletInfo.mnemonic
// root seed buffer
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
// HDNode of BIP44 account
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
console.log('Generating a new key pair for cashAddress: ', cashAddress)
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
const outObj = {
cashAddress,
wif,
hdIndex
}
return outObj
} catch (err) {
console.error('Error in getKeyPair()')
throw err
}
}
// Optimize the wallet by consolidating the UTXOs.
async optimize () {
const UTXO_THREASHOLD = 7
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
const dryRunOut = await this.bchWallet.optimize(true)
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
// Consolidate BCH UTXOs if the count is above the threashold.
const txids = await this.bchWallet.optimize()
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
}
return true
}
// Get the balance of the wallet in sats and PSF tokens.
// This function is called by the GET /entry/balance controller.
async getBalance () {
const balance = await this.bchWallet.getBalance()
// console.log('balance: ', balance)
const tokens = await this.bchWallet.listTokens()
// console.log('tokens: ', tokens)
// Find the array entry for the PSF token
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
// console.log('psfTokens: ', psfTokens)
let psfBalance = 0
if (psfTokens) {
psfBalance = psfTokens.qty
}
const outObj = {
satBalance: balance,
psfBalance,
success: true
}
return outObj
}
}
export default Wallet
+12 -4
View File
@@ -6,10 +6,17 @@
'use strict'
const winston = require('winston')
require('winston-daily-rotate-file')
// Global npm libraries
import winston from 'winston'
import 'winston-daily-rotate-file'
const config = require('../../config')
// Local libraries
import config from '../../config/index.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
class Wlogger {
constructor (localConfig = {}) {
@@ -69,4 +76,5 @@ logger.outputToConsole()
const wlogger = logger.wlogger
module.exports = { wlogger, Wlogger }
export { wlogger as default, Wlogger }
// export default wlogger
+15 -16
View File
@@ -8,25 +8,21 @@
// const EventEmitter = require('events')
// Local libraries
const config = require('../../config')
const Adapters = require('../adapters')
const JSONRPC = require('./json-rpc')
const UseCases = require('../use-cases')
const RESTControllers = require('./rest-api')
import Adapters from '../adapters/index.js'
import JSONRPC from './json-rpc/index.js'
import UseCases from '../use-cases/index.js'
import RESTControllers from './rest-api/index.js'
import TimerControllers from './timer-controllers.js'
import config from '../../config/index.js'
class Controllers {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.config = config
// Initialize EventEmitters used to pass event-driven data around the app.
// this.eventEmitter = new EventEmitter()
// localConfig.eventEmitter = this.eventEmitter
// Initialize Clean Architecture classes. Inject dependencies.
this.adapters = new Adapters(localConfig)
localConfig.adapters = this.adapters
this.useCases = new UseCases(localConfig)
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
this.config = config
}
// Spin up any adapter libraries that have async startup needs.
@@ -56,10 +52,13 @@ class Controllers {
// Wait for any startup processes to complete for the Adapters libraries.
// await this.adapters.start()
// Attach the REST controllers to the Koa app.
// this.attachRESTControllers(app)
if (this.config.useIpfs) {
// Attach JSON RPC controllers
this.attachRPCControllers()
}
this.attachRPCControllers()
// Attach and start the timer controllers
this.timerControllers.startTimers()
}
// Add the JSON RPC router to the ipfs-coord adapter.
@@ -76,4 +75,4 @@ class Controllers {
}
}
module.exports = Controllers
export default Controllers
+3 -3
View File
@@ -6,10 +6,10 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
import jsonrpc from 'jsonrpc-lite'
// Local libraries
const config = require('../../../../config')
import config from '../../../../config/index.js'
class AboutRPC {
constructor (localConfig) {
@@ -48,4 +48,4 @@ class AboutRPC {
}
}
module.exports = AboutRPC
export default AboutRPC
+5 -4
View File
@@ -3,13 +3,14 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
import jsonrpc from 'jsonrpc-lite'
// Local libraries
// const AuthLib = require('../../lib/auth')
// const UserLib = require('../../../use-cases/user')
const { wlogger } = require('../../../adapters/wlogger')
const RateLimit = require('../rate-limit')
import wlogger from '../../../adapters/wlogger.js'
import RateLimit from '../rate-limit.js'
class AuthRPC {
constructor (localConfig = {}) {
@@ -173,4 +174,4 @@ class AuthRPC {
}
}
module.exports = AuthRPC
export default AuthRPC
+13 -6
View File
@@ -3,13 +3,14 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
import jsonrpc from 'jsonrpc-lite'
// Local support libraries
const { wlogger } = require('../../adapters/wlogger')
const UserController = require('./users')
const AuthController = require('./auth')
const AboutController = require('./about')
import wlogger from '../../adapters/wlogger.js'
import UserController from './users/index.js'
import AuthController from './auth/index.js'
import AboutController from './about/index.js'
let _this
@@ -96,6 +97,7 @@ class JSONRPC {
wlogger.info(
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
// console.log('parsedData: ', JSON.stringify(parsedData, null, 2))
}
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
@@ -112,6 +114,11 @@ class JSONRPC {
console.log('routing to BCH adapter')
retObj = await _this.adapters.bch.rpcHandler(parsedData)
}
if (parsedData.payload.result.method === 'file-pin') {
console.log('routing to IPFS Files adapter')
retObj = await _this.adapters.ipfsFiles.rpcHandler(parsedData)
}
} catch (err) {
/* exit quietly */
}
@@ -213,4 +220,4 @@ class JSONRPC {
}
}
module.exports = JSONRPC
export default JSONRPC
+2 -2
View File
@@ -4,7 +4,7 @@
/* eslint no-useless-catch: 0 */
// Local libraries
const RateLimitLib = require('koa2-ratelimit').RateLimit
import { RateLimit as RateLimitLib } from 'koa2-ratelimit'
class RateLimit {
constructor (options) {
@@ -80,4 +80,4 @@ class RateLimit {
}
}
module.exports = RateLimit
export default RateLimit
+5 -4
View File
@@ -3,12 +3,13 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
import jsonrpc from 'jsonrpc-lite'
// Local libraries
// const UserLib = require('../../../use-cases/user')
const Validators = require('../validators')
const RateLimit = require('../rate-limit')
import Validators from '../validators.js'
import RateLimit from '../rate-limit.js'
class UserRPC {
constructor (localConfig = {}) {
@@ -533,4 +534,4 @@ class UserRPC {
// TODO create deleteUser()
}
module.exports = UserRPC
export default UserRPC
+4 -3
View File
@@ -4,10 +4,11 @@
/* eslint no-useless-catch: 0 */
// Public npm libraries
const jwt = require('jsonwebtoken')
import jwt from 'jsonwebtoken'
// Local libraries
const config = require('../../../config')
import config from '../../../config/index.js'
// const UserModel = require('../../adapters/localdb/models/users')
class Validators {
@@ -93,4 +94,4 @@ class Validators {
}
}
module.exports = Validators
export default Validators
+2 -2
View File
@@ -1,4 +1,4 @@
const Passport = require('../../../adapters/passport')
import Passport from '../../../adapters/passport.js'
const passport = new Passport()
let _this
@@ -96,4 +96,4 @@ class AuthRESTController {
}
}
module.exports = AuthRESTController
export default AuthRESTController
+3 -3
View File
@@ -3,10 +3,10 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const AuthRESTController = require('./controller')
import AuthRESTController from './controller.js'
class AuthRouter {
constructor (localConfig = {}) {
@@ -48,4 +48,4 @@ class AuthRouter {
}
}
module.exports = AuthRouter
export default AuthRouter
+138 -7
View File
@@ -2,7 +2,8 @@
REST API Controller library for the /bch route
*/
const { wlogger } = require('../../../adapters/wlogger')
import wlogger from '../../../adapters/wlogger.js'
import config from '../../../../config/index.js'
// let _this
@@ -23,10 +24,7 @@ class BchRESTControllerLib {
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
this.config = config
}
/**
@@ -66,6 +64,11 @@ class BchRESTControllerLib {
try {
const providerId = ctx.request.body.provider
// Throw an error, if the provider has been set by an environment variable.
if (this.config.freezeProvider) {
throw new Error('Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
}
await this.adapters.bch.selectProvider(providerId)
const body = {
@@ -231,6 +234,103 @@ class BchRESTControllerLib {
}
}
/**
* @api {post} /bch/utxosBulk UTXOs Bulk
* @apiName UTXOs Bulk
* @apiGroup REST BCH
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
* with token information.
* @apiDescription This endpoint is the same as the /bch/utxos endpoint, but
* it allows an array of up to 20 addresses to be queried at the same time.
* This reduces the number of JSON RPC calls, and is very handy
* for HD wallets that need to quickly scan a lot of addresses.
*
* Given an address, this endpoint will return an object with thre following
* properties:
*
* - addresses: [] - the addresses these UTXOs are associated with
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
* not spent, to be safe.
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
* - type1: {}
* - tokens: [] - SLP token Type 1 tokens.
* - mintBatons: [] - SLP token Type 1 mint batons.
* - nft: {}
* - tokens: [] - NFT tokens
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* [
* {
* "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"],
* "bchUtxos":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "tx_pos":0,
* "value":1000,
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "vout":0,
* "isValid":false
* }
* ],
* "nullUtxos":[
*
* ],
* "slpUtxos":{
* "type1":{
* "mintBatons":[
*
* ],
* "tokens":[
*
* ]
* },
* "nft":{
* "groupMintBatons":[
*
* ],
* "groupTokens":[
*
* ],
* "tokens":[
*
* ]
* }
* }
* }
* ]
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async utxosBulk (ctx) {
try {
const addrs = ctx.request.body.addresses
// console.log('address: ', address)
const utxos = await this.adapters.bch.getUtxosBulk(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = utxos
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/broadcast Broadcast
* @apiName Broadcast
@@ -606,12 +706,15 @@ class BchRESTControllerLib {
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d" }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "updateCache": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
*/
async getTokenData2 (ctx) {
try {
const tokenId = ctx.request.body.tokenId
const updateCache = ctx.request.body.updateCache
console.log('getTokenData2() updateCache: ', updateCache)
const data = await this.adapters.bch.getTokenData2(tokenId)
const data = await this.adapters.bch.getTokenData2(tokenId, updateCache)
console.log(`getTokenData2 data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
@@ -620,6 +723,33 @@ class BchRESTControllerLib {
}
}
/**
* @api {get} /bch/service Get the IPFS ID for the Wallet service
* @apiPermission public
* @apiName getService
* @apiGroup REST BCH
* @apiDescription Get the IPFS ID for the ipfs-bch-wallet-service node that
* this app is using to retrieve blockchain data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/bch/service
*
*/
async getService (ctx) {
try {
const selectedServiceProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
ctx.body = {
success: true,
selectedServiceProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
@@ -636,4 +766,5 @@ class BchRESTControllerLib {
}
}
module.exports = BchRESTControllerLib
// module.exports = BchRESTControllerLib
export default BchRESTControllerLib
+15 -4
View File
@@ -3,11 +3,11 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const BchRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
import BchRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -55,6 +55,7 @@ class BchRouter {
this.router.post('/provider', this.postProvider)
this.router.post('/balance', this.postBalance)
this.router.post('/utxos', this.postUtxos)
this.router.post('/utxosBulk', this.postUtxosBulk)
this.router.post('/broadcast', this.postBroadcast)
this.router.post('/txHistory', this.postTxHistory)
this.router.post('/txData', this.postTxData)
@@ -62,6 +63,7 @@ class BchRouter {
this.router.post('/utxoIsValid', this.utxoIsValid)
this.router.post('/getTokenData', this.getTokenData)
this.router.post('/getTokenData2', this.getTokenData2)
this.router.get('/service', this.getService)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -84,6 +86,10 @@ class BchRouter {
await _this.bchRESTController.utxos(ctx, next)
}
async postUtxosBulk (ctx, next) {
await _this.bchRESTController.utxosBulk(ctx, next)
}
async postBroadcast (ctx, next) {
await _this.bchRESTController.broadcast(ctx, next)
}
@@ -111,6 +117,11 @@ class BchRouter {
async getTokenData2 (ctx, next) {
await _this.bchRESTController.getTokenData2(ctx, next)
}
async getService (ctx, next) {
await _this.bchRESTController.getService(ctx, next)
}
}
module.exports = BchRouter
// module.exports = BchRouter
export default BchRouter
@@ -3,9 +3,9 @@
*/
/* eslint-disable no-useless-escape */
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
import ContactLib from '../../../adapters/contact.js'
const ContactLib = require('../../../adapters/contact')
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
const contactLib = new ContactLib()
let _this
@@ -74,4 +74,4 @@ class ContactController {
}
}
}
module.exports = ContactController
export default ContactController
+3 -3
View File
@@ -3,10 +3,10 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const ContactRESTControllerLib = require('./controller')
import ContactRESTControllerLib from './controller.js'
class ContactRouter {
constructor (localConfig = {}) {
@@ -53,4 +53,4 @@ class ContactRouter {
}
}
module.exports = ContactRouter
export default ContactRouter
+10 -10
View File
@@ -6,15 +6,15 @@
// Public npm libraries.
// Load the REST API Controllers.
const AuthRESTController = require('./auth')
const UserRouter = require('./users')
const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs')
const BchRESTController = require('./bch')
const IpfsRESTController = require('./ipfs')
const P2wdbRESTController = require('./p2wdb')
const PriceRESTController = require('./price')
// Local libraries
import AuthRESTController from './auth/index.js'
import UserRouter from './users/index.js'
import ContactRESTController from './contact/index.js'
import LogsRESTController from './logs/index.js'
import BchRESTController from './bch/index.js'
import IpfsRESTController from './ipfs/index.js'
import P2wdbRESTController from './p2wdb/index.js'
import PriceRESTController from './price/index.js'
class RESTControllers {
constructor (localConfig = {}) {
@@ -75,4 +75,4 @@ class RESTControllers {
}
}
module.exports = RESTControllers
export default RESTControllers
+186 -11
View File
@@ -2,9 +2,11 @@
REST API Controller library for the /ipfs route
*/
const { wlogger } = require('../../../adapters/wlogger')
// Global npm libraries
import mime from 'mime-types'
let _this
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
class IpfsRESTControllerLib {
constructor (localConfig = {}) {
@@ -26,14 +28,24 @@ class IpfsRESTControllerLib {
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
_this = this
// Bind 'this' object to all subfunctions
this.getStatus = this.getStatus.bind(this)
this.getPeers = this.getPeers.bind(this)
this.getRelays = this.getRelays.bind(this)
this.handleError = this.handleError.bind(this)
this.connect = this.connect.bind(this)
this.getThisNode = this.getThisNode.bind(this)
this.viewFile = this.viewFile.bind(this)
this.getService = this.getService.bind(this)
this.getFileInfo = this.getFileInfo.bind(this)
this.getPins = this.getPins.bind(this)
}
/**
* @api {get} /ipfs Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetIpfsStatus
* @apiGroup REST BCH
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
@@ -41,13 +53,13 @@ class IpfsRESTControllerLib {
*/
async getStatus (ctx) {
try {
const status = await _this.adapters.ipfs.getStatus()
const status = await this.adapters.ipfs.getStatus()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
@@ -56,26 +68,188 @@ class IpfsRESTControllerLib {
try {
const showAll = ctx.request.body.showAll
const peers = await _this.adapters.ipfs.getPeers(showAll)
const peers = await this.adapters.ipfs.getPeers(showAll)
ctx.body = { peers }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
async getRelays (ctx) {
try {
const relays = await _this.adapters.ipfs.getRelays()
const relays = await this.adapters.ipfs.getRelays()
ctx.body = { relays }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
async connect (ctx) {
try {
const multiaddr = ctx.request.body.multiaddr
const getDetails = ctx.request.body.getDetails
// console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs)
const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails })
// console.log('result: ', result)
ctx.body = result
} catch (err) {
wlogger.error('Error in ipfs/controller.js/connect():', err)
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
* @apiPermission public
* @apiName GetThisNode
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
*
*/
async getThisNode (ctx) {
try {
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
ctx.body = { thisNode }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getThisNode(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/view/:cid Retrieve and display a file via its IPFS CID
* @apiPermission public
* @apiName GetCidView
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/view/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async viewFile (ctx) {
try {
const { cid } = ctx.params
// const file = await this.adapters.ipfs.ipfs.blockstore.get(cid)
// return file
// const cid = ctx.params.cid
const { filename, readStream } = await this.useCases.ipfs.downloadCid({ cid })
// ctx.body = ctx.req.pipe(readStream)
// Lookup the mime type from the filename.
const contentType = mime.lookup(filename)
ctx.set('Content-Type', contentType)
ctx.set(
'Content-Disposition',
// 'inline; filename="' + filename + '"'
`inline; filename="${filename}"`
)
ctx.body = readStream
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/viewFile(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/service Get the IPFS ID for the File Pin service
* @apiPermission public
* @apiName GetService
* @apiGroup REST IPFS
* @apiDescription Get the IPFS ID for the ipfs-file-pin-service node that
* this app is using to retrieve file data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/service
*
*/
async getService (ctx) {
try {
const selectedIpfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
ctx.body = {
success: true,
selectedIpfsFileProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/file-info/:cid Get file pin info about a CID
* @apiPermission public
* @apiName GetFileInfo
* @apiGroup REST IPFS
* @apiDescription Get file metadata and pin status information give a CID.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/file-info/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async getFileInfo (ctx) {
try {
const { cid } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
ctx.body = metadata
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/pins Get info on latest 20 pinned files
* @apiPermission public
* @apiName GetPins
* @apiGroup REST IPFS
* @apiDescription Returns an array of the latest 20 pinned files.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/pins
*
*/
async getPins (ctx) {
try {
// const { cid } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const pinData = await ipfsFiles.getPins()
console.log('getPins() pinData: ', pinData)
ctx.body = pinData
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getPins(): ', err)
this.handleError(ctx, err)
}
}
@@ -95,4 +269,5 @@ class IpfsRESTControllerLib {
}
}
module.exports = IpfsRESTControllerLib
// module.exports = IpfsRESTControllerLib
export default IpfsRESTControllerLib
+12 -9
View File
@@ -3,15 +3,15 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const IPFSRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
import IPFSRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
// let _this
class BchRouter {
class IpfsRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
@@ -54,15 +54,18 @@ class BchRouter {
this.router.get('/', this.ipfsRESTController.getStatus)
this.router.post('/peers', this.ipfsRESTController.getPeers)
this.router.post('/relays', this.ipfsRESTController.getRelays)
this.router.post('/connect', this.ipfsRESTController.connect)
this.router.get('/node', this.ipfsRESTController.getThisNode)
this.router.get('/view/:cid', this.ipfsRESTController.viewFile)
this.router.get('/service', this.ipfsRESTController.getService)
this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo)
this.router.get('/pins', this.ipfsRESTController.getPins)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
// async getStatus (ctx, next) {
// await _this.ipfsRESTController.getStatus(ctx, next)
// }
}
module.exports = BchRouter
// module.exports = BchRouter
export default IpfsRouter
+2 -2
View File
@@ -1,4 +1,4 @@
const LogsApiLib = require('../../../adapters/logapi')
import LogsApiLib from '../../../adapters/logapi.js'
const logsApiLib = new LogsApiLib()
let _this
@@ -62,4 +62,4 @@ class LogsApi {
}
}
module.exports = LogsApi
export default LogsApi
+3 -3
View File
@@ -3,10 +3,10 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const LogsRESTControllerLib = require('./controller')
import LogsRESTControllerLib from './controller.js'
class LogsRouter {
constructor (localConfig = {}) {
@@ -52,4 +52,4 @@ class LogsRouter {
}
}
module.exports = LogsRouter
export default LogsRouter
+2 -2
View File
@@ -1,4 +1,4 @@
module.exports = function errorMiddleware () {
export default function errorMiddleware () {
return async (ctx, next) => {
try {
await next()
@@ -8,4 +8,4 @@ module.exports = function errorMiddleware () {
ctx.app.emit('error', err, ctx)
}
}
}
};
@@ -1,11 +1,24 @@
/*
REST API validator middleware.
These are a series of functions that ensure the user making a REST API
matches a user in the database (or not). In can do fine-grain user control
such as telling the difference between an admin, a normal user, and an
anonymous user.
This middleware is used to gatekeep access to different REST API resources.
CT 9/17/22:
This library was lightly refactored to make it work with the new unit tests.
This not and the commented code below can be deleted once it is verified that
this refactor did not result in any breaking changes.
*/
const User = require('../../../adapters/localdb/models/users')
const config = require('../../../../config')
const jwt = require('jsonwebtoken')
const { wlogger } = require('../../../adapters/wlogger')
import User from '../../../adapters/localdb/models/users.js'
import config from '../../../../config/index.js'
import jwt from 'jsonwebtoken'
import wlogger from '../../../adapters/wlogger.js'
let _this
@@ -20,12 +33,10 @@ class Validators {
async ensureUser (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
let decoded = null
@@ -34,20 +45,23 @@ class Validators {
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// console.log('Err: Could not find user.')
throw new Error('Could not find user')
}
// return next()
return true
} catch (error) {
ctx.throw(401)
// console.log('Ensure user error: ', error)
// console.log('ctx: ', ctx)
ctx.status = 401
ctx.throw(401, error.message)
}
}
@@ -60,7 +74,8 @@ class Validators {
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
let decoded = null
@@ -70,22 +85,26 @@ class Validators {
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not find user')
}
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
// ctx.throw(401, 'not admin')
throw new Error('User is not an admin')
}
// return next()
return true
} catch (error) {
ctx.status = 401
ctx.throw(401, error.message)
}
}
@@ -101,7 +120,8 @@ class Validators {
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
// The user ID targeted in this API call.
@@ -114,14 +134,16 @@ class Validators {
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
console.log(`Err: Token could not be decoded: ${err}`)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not find user')
}
// console.log('ctx.state.user: ', ctx.state.user)
@@ -135,7 +157,8 @@ class Validators {
// If they don't match, then the calling user better be an admin.
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
// ctx.throw(401, 'not admin')
throw new Error('User is not an admin')
} else {
wlogger.verbose("It's ok. The user is an admin.")
}
@@ -144,6 +167,8 @@ class Validators {
// return next()
return true
} catch (error) {
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
ctx.status = 401
ctx.throw(401, error.message)
}
}
@@ -166,4 +191,4 @@ class Validators {
}
}
module.exports = Validators
export default Validators
+3 -2
View File
@@ -2,7 +2,7 @@
REST API Controller library for the /p2wdb route
*/
const { wlogger } = require('../../../adapters/wlogger')
import wlogger from '../../../adapters/wlogger.js'
// let _this
@@ -135,4 +135,5 @@ class P2wdbRESTControllerLib {
}
}
module.exports = P2wdbRESTControllerLib
// module.exports = P2wdbRESTControllerLib
export default P2wdbRESTControllerLib
+5 -4
View File
@@ -3,11 +3,11 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const P2wdbRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
import P2wdbRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -98,4 +98,5 @@ class P2wdbRouter {
// }
}
module.exports = P2wdbRouter
// module.exports = P2wdbRouter
export default P2wdbRouter
+29 -3
View File
@@ -2,8 +2,8 @@
REST API Controller library for the /bch route
*/
const axios = require('axios')
const { wlogger } = require('../../../adapters/wlogger')
import axios from 'axios'
import wlogger from '../../../adapters/wlogger.js'
// let _this
@@ -64,6 +64,31 @@ class PriceRESTControllerLib {
}
}
// Get the XEC price.
// TODO: Add API docs.
async getXecPrice (ctx) {
try {
// Request options
const opt = {
method: 'get',
baseURL: 'https://api.coinex.com',
url: '/v1/market/ticker?market=xecusdt',
timeout: 15000
}
//
const response = await this.axios.request(opt)
console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
ctx.body = { usd: Number(response.data.data.ticker.last) }
} catch (err) {
// Write out error to error log.
wlogger.error('Error in GET /price/xecusd.', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
@@ -80,4 +105,5 @@ class PriceRESTControllerLib {
}
}
module.exports = PriceRESTControllerLib
// module.exports = PriceRESTControllerLib
export default PriceRESTControllerLib
+10 -4
View File
@@ -3,11 +3,11 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const PriceRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
import PriceRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -52,6 +52,7 @@ class PriceRouter {
// Define the routes and attach the controller.
this.router.get('/usd', this.getPrice)
this.router.get('/xecusd', this.getXecPrice)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -61,6 +62,11 @@ class PriceRouter {
async getPrice (ctx, next) {
await _this.priceRESTController.getUSD(ctx, next)
}
async getXecPrice (ctx, next) {
await _this.priceRESTController.getXecPrice(ctx, next)
}
}
module.exports = PriceRouter
// module.exports = PriceRouter
export default PriceRouter
+5 -5
View File
@@ -2,7 +2,7 @@
REST API Controller library for the /user route
*/
const { wlogger } = require('../../../adapters/wlogger')
import wlogger from '../../../adapters/wlogger.js'
let _this
@@ -36,7 +36,7 @@ class UserRESTControllerLib {
* @apiGroup REST Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5001/users
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users
*
* @apiParam {Object} user User object (required)
* @apiParam {String} user.email Email
@@ -124,7 +124,7 @@ class UserRESTControllerLib {
ctx.body = { users }
} catch (err) {
wlogger.error('Error in users/controller.js/getUsers(): '.err)
wlogger.error('Error in users/controller.js/getUsers(): ', err)
ctx.throw(422, err.message)
}
}
@@ -136,7 +136,7 @@ class UserRESTControllerLib {
* @apiGroup REST Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
* curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
@@ -281,4 +281,4 @@ class UserRESTControllerLib {
}
}
module.exports = UserRESTControllerLib
export default UserRESTControllerLib
+5 -4
View File
@@ -3,11 +3,12 @@
*/
// Public npm libraries.
const Router = require('koa-router')
import Router from 'koa-router'
// Local libraries.
const UserRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -85,4 +86,4 @@ class UserRouter {
}
}
module.exports = UserRouter
export default UserRouter
+65
View File
@@ -0,0 +1,65 @@
/*
This Controller library is concerned with timer-based functions that are
kicked off periodicially.
*/
import config from '../../config/index.js'
class TimerControllers {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating Timer Controller libraries.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating Timer Controller libraries.'
)
}
this.debugLevel = localConfig.debugLevel
// Encapsulate dependencies
this.config = config
// Bind 'this' object to all subfunctions.
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
// this.startTimers()
}
// Start all the time-based controllers.
startTimers () {
// Any new timer control functions can be added here. They will be started
// when the server starts.
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
return true
}
stopTimers () {
clearInterval(this.optimizeWalletHandle)
}
// Replace this example function with your own timer handler.
exampleTimerFunc (negativeTest) {
try {
// console.log('Example timer controller executed.')
if (negativeTest) throw new Error('test error')
return true
} catch (err) {
console.error('Error in exampleTimerFunc(): ', err)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
}
export default TimerControllers
+1 -1
View File
@@ -21,4 +21,4 @@ class User {
}
}
module.exports = User
export default User
+5 -8
View File
@@ -4,7 +4,9 @@
https://troutsblog.com/blog/clean-architecture
*/
const UserUseCases = require('./user')
// Local libraries
import UserUseCases from './user.js'
import IpfsUseCases from './ipfs-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -23,20 +25,15 @@ class UseCases {
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
this.ipfs = new IpfsUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
async start () {
// try {
console.log('Async Use Cases have been started.')
return true
// } catch (err) {
// console.error('Error in use-cases/index.js/start()')
// // console.log(err)
// throw err
// }
}
}
module.exports = UseCases
export default UseCases
+164
View File
@@ -0,0 +1,164 @@
/*
Use cases for working with IPFS.
*/
// Global npm libraries
// import Wallet from 'minimal-slp-wallet'
// import { CID } from 'multiformats'
// import RetryQueue from '@chris.troutner/retry-queue'
// import { exporter } from 'ipfs-unixfs-exporter'
import { Duplex } from 'stream'
// Local libraries
// import PinEntity from '../entities/pin.js'
// import config from '../../config/index.js'
// const PSF_TOKEN_ID = '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
class IpfsUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
// Encapsulate dependencies
// this.exporter = exporter
// Bind 'this' object to all class subfunctions.
this.downloadCid = this.downloadCid.bind(this)
// this.downloadCid2 = this.downloadCid2.bind(this)
}
// Download a pinned file, given its CID.
// Returns a readable stream.
async downloadCid (inObj = {}) {
try {
const { cid } = inObj
if (!cid) throw new Error('CID is undefined')
// const Pins = this.adapters.localdb.Pins
// let existingModel = await Pins.find({ cid })
// existingModel = existingModel[0]
// console.log('existingModel: ', existingModel)
// if (!existingModel) {
// throw new Error(`Database model for CID ${cid} does not exist.`)
// }
//
// if (!existingModel.dataPinned) {
// throw new Error('File has not been pinned. Not available.')
// }
const filename = await this.getCidMetadata({ cid })
const helia = this.adapters.ipfs.ipfs
// Convert the file to a Buffer.
const fileChunks = []
for await (const chunk of helia.fs.cat(cid)) {
fileChunks.push(chunk)
}
const fileBuf = Buffer.concat(fileChunks)
// Convert the Buffer into a readable stream
const bufferToStream = (myBuffer) => {
const tmp = new Duplex()
tmp.push(myBuffer)
tmp.push(null)
return tmp
}
const readStream = bufferToStream(fileBuf)
// const filename = 'test.jpg'
return { filename, readStream }
} catch (err) {
console.error('Error in use-cases/ipfs.js/dowloadCid()')
throw err
}
}
// Given a CID, this function will retrieve file metadata from the
// ipfs-file-pin-service, using the IPFS JSON-RPC. This metadata includes
// the filename, which can be used to infer mime-type, so that it can be
// delivered to a web browser.
async getCidMetadata (inObj = {}) {
try {
const { cid } = inObj
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
if (metadata.success === false) {
throw new Error(metadata.message)
}
const filename = metadata.fileMetadata.filename
return filename
} catch (err) {
console.error('Error in getCidMetadata(): ', err)
throw err
}
}
// async downloadCid2 (inObj = {}) {
// try {
// const { cid } = inObj
//
// // console.log(`downloadFile() retrieving this CID: ${cid}, with fileName: ${fileName}, and path: ${path}`)
//
// const blockstore = this.adapters.ipfs.ipfs.blockstore
// const entry = await this.exporter(cid, blockstore)
//
// console.info(entry.cid) // Qmqux
// console.log('entry: ', entry)
// // console.info(entry.unixfs.fileSize()) // 4
//
// // const filePath = `${path}/${fileName}`
// // console.log(`filePath: ${filePath}`)
// // const writableStream = this.fs.createWriteStream(filePath)
// //
// // writableStream.on('error', this.writeStreamError)
// //
// // writableStream.on('finish', this.writeStreamFinished)
// //
//
// const fileChunks = []
// for await (const buf of entry.content()) {
// fileChunks.push(buf)
// }
// const fileBuf = Buffer.concat(fileChunks)
//
// //
// // writableStream.end()
//
// // Convert the Buffer into a readable stream
// const bufferToStream = (myBuffer) => {
// const tmp = new Duplex()
// tmp.push(myBuffer)
// tmp.push(null)
// return tmp
// }
// const readStream = bufferToStream(fileBuf)
//
// const filename = 'test.jpg'
//
// return { filename, readStream }
//
// // return { cid }
// } catch (err) {
// console.error('Error in ipfs-use-cases.js/downloadCid()')
// throw err
// }
// }
}
export default IpfsUseCases
+4 -3
View File
@@ -3,8 +3,9 @@
functions are called by the /user REST API endpoints.
*/
const UserEntity = require('../entities/user')
const { wlogger } = require('../adapters/wlogger')
import UserEntity from '../entities/user.js'
import wlogger from '../adapters/wlogger.js'
class UserLib {
constructor (localConfig = {}) {
@@ -181,4 +182,4 @@ class UserLib {
}
}
module.exports = UserLib
export default UserLib
-3
View File
@@ -1,3 +0,0 @@
/key/swarm/psk/1.0.0/
/base16/
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
+9 -6
View File
@@ -5,15 +5,18 @@
*/
// Public npm libraries
const assert = require('chai').assert
const axios = require('axios').default
import { assert } from 'chai'
import axios from 'axios'
// const sinon = require('sinon')
// Local support libraries
const config = require('../../../config')
const Server = require('../../../bin/server')
const testUtils = require('../../utils/test-utils')
const AdminLib = require('../../../src/adapters/admin')
import config from '../../../config/index.js'
import Server from '../../../bin/server.js'
import testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen())
+54 -53
View File
@@ -1,21 +1,25 @@
const testUtils = require('../../utils/test-utils')
const assert = require('chai').assert
const config = require('../../../config')
const axios = require('axios').default
const sinon = require('sinon')
import testUtils from '../../utils/test-utils.js'
import { assert } from 'chai'
import config from '../../../config/index.js'
import axios from 'axios'
import sinon from 'sinon'
import util from 'util'
const util = require('util')
import UserController from '../../../src/controllers/rest-api/users/controller.js'
import Adapters from '../../../src/adapters/index.js'
import UseCases from '../../../src/use-cases/index.js'
// import UserController from '../../../src/controllers/rest-api/users/controller.js'
// import Adapters from '../../../src/adapters/index.js'
import EventEmitter from 'events'
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
const context = {}
const UserController = require('../../../src/controllers/rest-api/users/controller')
const Adapters = require('../../../src/adapters')
const EventEmitter = require('events')
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
const UseCases = require('../../../src/use-cases/')
// import UseCases from '../../../src/use-cases/index.js'
let uut
let sandbox
@@ -39,9 +43,9 @@ describe('Users', () => {
context.id2 = testUser.user._id
// Get the JWT used to log in as the admin 'system' user.
const adminJWT = await testUtils.getAdminJWT()
// console.log(`adminJWT: ${admi nJWT}`)
context.adminJWT = adminJWT
// const adminJWT = await testUtils.getAdminJWT()
// console.log(`adminJWT: ${adminJWT}`)
// context.adminJWT = adminJWT
// const admin = await testUtils.loginAdminUser()
// context.adminJWT = admin.token
@@ -72,9 +76,6 @@ describe('Users', () => {
await axios(options)
/* console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
) */
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 422, 'Error code 422 expected.')
@@ -599,30 +600,30 @@ describe('Users', () => {
}
})
it('should be able to update other user when admin', async () => {
const adminJWT = context.adminJWT
// it('should be able to update other user when admin', async () => {
// const adminJWT = context.adminJWT
const options = {
method: 'PUT',
url: `${LOCALHOST}/users/${context.user2._id.toString()}`,
headers: {
Authorization: `Bearer ${adminJWT}`
},
data: {
user: {
name: 'This should work',
email: 'test4@test.com',
password: 'password'
}
}
}
// const options = {
// method: 'PUT',
// url: `${LOCALHOST}/users/${context.user2._id.toString()}`,
// headers: {
// Authorization: `Bearer ${adminJWT}`
// },
// data: {
// user: {
// name: 'This should work',
// email: 'test4@test.com',
// password: 'password'
// }
// }
// }
const result = await axios(options)
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
// const result = await axios(options)
// // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
const userName = result.data.user.name
assert.equal(userName, 'This should work')
})
// const userName = result.data.user.name
// assert.equal(userName, 'This should work')
// })
it('should update user with minimum inputs', async () => {
const _id = context.user._id
@@ -771,22 +772,22 @@ describe('Users', () => {
assert.equal(result.data.success, true)
})
it('should be able to delete other users when admin', async () => {
const id = context.id2
const adminJWT = context.adminJWT
// it('should be able to delete other users when admin', async () => {
// const id = context.id2
// const adminJWT = context.adminJWT
const options = {
method: 'DELETE',
url: `${LOCALHOST}/users/${id}`,
headers: {
Accept: 'application/json',
Authorization: `Bearer ${adminJWT}`
}
}
const result = await axios(options)
// console.log(`result: ${util.inspect(result.data)}`)
// const options = {
// method: 'DELETE',
// url: `${LOCALHOST}/users/${id}`,
// headers: {
// Accept: 'application/json',
// Authorization: `Bearer ${adminJWT}`
// }
// }
// const result = await axios(options)
// // console.log(`result: ${util.inspect(result.data)}`)
assert.equal(result.data.success, true)
})
// assert.equal(result.data.success, true)
// })
})
})
+7 -7
View File
@@ -1,15 +1,15 @@
const config = require('../../../config')
const axios = require('axios').default
const assert = require('chai').assert
const sinon = require('sinon')
import config from '../../../config/index.js'
import axios from 'axios'
import { assert } from 'chai'
import sinon from 'sinon'
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
import ContactController from '../../../src/controllers/rest-api/contact/controller.js'
// Mock data
// const mockData = require('./mocks/contact-mocks')
const LOCALHOST = `http://localhost:${config.port}`
const mockContext = require('../../unit/mocks/ctx-mock').context
const ContactController = require('../../../src/controllers/rest-api/contact/controller')
let uut
let sandbox
+24 -28
View File
@@ -1,17 +1,15 @@
const config = require('../../../config')
const assert = require('chai').assert
import config from '../../../config/index.js'
import { assert } from 'chai'
import axios from 'axios'
import sinon from 'sinon'
import util from 'util'
const axios = require('axios').default
const sinon = require('sinon')
const util = require('util')
import LogsController from '../../../src/controllers/rest-api/logs/controller.js'
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
const LogsController = require('../../../src/controllers/rest-api/logs/controller')
const mockContext = require('../../unit/mocks/ctx-mock').context
let sandbox
let uut
describe('LogsApi', () => {
@@ -39,27 +37,25 @@ describe('LogsApi', () => {
}
})
it('should return log', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/logs`,
data: {
password: 'test'
}
}
// it('should return log', async () => {
const result = await axios(options)
// const options = {
// method: 'post',
// url: `${LOCALHOST}/logs`,
// data: {
// password: 'test'
// }
// }
assert.isTrue(result.data.success)
assert.isArray(result.data.data)
assert.property(result.data.data[0], 'message')
assert.property(result.data.data[0], 'level')
assert.property(result.data.data[0], 'timestamp')
} catch (err) {
assert(false, 'Unexpected result')
}
})
// const result = await axios(options)
// assert.isTrue(result.data.success)
// assert.isArray(result.data.data)
// assert.property(result.data.data[0], 'message')
// assert.property(result.data.data[0], 'level')
// assert.property(result.data.data[0], 'timestamp')
// })
it('should return false if files are not found!', async () => {
try {
@@ -1,12 +1,10 @@
const assert = require('chai').assert
const testUtils = require('../../utils/test-utils')
const Validators = require('../../../src/controllers/rest-api/middleware/validators')
const sinon = require('sinon')
const mockContext = require('../../unit/mocks/ctx-mock').context
const util = require('util')
/*
import { assert } from 'chai';
import testUtils from '../../utils/test-utils.js';
import Validators from '../../../src/controllers/rest-api/middleware/validators.js';
import sinon from 'sinon';
import { context as mockContext } from '../../unit/mocks/ctx-mock.js';
import util from 'util';
util.inspect.defaultOptions = { depth: 1 }
const context = {}
@@ -303,3 +301,4 @@ describe('Validators', () => {
})
})
})
*/
+4 -7
View File
@@ -1,10 +1,7 @@
const assert = require('chai').assert
const Admin = require('../../../src/adapters/admin')
const sinon = require('sinon')
const util = require('util')
import { assert } from 'chai'
import Admin from '../../../src/adapters/admin.js'
import sinon from 'sinon'
import util from 'util'
util.inspect.defaultOptions = { depth: 1 }
let sandbox
+6 -3
View File
@@ -3,11 +3,12 @@
*/
// Global npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
const Adapters = require('../../../src/adapters')
import Adapters from '../../../src/adapters/index.js'
describe('#adapters', () => {
let uut, sandbox
@@ -26,6 +27,8 @@ describe('#adapters', () => {
it('should start the async adapters', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
uut.config.useIpfs = true
uut.config.env = 'not-a-test'
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
sandbox.stub(uut.ipfs, 'start').resolves()
+96 -29
View File
@@ -5,19 +5,20 @@
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const EventEmitter = require('events')
const cloneDeep = require('lodash.clonedeep')
import { assert } from 'chai'
import sinon from 'sinon'
import EventEmitter from 'events'
import cloneDeep from 'lodash.clonedeep'
// Local support libraries
// const testUtils = require('../../utils/test-utils')
// Unit under test (uut)
const BchAdapter = require('../../../src/adapters/bch')
const adapters = require('../mocks/adapters')
import BchAdapter from '../../../src/adapters/bch/index.js'
import adapters from '../mocks/adapters/index.js'
import mockDataLib from '../mocks/use-cases/rest-api-mocks.js'
const eventEmitter = new EventEmitter()
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
describe('#bch-use-case', () => {
let uut
@@ -93,25 +94,12 @@ describe('#bch-use-case', () => {
it('should catch and throw an error', async () => {
try {
// Force an error
uut.ipfs.ipfsCoordAdapter.bchjs = {
Util: {
sleep: () => {
throw new Error('test error')
}
}
}
// Mock data.
const rpcId = '123'
uut.rpcDataQueue.push(mockData.rpcData)
await uut.waitForRPCResponse(rpcId)
await uut.waitForRPCResponse()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('error: ', err)
assert.include(err.message, 'test error')
assert.include(err.message, 'rpcId can not be false or undefined')
}
})
})
@@ -155,6 +143,9 @@ describe('#bch-use-case', () => {
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'uid').throws(new Error('test error'))
await uut.getBalances()
assert.fail('Unexpected code path')
@@ -165,6 +156,82 @@ describe('#bch-use-case', () => {
})
})
describe('#getUtxos', () => {
it('should get the UTXOs of an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addr = 'addr'
const result = await uut.getUtxos(addr)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getUtxos()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'addr required when calling getUtxos')
}
})
})
describe('#getUtxosBulk', () => {
it('should get the UTXOs of an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addrs = ['addr']
const result = await uut.getUtxosBulk(addrs)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should throw an error if addresses is not an array', async () => {
try {
await uut.getUtxosBulk()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'addresses parameter must be an array')
}
})
it('should throw an error if addresses array is larger than 20 elements', async () => {
try {
const addrs = []
for (let i = 0; i < 25; i++) {
addrs.push(i)
}
await uut.getUtxosBulk(addrs)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
}
})
})
describe('#broadcast', () => {
it('should broadcast a transaction', async () => {
// Force connection to a wallet service
@@ -190,7 +257,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'hex required when calling broadcast')
}
})
})
@@ -220,7 +287,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'address required when calling getTransactions()')
}
})
})
@@ -250,7 +317,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'txids required when calling getTransaction()')
}
})
})
@@ -280,7 +347,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'address required when calling getPubKey()')
}
})
})
@@ -310,7 +377,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'utxo required when calling utxoIsValid()')
}
})
})
@@ -340,7 +407,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'tokenId required when calling getTokenData()')
}
})
})
@@ -370,7 +437,7 @@ describe('#bch-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
assert.equal(err.message, 'tokenId required when calling getTokenData2()')
}
})
})
+3 -4
View File
@@ -1,7 +1,6 @@
const assert = require('chai').assert
const sinon = require('sinon')
const ContactLib = require('../../../src/adapters/contact')
import { assert } from 'chai'
import sinon from 'sinon'
import ContactLib from '../../../src/adapters/contact.js'
let uut
let sandbox
@@ -3,10 +3,10 @@
*/
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
import sinon from 'sinon'
import FullStackJWT from '../../../src/adapters/fullstack-jwt.js'
describe('#FullStackJWT', () => {
let sandbox
+26 -11
View File
@@ -2,15 +2,15 @@
Unit tests for the IPFS Adapter.
*/
const assert = require('chai').assert
const sinon = require('sinon')
const cloneDeep = require('lodash.clonedeep')
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
const IPFSMock = require('../mocks/ipfs-mock')
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
const config = require('../../../config')
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
import IPFSCoordAdapter from '../../../src/adapters/ipfs/ipfs-coord.js'
// import IPFSMock from '../mocks/ipfs-mock.js'
import create from '../mocks/ipfs-mock.js'
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
import config from '../../../config/index.js'
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
describe('#IPFS', () => {
let uut
@@ -18,7 +18,7 @@ describe('#IPFS', () => {
let mockData
beforeEach(() => {
const ipfs = IPFSMock.create()
const ipfs = create()
uut = new IPFSCoordAdapter({ ipfs })
sandbox = sinon.createSandbox()
@@ -76,7 +76,7 @@ describe('#IPFS', () => {
it('should get the public IP address if this node is a Circuit Relay', async () => {
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
sandbox.stub(uut.publicIp, 'v4').returns('123')
sandbox.stub(uut, 'publicIp').resolves('123')
// Force Circuit Relay
uut.config.isCircuitRelay = true
@@ -86,6 +86,21 @@ describe('#IPFS', () => {
assert.equal(result, true)
})
it('should exit quietly if this node is a Circuit Relay and there is an issue getting the IP address', async () => {
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
sandbox.stub(uut, 'publicIp').rejects(new Error('test error'))
// Force Circuit Relay
uut.config.isCircuitRelay = true
const result = await uut.start()
// console.log('result: ', result)
assert.equal(result, true)
})
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
uut.config.isProduction = true
// Mock dependencies.
+163 -8
View File
@@ -2,20 +2,22 @@
Unit tests for the index.js file for the IPFS and ipfs-coord libraries.
*/
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
const IPFSLib = require('../../../src/adapters/ipfs')
const IPFSMock = require('../mocks/ipfs-mock')
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
const EventEmitter = require('events')
import sinon from 'sinon'
import IPFSLib from '../../../src/adapters/ipfs/index.js'
// import IPFSMock from '../mocks/ipfs-mock.js'
// import create from '../mocks/ipfs-mock.js'
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
// import EventEmiter from 'events'
describe('#IPFS-adapter-index', () => {
let uut
let sandbox
beforeEach(() => {
uut = new IPFSLib({ eventEmitter: new EventEmitter() })
// uut = new IPFSLib({ eventEmitter: new EventEmitter() })
uut = new IPFSLib({ })
sandbox = sinon.createSandbox()
})
@@ -25,7 +27,9 @@ describe('#IPFS-adapter-index', () => {
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
// Mock dependencies.
uut.ipfsAdapter = new IPFSMock()
uut.ipfsAdapter = {
start: async () => {}
}
uut.IpfsCoordAdapter = IPFSCoordMock
const result = await uut.start()
@@ -65,4 +69,155 @@ describe('#IPFS-adapter-index', () => {
}
})
})
describe('#getStatus', () => {
it('should return an object with node metrics', () => {
// Force uut to have the needed properties
uut.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {
ipfsId: 'fake-id',
ipfsMultiaddrs: [],
bchAddr: 'fake-bch-addr',
slpAddr: 'fake-slp-addr',
pubKey: 'fake-pubkey',
peerList: [],
relayData: []
}
}
}
const result = uut.getStatus()
// console.log('result: ', result)
assert.property(result, 'ipfsId')
assert.property(result, 'multiAddrs')
assert.property(result, 'bchAddr')
assert.property(result, 'slpAddr')
assert.property(result, 'pubKey')
assert.property(result, 'peers')
assert.property(result, 'relays')
})
it('should catch, report, and throw errors', () => {
try {
uut.getStatus()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'Cannot read properties')
}
})
})
describe('#_removeDuplicatePeers', () => {
it('should return the same array when there are no duplicates', () => {
const inArray = [
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
]
const result = uut._removeDuplicatePeers(inArray)
// console.log('result: ', result)
assert.equal(result.length, inArray.length)
})
it('should remove duplicates from an array', () => {
const inArray = [
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu',
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA'
]
const result = uut._removeDuplicatePeers(inArray)
// console.log('result: ', result)
assert.equal(result.length, inArray.length - 2)
})
})
describe('#getPeers', () => {
it('should return an array of current JSON data about each peer when showAll is true', async () => {
// Mock dependencies
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', protocol: 'test', version: '1' } } }, { from: 'b' }]
},
adapters: {
ipfs: {
getPeers: async () => ['a', 'b']
}
}
}
const result = await uut.getPeers(true)
assert.isArray(result)
})
it('should catch, report, and throw errors', async () => {
try {
// Force an error
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {},
adapters: {
ipfs: {
getPeers: async () => { throw new Error('test error') }
}
}
}
await uut.getPeers()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'test error')
}
})
})
describe('#getRelays', () => {
it('should return known relays', () => {
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', description: 'test' } } }, { from: 'b' }],
relayData: [{ ipfsId: 'c' }, { ipfsId: 'a' }]
}
}
const result = uut.getRelays()
// console.log('result: ', result)
assert.isArray(result.v2Relays)
assert.equal(result.v2Relays.length, 2)
assert.property(result.v2Relays[0], 'name')
assert.property(result.v2Relays[0], 'description')
assert.property(result.v2Relays[1], 'name')
assert.property(result.v2Relays[1], 'description')
})
it('should catch, report, and throw errors', () => {
try {
// Force an error
uut.ipfsCoordAdapter.ipfsCoord = { thisNode: {} }
uut.getRelays()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'Cannot read')
}
})
})
})
+165 -41
View File
@@ -2,20 +2,29 @@
Unit tests for the IPFS Adapter.
*/
const assert = require('chai').assert
const sinon = require('sinon')
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
const IPFSMock = require('../mocks/ipfs-mock')
const config = require('../../../config')
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
import { peerIdFromString } from '@libp2p/peer-id'
// Local libraries
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
// import create from '../mocks/ipfs-mock.js'
import config from '../../../config/index.js'
import createHeliaLib from '../mocks/helia-mock.js'
// config.isProduction = true;
describe('#IPFS-adapter', () => {
let uut
let sandbox
let ipfs
beforeEach(() => {
uut = new IPFSLib()
ipfs = cloneDeep(createHeliaLib)
sandbox = sinon.createSandbox()
})
@@ -44,35 +53,28 @@ describe('#IPFS-adapter', () => {
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
// Mock dependencies.
uut.IPFS = IPFSMock
sandbox.stub(uut, 'createNode').resolves(ipfs)
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
const result = await uut.start()
// console.log('result: ', result)
// Assert properties of the instance are set.
assert.equal(uut.isReady, true)
assert.property(uut, 'multiaddrs')
assert.property(uut, 'id')
assert.property(result, 'config')
})
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
// Mock dependencies.
uut.IPFS = IPFSMock
uut.config.isProduction = true
const result = await uut.start()
// console.log('result: ', result)
assert.equal(uut.isReady, true)
assert.property(result, 'config')
// Output should be an instance of IPFS
assert.property(result, 'libp2p')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut.IPFS, 'create').rejects(new Error('test error'))
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
await uut.start()
assert.fail('Unexpected code path.')
} catch (err) {
// console.log(err)
@@ -95,24 +97,146 @@ describe('#IPFS-adapter', () => {
})
})
// describe('#rmBlocksDir', () => {
// it('should delete the /blocks directory', () => {
// const result = uut.rmBlocksDir()
//
// assert.equal(result, true)
// })
//
// it('should catch and throw an error', () => {
// try {
// // Force an error
// sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
//
// uut.rmBlocksDir()
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.equal(err.message, 'test error')
// }
// })
// })
describe('#ensureBlocksDir', () => {
it('should create directory if it does not exist', () => {
// Force desired code path
sandbox.stub(uut.fs, 'existsSync').returns(false)
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
const result = uut.ensureBlocksDir()
assert.equal(result, true)
})
it('should report and throw errors', () => {
// Force an error
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
try {
uut.ensureBlocksDir()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#createNode', () => {
it('should report and throw errors', async () => {
// Force an error
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
try {
await uut.createNode()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should create an IPFS node from Helia', async () => {
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a Circuit Relay if configured', async () => {
uut.config.isCircuitRelay = true
const result = await uut.createNode()
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a new private key on first run', async () => {
uut.config.isCircuitRelay = false
// Mock dependencies and force desired code path.
let beenCalled = false
sandbox.stub(uut, 'getKeychain').resolves({
exportPeerId: async () => {
if (!beenCalled) {
beenCalled = true
throw new Error('test error')
}
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
},
createKey: async () => {}
})
sandbox.stub(uut, 'createLibp2p').resolves()
sandbox.stub(uut, 'createHelia').resolves({})
const result = await uut.createNode()
// console.log('result: ', result)
assert.property(result, 'fs')
})
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
process.env.CONNECT_PREF = 'direct'
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
delete process.env.CONNECT_PREF
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
})
describe('#getSeed', () => {
it('should read the seed from the JSON file', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
it('should generate a new seed if the JSON file is not found', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
it('should catch, report, and throw errors', async () => {
try {
// Force an error
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error'))
await uut.getSeed()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+10 -6
View File
@@ -1,11 +1,15 @@
const assert = require('chai').assert
const fs = require('fs')
const sinon = require('sinon')
import { assert } from 'chai'
import fs from 'fs'
import sinon from 'sinon'
import util from 'util'
const util = require('util')
import JsonFiles from '../../../src/adapters/json-files.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
util.inspect.defaultOptions = { depth: 1 }
const JsonFiles = require('../../../src/adapters/json-files')
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const JSON_FILE = 'test-json-file.json'
const JSON_PATH = `${__dirname.toString()}/${JSON_FILE}`
+9 -6
View File
@@ -1,12 +1,15 @@
const assert = require('chai').assert
import { assert } from 'chai'
import sinon from 'sinon'
import util from 'util'
const sinon = require('sinon')
import LogsApiLib from '../../../src/adapters/logapi.js'
import mockData from '../mocks/log-api-mock.js'
const util = require('util')
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
util.inspect.defaultOptions = { depth: 1 }
const LogsApiLib = require('../../../src/adapters/logapi')
const mockData = require('../mocks/log-api-mock')
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const context = {}
let sandbox
@@ -3,10 +3,10 @@
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
const NodeMailer = require('../../../src/adapters/nodemailer')
import sinon from 'sinon'
import NodeMailer from '../../../src/adapters/nodemailer.js'
let sandbox
let uut
+3 -4
View File
@@ -1,7 +1,6 @@
const assert = require('chai').assert
const PassportLib = require('../../../src/adapters/passport')
const sinon = require('sinon')
import { assert } from 'chai'
import PassportLib from '../../../src/adapters/passport.js'
import sinon from 'sinon'
let uut
let sandbox
+7 -6
View File
@@ -2,16 +2,17 @@
Unit tests for the users Mongoose model.
*/
const assert = require('chai').assert
const sinon = require('sinon')
const mongoose = require('mongoose')
import { assert } from 'chai'
import sinon from 'sinon'
import mongoose from 'mongoose'
import User from '../../../src/adapters/localdb/models/users.js'
import config from '../../../config/index.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
const User = require('../../../src/adapters/localdb/models/users')
const config = require('../../../config')
describe('#User-Adapter', () => {
// let uut
let sandbox
+344
View File
@@ -0,0 +1,344 @@
/*
Unit tests for the Wallet Adapter library.
*/
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import BchWallet from 'minimal-slp-wallet'
import fs from 'fs'
// Local libraries
import WalletAdapter from '../../../src/adapters/wallet.adapter.js'
import { MockBchWallet } from '../mocks/adapters/wallet.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// Global constants
const testWalletFile = `${__dirname.toString()}test-wallet.json`
describe('#wallet', () => {
let uut
let sandbox
before(() => {
// Delete the test file if it exists.
try {
deleteFile(testWalletFile)
} catch (err) { }
})
beforeEach(() => {
uut = new WalletAdapter()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
after(() => {
// Delete the test file if it exists.
try {
deleteFile(testWalletFile)
} catch (err) { }
})
describe('#_instanceWallet', () => {
it('should create a wallet given a mnemonic', async () => {
const mnemonic = 'wagon tray learn flat erase laugh lonely rug check captain jacket morning'
const result = await uut._instanceWallet(mnemonic)
// console.log('result: ', result)
assert.equal(result.walletInfo.mnemonic, mnemonic)
})
})
describe('#openWallet', () => {
it('should create a new wallet when wallet file does not exist', async () => {
// Mock dependencies
uut.BchWallet = MockBchWallet
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const result = await uut.openWallet()
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
assert.property(result, 'cashAddress')
assert.property(result, 'address')
assert.property(result, 'slpAddress')
assert.property(result, 'legacyAddress')
assert.property(result, 'hdPath')
})
it('should open existing wallet file', async () => {
// This test case uses the file created in the previous test case.
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const result = await uut.openWallet()
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
assert.property(result, 'cashAddress')
assert.property(result, 'address')
assert.property(result, 'slpAddress')
assert.property(result, 'legacyAddress')
assert.property(result, 'hdPath')
})
it('should catch and throw an error', async () => {
try {
// Force an error
uut.config.walletFile = ''
uut.BchWallet = () => {
}
await uut.openWallet()
// console.log('result: ', result)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'this.BchWallet is not a constructor')
}
})
})
describe('#instanceWalletWithoutInitialization', () => {
it('should create an instance of BchWallet', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
uut.config.authPass = 'fake-auth-pass'
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const result = await uut.instanceWalletWithoutInitialization(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, '_instanceWallet').rejects(new Error('test error'))
await uut.instanceWalletWithoutInitialization()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
// Force desired code path
uut.config.walletInterface = 'web2'
const result = await uut.instanceWalletWithoutInitialization(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should generate wallet from mnemonic in config', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const originalConfig = uut.config.mnemonic
uut.config.mnemonic = walletData.mnemonic
const result = await uut.instanceWalletWithoutInitialization({})
// console.log('result: ', result)
uut.config.mnemonic = originalConfig
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
})
describe('#instanceWallet', () => {
it('should create an instance of BchWallet', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const result = await uut.instanceWallet(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'instanceWalletWithoutInitialization').rejects(new Error('test error'))
await uut.instanceWallet()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
// Force desired code path
uut.config.useFullStackCash = true
const result = await uut.instanceWallet(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
})
describe('#incrementNextAddress', () => {
it('should increment the nextAddress property', async () => {
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
const result = await uut.incrementNextAddress()
assert.equal(result, 2)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
await uut.incrementNextAddress()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#getKeyPair', () => {
it('should return an object with a key pair', async () => {
// Ensure we open the test file, not the production wallet file.
// uut.WALLET_FILE = testWalletFile
uut.config.walletFile = testWalletFile
const result = await uut.getKeyPair()
// console.log('result: ', result)
assert.property(result, 'cashAddress')
assert.property(result, 'wif')
assert.property(result, 'hdIndex')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(uut, 'incrementNextAddress')
.rejects(new Error('test error'))
await uut.getKeyPair()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#optimize', () => {
it('should call the wallet optimize function', async () => {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
sandbox.stub(uut.bchWallet, 'optimize').resolves({ bchUtxoCnt: 10 })
const result = await uut.optimize()
assert.equal(result, true)
})
})
describe('#getBalance', () => {
it('should get the balance for the wallet', async () => {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
// Mock dependencies and force desired code path
sandbox.stub(uut.bchWallet, 'getBalance').resolves(41012)
sandbox.stub(uut.bchWallet, 'listTokens').resolves([{
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
ticker: 'PSF',
name: 'Permissionless Software Foundation',
decimals: 8,
tokenType: 1,
url: 'psfoundation.cash',
qty: 2
}])
const result = await uut.getBalance()
// console.log('result: ', result)
// Assert the expected properties exist and have the expected values.
assert.equal(result.satBalance, 41012)
assert.equal(result.psfBalance, 2)
assert.equal(result.success, true)
})
})
})
const deleteFile = (filepath) => {
try {
// Delete state if exist
fs.unlinkSync(filepath)
} catch (err) {
// console.error('Error trying to delete file: ', err)
}
}
+3 -4
View File
@@ -1,7 +1,6 @@
const assert = require('chai').assert
const { Wlogger } = require('../../../src/adapters/wlogger')
const sinon = require('sinon')
import { assert } from 'chai'
import { Wlogger } from '../../../src/adapters/wlogger.js'
import sinon from 'sinon'
let uut
let sandbox
+5 -2
View File
@@ -4,9 +4,9 @@
// Public npm libraries
// const assert = require('chai').assert
const sinon = require('sinon')
import sinon from 'sinon'
const Controllers = require('../../../src/controllers')
import Controllers from '../../../src/controllers/index.js'
describe('#Controllers', () => {
let uut
@@ -28,6 +28,9 @@ describe('#Controllers', () => {
attachRPCRouter: () => {}
}
// Mock the timer controllers
sandbox.stub(uut.timerControllers, 'startTimers').returns()
const app = {
use: () => {}
}
+11 -9
View File
@@ -3,19 +3,21 @@
*/
// Public npm libraries
const assert = require('chai').assert
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const { v4: uid } = require('uuid')
import { assert } from 'chai'
import jsonrpc from 'jsonrpc-lite'
import sinon from 'sinon'
import { v4 as uid } from 'uuid'
// Local libraries.
import JSONRPC from '../../../../src/controllers/json-rpc/index.js'
import adapters from '../../mocks/adapters/index.js'
import UseCasesMock from '../../mocks/use-cases/index.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries.
const JSONRPC = require('../../../../src/controllers/json-rpc')
const adapters = require('../../mocks/adapters')
const UseCasesMock = require('../../mocks/use-cases')
describe('#JSON RPC', () => {
let uut
let sandbox
@@ -5,18 +5,20 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
import jsonrpc from 'jsonrpc-lite'
import sinon from 'sinon'
import { assert } from 'chai'
import { v4 as uid } from 'uuid'
// Local libraries
import Validators from '../../../../src/controllers/json-rpc/validators.js'
import adapters from '../../mocks/adapters/index.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const Validators = require('../../../../src/controllers/json-rpc/validators')
const adapters = require('../../mocks/adapters')
describe('#validators', () => {
let uut
let sandbox
@@ -5,15 +5,16 @@
*/
// Public npm libraries
const sinon = require('sinon')
const assert = require('chai').assert
import sinon from 'sinon'
import { assert } from 'chai'
// Local libraries
import RateLimit from '../../../../src/controllers/json-rpc/rate-limit.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const RateLimit = require('../../../../src/controllers/json-rpc/rate-limit')
describe('#rate-limit', () => {
let uut
let sandbox
@@ -3,11 +3,12 @@
*/
// Public npm libraries
const sinon = require('sinon')
const assert = require('chai').assert
import sinon from 'sinon'
import { assert } from 'chai'
// Local libraries
const AboutRPC = require('../../../../src/controllers/json-rpc/about')
import AboutRPC from '../../../../src/controllers/json-rpc/about/index.js'
describe('#AboutRPC', () => {
let uut
@@ -3,20 +3,22 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
import jsonrpc from 'jsonrpc-lite'
import sinon from 'sinon'
import { assert } from 'chai'
import { v4 as uid } from 'uuid'
// Local libraries
import AuthRPC from '../../../../src/controllers/json-rpc/auth/index.js'
import RateLimit from '../../../../src/controllers/json-rpc/rate-limit.js'
import adapters from '../../mocks/adapters/index.js'
import UseCasesMock from '../../mocks/use-cases/index.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const AuthRPC = require('../../../../src/controllers/json-rpc/auth')
const RateLimit = require('../../../../src/controllers/json-rpc/rate-limit')
const adapters = require('../../mocks/adapters')
const UseCasesMock = require('../../mocks/use-cases')
describe('#AuthRPC', () => {
let uut
let sandbox
@@ -3,19 +3,21 @@
*/
// Public npm libraries
const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const assert = require('chai').assert
const { v4: uid } = require('uuid')
import jsonrpc from 'jsonrpc-lite'
import sinon from 'sinon'
import { assert } from 'chai'
import { v4 as uid } from 'uuid'
// Local libraries
import UserRPC from '../../../../src/controllers/json-rpc/users/index.js'
import adapters from '../../mocks/adapters/index.js'
import UseCasesMock from '../../mocks/use-cases/index.js'
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries
const UserRPC = require('../../../../src/controllers/json-rpc/users')
const adapters = require('../../mocks/adapters')
const UseCasesMock = require('../../mocks/use-cases')
describe('#UserRPC', () => {
let uut
let sandbox
@@ -3,21 +3,25 @@
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
// const app = require('../../../mocks/app-mock')
const AuthRESTController = require('../../../../../src/controllers/rest-api/auth/controller')
import AuthRESTController from '../../../../../src/controllers/rest-api/auth/controller.js'
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
let uut
let sandbox
let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Auth-REST-Router', () => {
// const testUser = {}
@@ -3,15 +3,19 @@
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
// const app = require('../../../mocks/app-mock')
const AuthRouter = require('../../../../../src/controllers/rest-api/auth')
import AuthRouter from '../../../../../src/controllers/rest-api/auth/index.js'
let uut
let sandbox
// let ctx
@@ -3,20 +3,21 @@
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
const BchController = require('../../../../../src/controllers/rest-api/bch/controller')
import BchController from '../../../../../src/controllers/rest-api/bch/controller.js'
import { context } from '../../../../unit/mocks/ctx-mock.js'
// import blah '../../../../unit/mocks/ctx-mock.js'
// console.log('blah: ', blah)
let uut
let sandbox
let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#BCH-REST-Controller', () => {
// const testUser = {}
@@ -27,7 +28,7 @@ describe('#BCH-REST-Controller', () => {
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
ctx = context()
})
afterEach(() => sandbox.restore())
@@ -177,6 +178,42 @@ describe('#BCH-REST-Controller', () => {
})
})
describe('#utxosBulk', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getUtxosBulk')
.rejects(new Error('test error'))
ctx.request.body = {
address: 'blah'
}
await uut.utxosBulk(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'getUtxosBulk').resolves({ status: 200 })
ctx.request.body = {
address: 'blah'
}
await uut.utxosBulk(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#broadcast', () => {
it('should return 422 status on arbitrary error', async () => {
try {

Some files were not shown because too many files have changed in this diff Show More