From 267d481ac6bbad034d2578ab90b34306dddecc15 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 11 May 2020 10:47:44 -0700 Subject: [PATCH 01/10] feat(ipfs): Added IPFS class for uploading and download files from IPFS --- .gitignore | 1 + package-lock.json | 213 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 + src/bch-js.js | 3 + src/ipfs.js | 71 ++++++++++++++++ test/unit/ipfs.js | 42 +++++++++ 6 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 src/ipfs.js create mode 100644 test/unit/ipfs.js diff --git a/.gitignore b/.gitignore index 9c7b6ab..26c642a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ wallet-info.txt wallet.json docs/ +coverage/ # This paragraph comes last. Force includes specific files. diff --git a/package-lock.json b/package-lock.json index 37b5fda..6b42185 100644 --- a/package-lock.json +++ b/package-lock.json @@ -633,6 +633,57 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "@types/tus-js-client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/tus-js-client/-/tus-js-client-1.8.0.tgz", + "integrity": "sha512-lWxu5+6qfyfwsW99GzUeJ9y9JeOSSLduKxgYMvaYM7sGTDKZsrIIHTUbHI2P016xhXtu9NxmUM3GrB4i14ie4A==" + }, + "@uppy/companion-client": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-1.4.4.tgz", + "integrity": "sha512-4M1mAIYLDDhNEO1Lphuf5+wZv9HxTbEhNEoQ9KaWLeKjfRmTMMoWMVqgtEjzwp8C5A3XU134+H4beSQTbxQneg==", + "requires": { + "namespace-emitter": "^2.0.1" + } + }, + "@uppy/core": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-1.10.4.tgz", + "integrity": "sha512-mORSXL4JyXHGo98u6u0sMzv3wtdI5jjHsqyThQAXx3LvctWE0EBrxE7vJqrXue2z/m9lovsZVhuzP2L7Qkuebg==", + "requires": { + "@uppy/store-default": "^1.2.1", + "@uppy/utils": "^2.4.4", + "cuid": "^2.1.1", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "preact": "8.2.9" + } + }, + "@uppy/store-default": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-1.2.1.tgz", + "integrity": "sha512-B7iKa9CMLTPgSu1lHQX+Z82NgxoBrzWfZg9WbZnvoJRHnoiwu8A3EFXPLq0OEslvJndBD4xPgUms0BPt4WbnWw==" + }, + "@uppy/tus": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-1.5.12.tgz", + "integrity": "sha512-jDiwsTRHnMfb83ZB8JdDD0uA9DX0K/FqenGMYBGTOlkCjAcXD3sI59DzEHwq2TtY3ZH7iaL3fTNQ7ExBWkwrqw==", + "requires": { + "@types/tus-js-client": "^1.8.0", + "@uppy/companion-client": "^1.4.4", + "@uppy/utils": "^2.4.4", + "tus-js-client": "^1.8.0" + } + }, + "@uppy/utils": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-2.4.4.tgz", + "integrity": "sha512-7A0uwK5Rf8XcKqlpNUZ5L5LmkHT5c0/UWjDJGwmzeCxp2lECgzsMC+4vgA6kT4sFzPFbLtUtxHi7ecFwow3NQQ==", + "requires": { + "lodash.throttle": "^4.1.1" + } + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -1713,6 +1764,26 @@ "text-hex": "1.0.x" } }, + "combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", + "requires": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + }, + "dependencies": { + "lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", + "requires": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + } + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2124,6 +2195,11 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "cuid": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", + "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -2133,6 +2209,11 @@ "array-find-index": "^1.0.1" } }, + "custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3339,8 +3420,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "external-editor": { "version": "2.2.0", @@ -4392,6 +4472,11 @@ "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true }, + "js-base64": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", + "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==" + }, "js-sha256": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", @@ -4578,6 +4663,46 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", + "requires": { + "lodash._stringtopath": "~4.8.0" + } + }, + "lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" + }, + "lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", + "requires": { + "lodash._basetostring": "~4.12.0" + } + }, "lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", @@ -4625,6 +4750,11 @@ "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -4926,6 +5056,14 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, + "mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha1-P4fDHprxpf1IX7nbE0Qosju7e6g=", + "requires": { + "wildcard": "^1.1.0" + } + }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", @@ -5100,6 +5238,11 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, + "namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -9304,6 +9447,11 @@ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" }, + "preact": { + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.9.tgz", + "integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -9350,6 +9498,22 @@ "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", "dev": true }, + "proper-lockfile": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz", + "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=", + "requires": { + "graceful-fs": "^4.1.2", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + } + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -9419,6 +9583,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz", "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==" }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", @@ -9679,6 +9848,11 @@ } } }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -10985,6 +11159,27 @@ "safe-buffer": "^5.0.1" } }, + "tus-js-client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-1.8.0.tgz", + "integrity": "sha512-qPX3TywqzxocTxUZtcS8X7Aik72SVMa0jKi4hWyfvRV+s9raVzzYGaP4MoJGaF0yOgm2+b6jXaVEHogxcJ8LGw==", + "requires": { + "buffer-from": "^0.1.1", + "combine-errors": "^3.0.3", + "extend": "^3.0.2", + "js-base64": "^2.4.9", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^2.0.1", + "url-parse": "^1.4.3" + }, + "dependencies": { + "buffer-from": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" + } + } + }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -11106,6 +11301,15 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/util/-/util-0.12.1.tgz", @@ -11213,6 +11417,11 @@ "bs58check": "<3.0.0" } }, + "wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha1-pwIEUwhNjNLv5wup02liY94XEKU=" + }, "windows-release": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", diff --git a/package.json b/package.json index ef84332..9798a66 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "test:integration:testnet": "mocha --timeout 30000 test/integration/testnet/", "test:integration:local": "RESTURL=http://localhost:3000/v3/ mocha --timeout 30000 test/integration", "test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet", + "test:temp": "nyc mocha --timeout 30000 test/unit/ipfs.js", "test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration", "coverage": "nyc report --reporter=text-lcov | coveralls", + "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", "docs": "./node_modules/.bin/apidoc -i src/ -o docs" }, "license": "MIT", @@ -24,6 +26,8 @@ "url": "git+https://github.com/christroutner/bch-js.git" }, "dependencies": { + "@uppy/core": "^1.10.4", + "@uppy/tus": "^1.5.12", "apidoc": "^0.17.7", "assert": "^2.0.0", "axios": "^0.19.0", diff --git a/src/bch-js.js b/src/bch-js.js index 7982ed0..4f4ad4c 100644 --- a/src/bch-js.js +++ b/src/bch-js.js @@ -30,6 +30,7 @@ const Socket = require("./socket") const Wallet = require("./wallet") const Schnorr = require("./schnorr") const SLP = require("./slp/slp") +const IPFS = require("./ipfs") const Blockbook = require("./blockbook") const OpenBazaar = require("./openbazaar") @@ -97,6 +98,8 @@ class BCHJS { this.SLP = new SLP(libConfig) this.SLP.HDNode = this.HDNode + + this.IPFS = new IPFS() } static BitboxShim() { diff --git a/src/ipfs.js b/src/ipfs.js new file mode 100644 index 0000000..d4a3970 --- /dev/null +++ b/src/ipfs.js @@ -0,0 +1,71 @@ +/* + This library interacts with the IPFS REST API at FullStack.cash for uploading + and downloading data from the IPFS network. +*/ + +const axios = require("axios") +const Uppy = require("@uppy/core") +const Tus = require("@uppy/tus") +const fs = require("fs") + +let _this + +class IPFS { + constructor(config) { + this.IPFS_API = process.env.IPFS_API + ? process.env.IPFS_API + : `https://ipfs-api.fullstack.cash` + + // Default options when calling axios. + this.axiosOptions = { + headers: { + // authorization: `Token ${this.apiToken}`, + timeout: 15000 // Timeout if the server does not respond in time. + } + } + + _this = this + + _this.axios = axios + _this.fs = fs + + // Initialize Uppy + _this.uppy = _this.initUppy + } + + // Initializes Uppy, which is used for file uploads. + initUppy() { + const uppy = Uppy({ + allowMultipleUploads: false, + meta: { test: "avatar" }, + debug: false, + restrictions: { + maxFileSize: null, + maxNumberOfFiles: 1, + minNumberOfFiles: 1, + allowedFileTypes: null //type of files allowed to load + } + }) + + uppy.use(Tus, { endpoint: `${_this.IPFS_API}/uppy-files` }) + + return uppy + } + + // Upload a file to the FullStack.cash IPFS server. If successful, it will + // return an object with an ID, a BCH address, and an amount of BCH to pay. + async upload(path) { + try { + // Ensure the file exists. + if (!_this.fs.existsSync(path)) + throw new Error(`Could not find this file: ${path}`) + + return true + } catch (err) { + console.error(`Error in bch-js/src/ipfs.js/upload()`) + throw err + } + } +} + +module.exports = IPFS diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js new file mode 100644 index 0000000..ea001e6 --- /dev/null +++ b/test/unit/ipfs.js @@ -0,0 +1,42 @@ +const assert = require("chai").assert +// const axios = require("axios") +const sinon = require("sinon") + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +// const mockData = require("./fixtures/electrumx-mock") + +describe(`#IPFS`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe("#initUppy", () => { + it("should initialize uppy", () => { + bchjs.IPFS.initUppy() + + // console.log(`bchjs.IPFS.uppy: `, bchjs.IPFS.uppy) + }) + }) + + describe("#upload", () => { + it("should throw an error if file does not exist", async () => { + try { + const path = "/non-existant-file" + + await bchjs.IPFS.upload(path) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + console.log(`err.message: ${err.message}`) + } + }) + + it("should do something", async () => { + const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` + + await bchjs.IPFS.upload(path) + }) + }) +}) From a91e6f192047109c7847fa94f8e839a3ec4f0619 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 11 May 2020 11:31:07 -0700 Subject: [PATCH 02/10] Saving progress before investigating blobs --- src/ipfs.js | 30 +++++++++++++++++++++++++++--- test/unit/ipfs.js | 17 ++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/ipfs.js b/src/ipfs.js index d4a3970..99898e8 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -14,7 +14,8 @@ class IPFS { constructor(config) { this.IPFS_API = process.env.IPFS_API ? process.env.IPFS_API - : `https://ipfs-api.fullstack.cash` + : // : `https://ipfs-api.fullstack.cash` + `http://localhost:5001` // Default options when calling axios. this.axiosOptions = { @@ -30,7 +31,7 @@ class IPFS { _this.fs = fs // Initialize Uppy - _this.uppy = _this.initUppy + _this.uppy = _this.initUppy() } // Initializes Uppy, which is used for file uploads. @@ -54,12 +55,35 @@ class IPFS { // Upload a file to the FullStack.cash IPFS server. If successful, it will // return an object with an ID, a BCH address, and an amount of BCH to pay. - async upload(path) { + async uploadFile(path) { try { // Ensure the file exists. if (!_this.fs.existsSync(path)) throw new Error(`Could not find this file: ${path}`) + // Read in the file. + const fileBuf = _this.fs.readFileSync(path) + + // Convert the node.js Buffer to a Blob + // https://stackoverflow.com/questions/14653349/node-js-can%C2%B4t-create-blobs + const blob = Uint8Array.from(fileBuf).buffer + + // Get the file name from the path. + const splitPath = path.split("/") + const fileName = splitPath[splitPath.length - 1] + + const id = _this.uppy.addFile({ + name: fileName, + data: blob, + source: "Local", + isRemote: false + }) + + console.log(`id: ${JSON.stringify(id, null, 2)}`) + + const upData = await _this.uppy.upload() + console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + return true } catch (err) { console.error(`Error in bch-js/src/ipfs.js/upload()`) diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index ea001e6..9d120ec 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -3,13 +3,20 @@ const assert = require("chai").assert const sinon = require("sinon") const BCHJS = require("../../src/bch-js") -const bchjs = new BCHJS() +// const bchjs = new BCHJS() +let bchjs // const mockData = require("./fixtures/electrumx-mock") describe(`#IPFS`, () => { let sandbox - beforeEach(() => (sandbox = sinon.createSandbox())) + + beforeEach(() => { + sandbox = sinon.createSandbox() + + bchjs = new BCHJS() + }) + afterEach(() => sandbox.restore()) describe("#initUppy", () => { @@ -20,12 +27,12 @@ describe(`#IPFS`, () => { }) }) - describe("#upload", () => { + describe("#uploadFile", () => { it("should throw an error if file does not exist", async () => { try { const path = "/non-existant-file" - await bchjs.IPFS.upload(path) + await bchjs.IPFS.uploadFile(path) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -36,7 +43,7 @@ describe(`#IPFS`, () => { it("should do something", async () => { const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` - await bchjs.IPFS.upload(path) + await bchjs.IPFS.uploadFile(path) }) }) }) From f314b64461624d726f23adbaa76e05b0c1515010 Mon Sep 17 00:00:00 2001 From: danielhumgon Date: Wed, 13 May 2020 14:06:18 -0400 Subject: [PATCH 03/10] feat(ipfs): Added IPFS file upload to bch-js --- src/ipfs.js | 16 ++++++++++++---- test/unit/ipfs.js | 18 ++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/ipfs.js b/src/ipfs.js index 99898e8..b2aafec 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -47,7 +47,6 @@ class IPFS { allowedFileTypes: null //type of files allowed to load } }) - uppy.use(Tus, { endpoint: `${_this.IPFS_API}/uppy-files` }) return uppy @@ -66,7 +65,7 @@ class IPFS { // Convert the node.js Buffer to a Blob // https://stackoverflow.com/questions/14653349/node-js-can%C2%B4t-create-blobs - const blob = Uint8Array.from(fileBuf).buffer + // const blob = Uint8Array.from(fileBuf).buffer // Get the file name from the path. const splitPath = path.split("/") @@ -74,7 +73,7 @@ class IPFS { const id = _this.uppy.addFile({ name: fileName, - data: blob, + data: fileBuf, source: "Local", isRemote: false }) @@ -82,7 +81,16 @@ class IPFS { console.log(`id: ${JSON.stringify(id, null, 2)}`) const upData = await _this.uppy.upload() - console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + + //console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + console.log(` + Files Successful : ${upData.successful.length} + Files Failed : ${upData.failed.length} + `) + + if (upData.failed.length) { + throw new Error('The file could not be uploaded') + } return true } catch (err) { diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 9d120ec..5aefc8e 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -36,14 +36,24 @@ describe(`#IPFS`, () => { assert.equal(true, false, "Unexpected result") } catch (err) { - console.log(`err.message: ${err.message}`) + //console.log(`err.message: ${err.message}`) + assert.include(err.message,`Could not find this file`) } }) - it("should do something", async () => { - const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` + it("should return true if the file is uploaded", async () => { + //const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` + try { + const path = `${__dirname}/ipfs.js` + const result = await bchjs.IPFS.uploadFile(path) - await bchjs.IPFS.uploadFile(path) + assert.isBoolean(result) + assert.isTrue(result) + + } catch (err) { + //console.log(err) + assert.equal(true, false, "Unexpected result") + } }) }) }) From 9dad04068ea7da44b959ff9aaef9d4cf2d969952 Mon Sep 17 00:00:00 2001 From: danielhumgon Date: Wed, 13 May 2020 14:48:07 -0400 Subject: [PATCH 04/10] fix(tests): Completing unit tests --- test/unit/ipfs.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 5aefc8e..32ec429 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -37,13 +37,25 @@ describe(`#IPFS`, () => { assert.equal(true, false, "Unexpected result") } catch (err) { //console.log(`err.message: ${err.message}`) - assert.include(err.message,`Could not find this file`) + assert.include(err.message, `Could not find this file`) } }) it("should return true if the file is uploaded", async () => { //const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` try { + const mock = { + successful: [ + { + id: 'file id' + } + ], + failed: [] + } + sandbox + .stub(bchjs.IPFS.uppy, 'upload') + .resolves(mock) + const path = `${__dirname}/ipfs.js` const result = await bchjs.IPFS.uploadFile(path) @@ -55,5 +67,32 @@ describe(`#IPFS`, () => { assert.equal(true, false, "Unexpected result") } }) + it("Should throw error if the file was not uploaded", async () => { + //const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` + try { + const mock = { + successful: [], + failed: [ + { + id: 'file id' + } + ] + } + sandbox + .stub(bchjs.IPFS.uppy, 'upload') + .resolves(mock) + + const path = `${__dirname}/ipfs.js` + await bchjs.IPFS.uploadFile(path) + + assert.equal(true, false, "Unexpected result") + + + } catch (err) { + //console.log(err) + assert.include(err.message, `The file could not be uploaded`) + + } + }) }) }) From e02a5d37e7da12342d96814635d535a14b3a0660 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 15 May 2020 19:49:25 -0700 Subject: [PATCH 05/10] Unit tests passing --- src/ipfs.js | 39 ++++++++++++++------- test/unit/fixtures/ipfs-mock.js | 47 +++++++++++++++++++++++++ test/unit/ipfs.js | 61 ++++++++++++++------------------- 3 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 test/unit/fixtures/ipfs-mock.js diff --git a/src/ipfs.js b/src/ipfs.js index b2aafec..45bcd77 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -14,8 +14,8 @@ class IPFS { constructor(config) { this.IPFS_API = process.env.IPFS_API ? process.env.IPFS_API - : // : `https://ipfs-api.fullstack.cash` - `http://localhost:5001` + : `http://localhost:5001` + // : `https://ipfs-api.fullstack.cash` // Default options when calling axios. this.axiosOptions = { @@ -82,19 +82,34 @@ class IPFS { const upData = await _this.uppy.upload() - //console.log(`upData: ${JSON.stringify(upData, null, 2)}`) - console.log(` - Files Successful : ${upData.successful.length} - Files Failed : ${upData.failed.length} - `) + // console.log(` + // Files Successful : ${upData.successful.length} + // Files Failed : ${upData.failed.length} + // `) - if (upData.failed.length) { - throw new Error('The file could not be uploaded') - } + if (upData.failed.length) + throw new Error("The file could not be uploaded") - return true + if (upData.successful.length) { + delete upData.successful[0].data + // console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + + const fileObj = { + schemaVersion: 1, + size: upData.successful[0].progress.bytesTotal, + fileId: upData.successful[0].id, + fileName: upData.successful[0].name, + fileExtension: upData.successful[0].extension + } + // console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) + + return fileObj + // return true + } + + return false } catch (err) { - console.error(`Error in bch-js/src/ipfs.js/upload()`) + console.error(`Error in bch-js/src/ipfs.js/upload(): `) throw err } } diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js new file mode 100644 index 0000000..1dfe18b --- /dev/null +++ b/test/unit/fixtures/ipfs-mock.js @@ -0,0 +1,47 @@ +/* + Mock data for IPFS unit tests. +*/ + +const uploadData = { + successful: [ + { + source: "Local", + id: "uppy-ipfs/js-1e-application/octet-stream", + name: "ipfs.js", + extension: "js", + meta: { + test: "avatar", + name: "ipfs.js", + type: "application/octet-stream" + }, + type: "application/octet-stream", + progress: { + uploadStarted: 1589596706524, + uploadComplete: true, + percentage: 100, + bytesUploaded: 2374, + bytesTotal: 2374 + }, + size: null, + isRemote: false, + remote: "", + tus: { + uploadUrl: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33" + }, + response: { + uploadURL: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33" + }, + uploadURL: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33", + isPaused: false + } + ], + failed: [], + uploadID: "cka90u4m300000fi0cldg6lrf" +} + +module.exports = { + uploadData +} diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 32ec429..535326e 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -1,12 +1,14 @@ +/* + Unit tests for the IPFS Class. +*/ + const assert = require("chai").assert -// const axios = require("axios") const sinon = require("sinon") const BCHJS = require("../../src/bch-js") -// const bchjs = new BCHJS() let bchjs -// const mockData = require("./fixtures/electrumx-mock") +const mockData = require("./fixtures/ipfs-mock") describe(`#IPFS`, () => { let sandbox @@ -41,57 +43,44 @@ describe(`#IPFS`, () => { } }) - it("should return true if the file is uploaded", async () => { - //const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` - try { - const mock = { - successful: [ - { - id: 'file id' - } - ], - failed: [] - } - sandbox - .stub(bchjs.IPFS.uppy, 'upload') - .resolves(mock) - - const path = `${__dirname}/ipfs.js` - const result = await bchjs.IPFS.uploadFile(path) - - assert.isBoolean(result) - assert.isTrue(result) - - } catch (err) { - //console.log(err) - assert.equal(true, false, "Unexpected result") - } - }) it("Should throw error if the file was not uploaded", async () => { - //const path = `/home/trout/work/personal/bch-js/test/unit/ipfs.js` try { const mock = { successful: [], failed: [ { - id: 'file id' + id: "file id" } ] } - sandbox - .stub(bchjs.IPFS.uppy, 'upload') - .resolves(mock) + sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) const path = `${__dirname}/ipfs.js` await bchjs.IPFS.uploadFile(path) assert.equal(true, false, "Unexpected result") - - } catch (err) { //console.log(err) assert.include(err.message, `The file could not be uploaded`) + } + }) + it("should return true if the file is uploaded", async () => { + try { + sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) + + const path = `${__dirname}/ipfs.js` + const result = await bchjs.IPFS.uploadFile(path) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "schemaVersion") + assert.property(result, "size") + assert.property(result, "fileId") + assert.property(result, "fileName") + assert.property(result, "fileExtension") + } catch (err) { + //console.log(err) + assert.equal(true, false, "Unexpected result") } }) }) From 83c1e7f449a878f9ac3a4bdb786ebd3194302249 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 16 May 2020 09:17:44 -0700 Subject: [PATCH 06/10] Added getPaymentInfo() to IPFS class --- src/ipfs.js | 21 +++++++++++++----- test/unit/fixtures/ipfs-mock.js | 26 ++++++++++++++++++++-- test/unit/ipfs.js | 38 ++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/ipfs.js b/src/ipfs.js index 45bcd77..d71cdd9 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -63,10 +63,6 @@ class IPFS { // Read in the file. const fileBuf = _this.fs.readFileSync(path) - // Convert the node.js Buffer to a Blob - // https://stackoverflow.com/questions/14653349/node-js-can%C2%B4t-create-blobs - // const blob = Uint8Array.from(fileBuf).buffer - // Get the file name from the path. const splitPath = path.split("/") const fileName = splitPath[splitPath.length - 1] @@ -92,7 +88,7 @@ class IPFS { if (upData.successful.length) { delete upData.successful[0].data - // console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + console.log(`upData: ${JSON.stringify(upData, null, 2)}`) const fileObj = { schemaVersion: 1, @@ -113,6 +109,21 @@ class IPFS { throw err } } + + // Call this after uploadFile(). + async getPaymentInfo(fileObj) { + try { + const fileData = await _this.axios.post(`${this.IPFS_API}/files`, { + file: fileObj + }) + // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) + + return fileData.data + } catch (err) { + console.error(`Error in getPaymentInfo()`) + throw err + } + } } module.exports = IPFS diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js index 1dfe18b..8970768 100644 --- a/test/unit/fixtures/ipfs-mock.js +++ b/test/unit/fixtures/ipfs-mock.js @@ -42,6 +42,28 @@ const uploadData = { uploadID: "cka90u4m300000fi0cldg6lrf" } -module.exports = { - uploadData +const paymentInfo = { + success: true, + hostingCostBCH: 0.00004201, + hostingCostUSD: 0.01, + file: { + payloadLink: "", + hasBeenPaid: false, + _id: "5ebf5d04cba8b038394e0d62", + schemaVersion: 1, + size: 2374, + fileId: "uppy-ipfs/js-1e-application/octet-stream", + fileName: "ipfs.js", + fileExtension: "js", + createdTimestamp: "1589599492.952", + hostingCost: 4201, + walletIndex: 36, + bchAddr: "bchtest:qqymyk4zfvnw8rh6hcvl922ewudkyrn9jvk6gtuae6", + __v: 0 + } +} + +module.exports = { + uploadData, + paymentInfo } diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 535326e..506ace8 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -65,7 +65,7 @@ describe(`#IPFS`, () => { } }) - it("should return true if the file is uploaded", async () => { + it("should return file object if the file is uploaded", async () => { try { sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) @@ -84,4 +84,40 @@ describe(`#IPFS`, () => { } }) }) + + describe("#getPaymentInfo", () => { + it("should return payment information", async () => { + sandbox + .stub(bchjs.IPFS.axios, "post") + .resolves({ data: mockData.paymentInfo }) + + const fileObj = { + schemaVersion: 1, + size: 2374, + fileId: "uppy-ipfs/js-1e-application/octet-stream", + fileName: "ipfs.js", + fileExtension: "js" + } + + const result = await bchjs.IPFS.getPaymentInfo(fileObj) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.property(result, "hostingCostBCH") + assert.property(result, "hostingCostUSD") + assert.property(result, "file") + assert.property(result.file, "payloadLink") + assert.property(result.file, "hasBeenPaid") + assert.property(result.file, "_id") + assert.property(result.file, "schemaVersion") + assert.property(result.file, "size") + assert.property(result.file, "fileId") + assert.property(result.file, "fileName") + assert.property(result.file, "fileExtension") + assert.property(result.file, "createdTimestamp") + assert.property(result.file, "hostingCost") + assert.property(result.file, "walletIndex") + assert.property(result.file, "bchAddr") + }) + }) }) From e8f59ea32796ab06b5cb1753b33318003893ec6d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 20 May 2020 10:50:10 -0700 Subject: [PATCH 07/10] new(ipfs.createFileMode): Refactored for changes to ipfs-upload-server --- package-lock.json | 296 ++++++++++++++++++++++++-------- src/ipfs.js | 43 +++++ test/unit/fixtures/ipfs-mock.js | 23 ++- test/unit/ipfs.js | 159 +++++++++++------ 4 files changed, 392 insertions(+), 129 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b791e3..7622dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -616,6 +616,12 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, "@types/node": { "version": "11.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", @@ -1889,33 +1895,58 @@ } }, "conventional-changelog-writer": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz", - "integrity": "sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", + "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", "dev": true, "requires": { "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.2", + "conventional-commits-filter": "^2.0.6", "dateformat": "^3.0.0", - "handlebars": "^4.4.0", + "handlebars": "^4.7.6", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.15", - "meow": "^5.0.0", + "meow": "^7.0.0", "semver": "^6.0.0", "split": "^1.0.0", "through2": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, "conventional-commits-filter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz", - "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz", + "integrity": "sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw==", "dev": true, "requires": { "lodash.ismatch": "^4.4.0", @@ -1923,73 +1954,156 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { - "p-try": "^1.0.0" + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, "semver": { @@ -1998,6 +2112,15 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", @@ -2007,13 +2130,34 @@ "readable-stream": "2 || 3" } }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } } } @@ -3911,6 +4055,12 @@ } } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -4586,6 +4736,12 @@ "safe-buffer": "^5.1.0" } }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, "klaw-sync": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-2.1.0.tgz", @@ -5077,6 +5233,12 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -11248,14 +11410,13 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-r5ImcL6QyzQGVimQoov3aL2ZScywrOgBXGndbWrdehKoSvGe/RmiE5Jpw/v+GvxODt6l2tpBXwA7n+qZVlHBMA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" + "commander": "~2.20.3" }, "dependencies": { "commander": { @@ -11264,13 +11425,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true } } }, diff --git a/src/ipfs.js b/src/ipfs.js index d71cdd9..8fec092 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -52,6 +52,7 @@ class IPFS { return uppy } + // DEPRECATED // Upload a file to the FullStack.cash IPFS server. If successful, it will // return an object with an ID, a BCH address, and an amount of BCH to pay. async uploadFile(path) { @@ -110,6 +111,7 @@ class IPFS { } } + // DEPRECATED // Call this after uploadFile(). async getPaymentInfo(fileObj) { try { @@ -124,6 +126,47 @@ class IPFS { throw err } } + + async createFileModel(path) { + try { + // Ensure the file exists. + if (!_this.fs.existsSync(path)) + throw new Error(`Could not find this file: ${path}`) + + // Read in the file. + const fileBuf = _this.fs.readFileSync(path) + // console.log(`fileBuf: `, fileBuf) + + // console.log(`Buffer length: ${fileBuf.length}`) + + // Get the file name from the path. + const splitPath = path.split("/") + const fileName = splitPath[splitPath.length - 1] + + // Get the file extension. + const splitExt = fileName.split(".") + const fileExt = splitExt[splitExt.length - 1] + + const fileObj = { + schemaVersion: 1, + size: fileBuf.length, + // fileId: upData.successful[0].id, + fileName: fileName, + fileExtension: fileExt + } + // console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) + + const fileData = await _this.axios.post(`${this.IPFS_API}/files`, { + file: fileObj + }) + // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) + + return fileData.data + } catch (err) { + console.error(`Error in createFileModel()`) + throw err + } + } } module.exports = IPFS diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js index 8970768..b87621d 100644 --- a/test/unit/fixtures/ipfs-mock.js +++ b/test/unit/fixtures/ipfs-mock.js @@ -63,7 +63,28 @@ const paymentInfo = { } } +mockNewFileModel = { + success: true, + hostingCostBCH: 0.00004197, + hostingCostUSD: 0.01, + file: { + payloadLink: "", + hasBeenPaid: false, + _id: "5ec562319bfacc745e8d8a52", + schemaVersion: 1, + size: 4458, + fileName: "ipfs.js", + fileExtension: "js", + createdTimestamp: "1589994033.655", + hostingCost: 4196, + walletIndex: 49, + bchAddr: "bchtest:qzrpkevu7h2ayfa4rjx08r5elvpfu72dg567x3mh3c", + __v: 0 + } +} + module.exports = { uploadData, - paymentInfo + paymentInfo, + mockNewFileModel } diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 506ace8..b8835bc 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -29,12 +29,104 @@ describe(`#IPFS`, () => { }) }) - describe("#uploadFile", () => { + // describe("#uploadFile", () => { + // it("should throw an error if file does not exist", async () => { + // try { + // const path = "/non-existant-file" + // + // await bchjs.IPFS.uploadFile(path) + // + // assert.equal(true, false, "Unexpected result") + // } catch (err) { + // //console.log(`err.message: ${err.message}`) + // assert.include(err.message, `Could not find this file`) + // } + // }) + // + // it("Should throw error if the file was not uploaded", async () => { + // try { + // const mock = { + // successful: [], + // failed: [ + // { + // id: "file id" + // } + // ] + // } + // sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) + // + // const path = `${__dirname}/ipfs.js` + // await bchjs.IPFS.uploadFile(path) + // + // assert.equal(true, false, "Unexpected result") + // } catch (err) { + // //console.log(err) + // assert.include(err.message, `The file could not be uploaded`) + // } + // }) + // + // it("should return file object if the file is uploaded", async () => { + // try { + // sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) + // + // const path = `${__dirname}/ipfs.js` + // const result = await bchjs.IPFS.uploadFile(path) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, "schemaVersion") + // assert.property(result, "size") + // assert.property(result, "fileId") + // assert.property(result, "fileName") + // assert.property(result, "fileExtension") + // } catch (err) { + // //console.log(err) + // assert.equal(true, false, "Unexpected result") + // } + // }) + // }) + + // describe("#getPaymentInfo", () => { + // it("should return payment information", async () => { + // sandbox + // .stub(bchjs.IPFS.axios, "post") + // .resolves({ data: mockData.paymentInfo }) + // + // const fileObj = { + // schemaVersion: 1, + // size: 2374, + // fileId: "uppy-ipfs/js-1e-application/octet-stream", + // fileName: "ipfs.js", + // fileExtension: "js" + // } + // + // const result = await bchjs.IPFS.getPaymentInfo(fileObj) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.property(result, "success") + // assert.property(result, "hostingCostBCH") + // assert.property(result, "hostingCostUSD") + // assert.property(result, "file") + // assert.property(result.file, "payloadLink") + // assert.property(result.file, "hasBeenPaid") + // assert.property(result.file, "_id") + // assert.property(result.file, "schemaVersion") + // assert.property(result.file, "size") + // assert.property(result.file, "fileId") + // assert.property(result.file, "fileName") + // assert.property(result.file, "fileExtension") + // assert.property(result.file, "createdTimestamp") + // assert.property(result.file, "hostingCost") + // assert.property(result.file, "walletIndex") + // assert.property(result.file, "bchAddr") + // }) + // }) + + describe("#createFileModel", () => { it("should throw an error if file does not exist", async () => { try { const path = "/non-existant-file" - await bchjs.IPFS.uploadFile(path) + await bchjs.IPFS.createFileModel(path) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -43,75 +135,28 @@ describe(`#IPFS`, () => { } }) - it("Should throw error if the file was not uploaded", async () => { - try { - const mock = { - successful: [], - failed: [ - { - id: "file id" - } - ] - } - sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) + it("should create a new file model", async () => { + const path = `${__dirname}/ipfs.js` - const path = `${__dirname}/ipfs.js` - await bchjs.IPFS.uploadFile(path) - - assert.equal(true, false, "Unexpected result") - } catch (err) { - //console.log(err) - assert.include(err.message, `The file could not be uploaded`) - } - }) - - it("should return file object if the file is uploaded", async () => { - try { - sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) - - const path = `${__dirname}/ipfs.js` - const result = await bchjs.IPFS.uploadFile(path) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, "schemaVersion") - assert.property(result, "size") - assert.property(result, "fileId") - assert.property(result, "fileName") - assert.property(result, "fileExtension") - } catch (err) { - //console.log(err) - assert.equal(true, false, "Unexpected result") - } - }) - }) - - describe("#getPaymentInfo", () => { - it("should return payment information", async () => { sandbox .stub(bchjs.IPFS.axios, "post") - .resolves({ data: mockData.paymentInfo }) + .resolves({ data: mockData.mockNewFileModel }) - const fileObj = { - schemaVersion: 1, - size: 2374, - fileId: "uppy-ipfs/js-1e-application/octet-stream", - fileName: "ipfs.js", - fileExtension: "js" - } - - const result = await bchjs.IPFS.getPaymentInfo(fileObj) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) + const result = await bchjs.IPFS.createFileModel(path) + console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, "success") + assert.equal(result.success, true) + assert.property(result, "hostingCostBCH") assert.property(result, "hostingCostUSD") assert.property(result, "file") + assert.property(result.file, "payloadLink") assert.property(result.file, "hasBeenPaid") assert.property(result.file, "_id") assert.property(result.file, "schemaVersion") assert.property(result.file, "size") - assert.property(result.file, "fileId") assert.property(result.file, "fileName") assert.property(result.file, "fileExtension") assert.property(result.file, "createdTimestamp") From fa984e0fa6692487411033a97043f4fee6650e23 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 21 May 2020 19:16:15 -0700 Subject: [PATCH 08/10] fix(ipfs): Improved functions and unit tests, addded e2e test --- src/ipfs.js | 43 ++++---- test/e2e/ipfs/ipfs-e2e.js | 24 +++++ test/unit/fixtures/ipfs-mock.js | 3 +- test/unit/ipfs.js | 168 ++++++++++++++------------------ 4 files changed, 116 insertions(+), 122 deletions(-) create mode 100644 test/e2e/ipfs/ipfs-e2e.js diff --git a/src/ipfs.js b/src/ipfs.js index 8fec092..eb44600 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -55,12 +55,18 @@ class IPFS { // DEPRECATED // Upload a file to the FullStack.cash IPFS server. If successful, it will // return an object with an ID, a BCH address, and an amount of BCH to pay. - async uploadFile(path) { + async uploadFile(path, modelId) { try { // Ensure the file exists. if (!_this.fs.existsSync(path)) throw new Error(`Could not find this file: ${path}`) + if (!modelId) { + throw new Error( + `Must include a file model ID in order to upload a file.` + ) + } + // Read in the file. const fileBuf = _this.fs.readFileSync(path) @@ -68,33 +74,32 @@ class IPFS { const splitPath = path.split("/") const fileName = splitPath[splitPath.length - 1] + // Prepare the upload object. Get a file ID from Uppy. const id = _this.uppy.addFile({ name: fileName, data: fileBuf, source: "Local", isRemote: false }) + // console.log(`id: ${JSON.stringify(id, null, 2)}`) - console.log(`id: ${JSON.stringify(id, null, 2)}`) + // Add the model ID, required to be allowed to upload the file. + _this.uppy.setFileMeta(id, { fileModelId: modelId }) + // Upload the file to the server. const upData = await _this.uppy.upload() - // console.log(` - // Files Successful : ${upData.successful.length} - // Files Failed : ${upData.failed.length} - // `) - if (upData.failed.length) throw new Error("The file could not be uploaded") if (upData.successful.length) { delete upData.successful[0].data - console.log(`upData: ${JSON.stringify(upData, null, 2)}`) + // console.log(`upData: ${JSON.stringify(upData, null, 2)}`) const fileObj = { schemaVersion: 1, size: upData.successful[0].progress.bytesTotal, - fileId: upData.successful[0].id, + fileId: upData.successful[0].meta.fileModelId, fileName: upData.successful[0].name, fileExtension: upData.successful[0].extension } @@ -111,22 +116,9 @@ class IPFS { } } - // DEPRECATED - // Call this after uploadFile(). - async getPaymentInfo(fileObj) { - try { - const fileData = await _this.axios.post(`${this.IPFS_API}/files`, { - file: fileObj - }) - // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) - - return fileData.data - } catch (err) { - console.error(`Error in getPaymentInfo()`) - throw err - } - } - + // Creates a new model on the server. If successful, will return an object + // with file data. That object contains a BCH address, payment amount, and + // _id required to be able to upload a file. async createFileModel(path) { try { // Ensure the file exists. @@ -150,7 +142,6 @@ class IPFS { const fileObj = { schemaVersion: 1, size: fileBuf.length, - // fileId: upData.successful[0].id, fileName: fileName, fileExtension: fileExt } diff --git a/test/e2e/ipfs/ipfs-e2e.js b/test/e2e/ipfs/ipfs-e2e.js new file mode 100644 index 0000000..7111a5e --- /dev/null +++ b/test/e2e/ipfs/ipfs-e2e.js @@ -0,0 +1,24 @@ +/* + An end-to-end test for testing the functionality of uploading a file to + IPFS is working. +*/ + +process.env.IPFS_API = `http://localhost:5001` +// process.env.IPFS_API = `https://ipfs-api.fullstack.cash` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#IPFS`, () => { + it("should upload a file to the server", async () => { + const path = `${__dirname}/ipfs-e2e.js` + + const fileModel = await bchjs.IPFS.createFileModel(path) + console.log(`fileModel: ${JSON.stringify(fileModel, null, 2)}`) + + const fileId = fileModel.file._id + + const fileObj = await bchjs.IPFS.uploadFile(path, fileId) + console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) + }).timeout(30000) +}) diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js index b87621d..0a04e24 100644 --- a/test/unit/fixtures/ipfs-mock.js +++ b/test/unit/fixtures/ipfs-mock.js @@ -12,7 +12,8 @@ const uploadData = { meta: { test: "avatar", name: "ipfs.js", - type: "application/octet-stream" + type: "application/octet-stream", + fileModelId: "5ec562319bfacc745e8d8a52" }, type: "application/octet-stream", progress: { diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index b8835bc..11f5604 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -24,103 +24,9 @@ describe(`#IPFS`, () => { describe("#initUppy", () => { it("should initialize uppy", () => { bchjs.IPFS.initUppy() - - // console.log(`bchjs.IPFS.uppy: `, bchjs.IPFS.uppy) }) }) - // describe("#uploadFile", () => { - // it("should throw an error if file does not exist", async () => { - // try { - // const path = "/non-existant-file" - // - // await bchjs.IPFS.uploadFile(path) - // - // assert.equal(true, false, "Unexpected result") - // } catch (err) { - // //console.log(`err.message: ${err.message}`) - // assert.include(err.message, `Could not find this file`) - // } - // }) - // - // it("Should throw error if the file was not uploaded", async () => { - // try { - // const mock = { - // successful: [], - // failed: [ - // { - // id: "file id" - // } - // ] - // } - // sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) - // - // const path = `${__dirname}/ipfs.js` - // await bchjs.IPFS.uploadFile(path) - // - // assert.equal(true, false, "Unexpected result") - // } catch (err) { - // //console.log(err) - // assert.include(err.message, `The file could not be uploaded`) - // } - // }) - // - // it("should return file object if the file is uploaded", async () => { - // try { - // sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) - // - // const path = `${__dirname}/ipfs.js` - // const result = await bchjs.IPFS.uploadFile(path) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, "schemaVersion") - // assert.property(result, "size") - // assert.property(result, "fileId") - // assert.property(result, "fileName") - // assert.property(result, "fileExtension") - // } catch (err) { - // //console.log(err) - // assert.equal(true, false, "Unexpected result") - // } - // }) - // }) - - // describe("#getPaymentInfo", () => { - // it("should return payment information", async () => { - // sandbox - // .stub(bchjs.IPFS.axios, "post") - // .resolves({ data: mockData.paymentInfo }) - // - // const fileObj = { - // schemaVersion: 1, - // size: 2374, - // fileId: "uppy-ipfs/js-1e-application/octet-stream", - // fileName: "ipfs.js", - // fileExtension: "js" - // } - // - // const result = await bchjs.IPFS.getPaymentInfo(fileObj) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.property(result, "success") - // assert.property(result, "hostingCostBCH") - // assert.property(result, "hostingCostUSD") - // assert.property(result, "file") - // assert.property(result.file, "payloadLink") - // assert.property(result.file, "hasBeenPaid") - // assert.property(result.file, "_id") - // assert.property(result.file, "schemaVersion") - // assert.property(result.file, "size") - // assert.property(result.file, "fileId") - // assert.property(result.file, "fileName") - // assert.property(result.file, "fileExtension") - // assert.property(result.file, "createdTimestamp") - // assert.property(result.file, "hostingCost") - // assert.property(result.file, "walletIndex") - // assert.property(result.file, "bchAddr") - // }) - // }) - describe("#createFileModel", () => { it("should throw an error if file does not exist", async () => { try { @@ -143,7 +49,7 @@ describe(`#IPFS`, () => { .resolves({ data: mockData.mockNewFileModel }) const result = await bchjs.IPFS.createFileModel(path) - console.log(`result: ${JSON.stringify(result, null, 2)}`) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, "success") assert.equal(result.success, true) @@ -165,4 +71,76 @@ describe(`#IPFS`, () => { assert.property(result.file, "bchAddr") }) }) + + describe("#uploadFile", () => { + it("should throw an error if file does not exist", async () => { + try { + const path = "/non-existant-file" + + await bchjs.IPFS.uploadFile(path) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include(err.message, `Could not find this file`) + } + }) + + it("should throw an error if modelId is not included", async () => { + try { + const path = `${__dirname}/ipfs.js` + + await bchjs.IPFS.uploadFile(path) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include(err.message, `Must include a file model ID`) + } + }) + + it("Should throw error if the file was not uploaded", async () => { + try { + const mock = { + successful: [], + failed: [ + { + id: "file id" + } + ] + } + sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) + + const path = `${__dirname}/ipfs.js` + await bchjs.IPFS.uploadFile(path, "5ec562319bfacc745e8d8a52") + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(err) + assert.include(err.message, `The file could not be uploaded`) + } + }) + + it("should return file object if the file is uploaded", async () => { + try { + sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) + + const path = `${__dirname}/ipfs.js` + const result = await bchjs.IPFS.uploadFile( + path, + "5ec562319bfacc745e8d8a52" + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "schemaVersion") + assert.property(result, "size") + assert.property(result, "fileId") + assert.property(result, "fileName") + assert.property(result, "fileExtension") + } catch (err) { + //console.log(err) + assert.equal(true, false, "Unexpected result") + } + }) + }) }) From 8220fee639bb378b8fb05766b0d0bce1e0f55f4b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 21 May 2020 19:57:49 -0700 Subject: [PATCH 09/10] fix(ipfs): Adding getStatus() to check file upload status --- src/ipfs.js | 27 ++++++++++++++++++ test/unit/fixtures/ipfs-mock.js | 38 ++++++++++++++++++++++++- test/unit/ipfs.js | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/ipfs.js b/src/ipfs.js index eb44600..6214b29 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -158,6 +158,33 @@ class IPFS { throw err } } + + // Gets the status of the uploaded file. Will return an object that indicates + // the payment status. If the file is paid, it should contain an IPFS hash. + async getStatus(modelId) { + try { + if (!modelId) throw new Error(`Must include a file model ID.`) + + const fileData = await _this.axios.get( + `${this.IPFS_API}/files/${modelId}` + ) + // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) + + const fileObj = { + hasBeenPaid: fileData.data.file.hasBeenPaid, + satCost: fileData.data.file.hostingCost, + bchAddr: fileData.data.file.bchAddr, + ipfsHash: fileData.data.file.payloadLink, + fileId: fileData.data.file._id, + fileName: fileData.data.file.fileName + } + + return fileObj + } catch (err) { + console.error(`Error in getStatus()`) + throw err + } + } } module.exports = IPFS diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js index 0a04e24..25f628a 100644 --- a/test/unit/fixtures/ipfs-mock.js +++ b/test/unit/fixtures/ipfs-mock.js @@ -84,8 +84,44 @@ mockNewFileModel = { } } +const unpaidFileData = { + file: { + payloadLink: "", + hasBeenPaid: false, + _id: "5ec7392c2acfe57aa62e945a", + schemaVersion: 1, + size: 726, + fileName: "ipfs-e2e.js", + fileExtension: "js", + createdTimestamp: "1590114604.986", + hostingCost: 4403, + walletIndex: 56, + bchAddr: "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk", + __v: 0 + } +} + +const paidFileData = { + file: { + payloadLink: "QmRDHPhY5hCNVRMVQvS2H9uty8P1skdwgLaHpUAkEvsjcE", + hasBeenPaid: true, + _id: "5ec7392c2acfe57aa62e945a", + schemaVersion: 1, + size: 726, + fileName: "ipfs-e2e.js", + fileExtension: "js", + createdTimestamp: "1590114604.986", + hostingCost: 4403, + walletIndex: 56, + bchAddr: "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk", + __v: 0 + } +} + module.exports = { uploadData, paymentInfo, - mockNewFileModel + mockNewFileModel, + unpaidFileData, + paidFileData } diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index 11f5604..cc95458 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -143,4 +143,53 @@ describe(`#IPFS`, () => { } }) }) + + describe("#getStatus", () => { + it("should throw an error if modelId is not included", async () => { + try { + await bchjs.IPFS.getStatus() + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include(err.message, `Must include a file model ID`) + } + }) + + it("should get data on an unpaid file", async () => { + const modelId = "5ec7392c2acfe57aa62e945a" + + sandbox + .stub(bchjs.IPFS.axios, "get") + .resolves({ data: mockData.unpaidFileData }) + + const result = await bchjs.IPFS.getStatus(modelId) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "hasBeenPaid") + assert.property(result, "satCost") + assert.property(result, "bchAddr") + assert.property(result, "ipfsHash") + assert.property(result, "fileId") + assert.property(result, "fileName") + }) + + it("should get data on an unpaid file", async () => { + const modelId = "5ec7392c2acfe57aa62e945a" + + sandbox + .stub(bchjs.IPFS.axios, "get") + .resolves({ data: mockData.paidFileData }) + + const result = await bchjs.IPFS.getStatus(modelId) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "hasBeenPaid") + assert.property(result, "satCost") + assert.property(result, "bchAddr") + assert.property(result, "ipfsHash") + assert.property(result, "fileId") + assert.property(result, "fileName") + }) + }) }) From 64670fd9ed0b5c4fb5757ecba218259f22378ddc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 21 May 2020 20:22:11 -0700 Subject: [PATCH 10/10] fix(ipfs documentation): Added apidocs for IPFS class --- src/ipfs.js | 181 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 48 deletions(-) diff --git a/src/ipfs.js b/src/ipfs.js index 6214b29..45e7fc7 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -52,9 +52,111 @@ class IPFS { return uppy } - // DEPRECATED - // Upload a file to the FullStack.cash IPFS server. If successful, it will - // return an object with an ID, a BCH address, and an amount of BCH to pay. + /** + * @api IPFS.createFileModel() createFileModel() - Create a file model + * @apiName createFileModel() + * @apiGroup IPFS + * @apiDescription Creates a new model on the server. If successful, will + * return an object with file data. That object contains a BCH address, + * payment amount, and _id required to be able to upload a file. + * + * @apiExample Example usage: + * (async () => { + * try { + * const path = `${__dirname}/ipfs.js` + * let fileData = await bchjs.IPFS.uploadFile(path); + * console.log(fileData); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "success": true, + * "hostingCostBCH": 0.00004197, + * "hostingCostUSD": 0.01, + * "file": { + * "payloadLink": "", + * "hasBeenPaid": false, + * "_id": "5ec562319bfacc745e8d8a52", + * "schemaVersion": 1, + * "size": 4458, + * "fileName": "ipfs.js", + * "fileExtension": "js", + * "createdTimestamp": "1589994033.655", + * "hostingCost": 4196, + * "walletIndex": 49, + * "bchAddr": "bchtest:qzrpkevu7h2ayfa4rjx08r5elvpfu72dg567x3mh3c", + * "__v": 0 + * } + */ + async createFileModel(path) { + try { + // Ensure the file exists. + if (!_this.fs.existsSync(path)) + throw new Error(`Could not find this file: ${path}`) + + // Read in the file. + const fileBuf = _this.fs.readFileSync(path) + // console.log(`fileBuf: `, fileBuf) + + // console.log(`Buffer length: ${fileBuf.length}`) + + // Get the file name from the path. + const splitPath = path.split("/") + const fileName = splitPath[splitPath.length - 1] + + // Get the file extension. + const splitExt = fileName.split(".") + const fileExt = splitExt[splitExt.length - 1] + + const fileObj = { + schemaVersion: 1, + size: fileBuf.length, + fileName: fileName, + fileExtension: fileExt + } + // console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) + + const fileData = await _this.axios.post(`${this.IPFS_API}/files`, { + file: fileObj + }) + // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) + + return fileData.data + } catch (err) { + console.error(`Error in createFileModel()`) + throw err + } + } + + /** + * @api IPFS.uploadFile() uploadFile() - Upload a file to IPFS + * @apiName uploadFile() + * @apiGroup IPFS + * @apiDescription Upload a file to the FullStack.cash IPFS server. If + * successful, it will return an object with an ID, a BCH address, and an + * amount of BCH to pay. + * + * @apiExample Example usage: + * (async () => { + * try { + * const path = `${__dirname}/ipfs.js` + * let fileData = await bchjs.IPFS.uploadFile(path, "5ec562319bfacc745e8d8a52"); + * console.log(fileData); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "schemaVersion": 1, + * "size": 2374, + * "fileId": "5ec562319bfacc745e8d8a52", + * "fileName": "ipfs.js", + * "fileExtension": "js" + * } + */ async uploadFile(path, modelId) { try { // Ensure the file exists. @@ -116,51 +218,34 @@ class IPFS { } } - // Creates a new model on the server. If successful, will return an object - // with file data. That object contains a BCH address, payment amount, and - // _id required to be able to upload a file. - async createFileModel(path) { - try { - // Ensure the file exists. - if (!_this.fs.existsSync(path)) - throw new Error(`Could not find this file: ${path}`) - - // Read in the file. - const fileBuf = _this.fs.readFileSync(path) - // console.log(`fileBuf: `, fileBuf) - - // console.log(`Buffer length: ${fileBuf.length}`) - - // Get the file name from the path. - const splitPath = path.split("/") - const fileName = splitPath[splitPath.length - 1] - - // Get the file extension. - const splitExt = fileName.split(".") - const fileExt = splitExt[splitExt.length - 1] - - const fileObj = { - schemaVersion: 1, - size: fileBuf.length, - fileName: fileName, - fileExtension: fileExt - } - // console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) - - const fileData = await _this.axios.post(`${this.IPFS_API}/files`, { - file: fileObj - }) - // console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`) - - return fileData.data - } catch (err) { - console.error(`Error in createFileModel()`) - throw err - } - } - - // Gets the status of the uploaded file. Will return an object that indicates - // the payment status. If the file is paid, it should contain an IPFS hash. + /** + * @api IPFS.getStatus() getStatus() - Check status of file upload + * @apiName getStatus() + * @apiGroup IPFS + * @apiDescription Gets the status of the uploaded file. Will return an object + * that indicates the payment status. If the file is paid, it should contain + * an IPFS hash. + * + * @apiExample Example usage: + * (async () => { + * try { + * const modelId = "5ec7392c2acfe57aa62e945a" + * let fileData = await bchjs.IPFS.getStatus(modelId) + * console.log(fileData) + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "hasBeenPaid": true, + * "satCost": 4403, + * "bchAddr": "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk", + * "ipfsHash": "QmRDHPhY5hCNVRMVQvS2H9uty8P1skdwgLaHpUAkEvsjcE", + * "fileId": "5ec7392c2acfe57aa62e945a", + * "fileName": "ipfs-e2e.js" + * } + */ async getStatus(modelId) { try { if (!modelId) throw new Error(`Must include a file model ID.`)