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) + }) + }) +})