diff --git a/src/ipfs.js b/src/ipfs.js index 8b124a1..af07f01 100644 --- a/src/ipfs.js +++ b/src/ipfs.js @@ -53,8 +53,8 @@ class IPFS { } /** - * @api IPFS.createFileModel() createFileModel() - Create a file model - * @apiName createFileModel() + * @api IPFS.createFileModelServer() createFileModelServer() - Create a file model + * @apiName createFileModelServer() * @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, @@ -64,7 +64,7 @@ class IPFS { * (async () => { * try { * const path = `${__dirname}/ipfs.js` - * let fileData = await bchjs.IPFS.uploadFile(path); + * let fileData = await bchjs.IPFS.createFileModelServer(path); * console.log(fileData); * } catch(error) { * console.error(error) @@ -90,7 +90,7 @@ class IPFS { * "__v": 0 * } */ - async createFileModel(path) { + async createFileModelServer(path) { try { // Ensure the file exists. if (!_this.fs.existsSync(path)) @@ -131,7 +131,7 @@ class IPFS { } /** - * @api IPFS.uploadFile() uploadFile() - Upload a file to IPFS + * @api IPFS.uploadFileServer() uploadFileServer() - Upload a file to IPFS * @apiName uploadFile() * @apiGroup IPFS * @apiDescription Upload a file to the FullStack.cash IPFS server. If @@ -142,7 +142,7 @@ class IPFS { * (async () => { * try { * const path = `${__dirname}/ipfs.js` - * let fileData = await bchjs.IPFS.uploadFile(path, "5ec562319bfacc745e8d8a52"); + * let fileData = await bchjs.IPFS.uploadFileServer(path, "5ec562319bfacc745e8d8a52"); * console.log(fileData); * } catch(error) { * console.error(error) @@ -157,7 +157,7 @@ class IPFS { * "fileExtension": "js" * } */ - async uploadFile(path, modelId) { + async uploadFileServer(path, modelId) { try { // Ensure the file exists. if (!_this.fs.existsSync(path)) @@ -270,6 +270,175 @@ class IPFS { throw err } } + /** + * @api IPFS.createFileModelWeb() createFileModelWeb() - Create a file model + * @apiName createFileModelWeb() + * @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 content = ['PSF'] + * const name = 'psf.txt' // Content can be Array , ArrayBuffer , Blob + * const options = { type: "text/plain" } + * const file = new File(content,name,options) + * let fileData = await bchjs.IPFS.createFileModelWeb(file); + * 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 createFileModelWeb(file) { + try { + if (!file) throw new Error(`File is required`) + + const { name, size } = file + + if (!name || typeof name !== "string") + throw new Error(`File should have the property 'name' of string type`) + + if (!size || typeof size !== "number") + throw new Error(`File should have the property 'size' of number type`) + + // Get the file extension. + const splitExt = name.split(".") + const fileExt = splitExt[splitExt.length - 1] + + const fileObj = { + schemaVersion: 1, + size: size, + fileName: name, + 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 createFileModelWeb()`) + throw err + } + } + + /** + * @api IPFS.uploadFileWeb() uploadFileWeb() - Upload a file to IPFS + * @apiName uploadFileWeb() + * @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 content = ['PSF'] + * const name = 'psf.txt' // Content can be Array , ArrayBuffer , Blob + * const options = { type: "text/plain" } + * const file = new File(content,name,options) + * let fileData = await bchjs.IPFS.uploadFile(file, "5ec562319bfacc745e8d8a52"); + * console.log(fileData); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "schemaVersion": 1, + * "size": 2374, + * "fileId": "5ec562319bfacc745e8d8a52", + * "fileName": "ipfs.js", + * "fileExtension": "js" + * } + */ + async uploadFileWeb(file, modelId) { + try { + if (!file) throw new Error(`File is required`) + + const { name, type, size } = file + + if (!name || typeof name !== "string") + throw new Error(`File should have the property 'name' of string type`) + + if (!type || typeof type !== "string") + throw new Error(`File should have the property 'type' of string type`) + + if (!size || typeof size !== "number") + throw new Error(`File should have the property 'size' of number type`) + + if (!modelId) { + throw new Error( + `Must include a file model ID in order to upload a file.` + ) + } + + // Prepare the upload object. Get a file ID from Uppy. + const id = _this.uppy.addFile({ + name: name, + data: file, + source: "Local", + isRemote: false + }) + // 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() + + 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)}`) + + const fileObj = { + schemaVersion: 1, + size: upData.successful[0].progress.bytesTotal, + fileId: upData.successful[0].meta.fileModelId, + 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/uploadFileWeb(): `) + throw err + } + } } module.exports = IPFS diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js index cc95458..3c6ea3d 100644 --- a/test/unit/ipfs.js +++ b/test/unit/ipfs.js @@ -4,7 +4,6 @@ const assert = require("chai").assert const sinon = require("sinon") - const BCHJS = require("../../src/bch-js") let bchjs @@ -27,12 +26,12 @@ describe(`#IPFS`, () => { }) }) - describe("#createFileModel", () => { + describe("#createFileModelServer", () => { it("should throw an error if file does not exist", async () => { try { const path = "/non-existant-file" - await bchjs.IPFS.createFileModel(path) + await bchjs.IPFS.createFileModelServer(path) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -48,7 +47,7 @@ describe(`#IPFS`, () => { .stub(bchjs.IPFS.axios, "post") .resolves({ data: mockData.mockNewFileModel }) - const result = await bchjs.IPFS.createFileModel(path) + const result = await bchjs.IPFS.createFileModelServer(path) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, "success") @@ -72,12 +71,12 @@ describe(`#IPFS`, () => { }) }) - describe("#uploadFile", () => { + describe("#uploadFileServer", () => { 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.uploadFileServer(path) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -90,7 +89,7 @@ describe(`#IPFS`, () => { try { const path = `${__dirname}/ipfs.js` - await bchjs.IPFS.uploadFile(path) + await bchjs.IPFS.uploadFileServer(path) assert.equal(true, false, "Unexpected result") } catch (err) { @@ -112,7 +111,7 @@ describe(`#IPFS`, () => { sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mock) const path = `${__dirname}/ipfs.js` - await bchjs.IPFS.uploadFile(path, "5ec562319bfacc745e8d8a52") + await bchjs.IPFS.uploadFileServer(path, "5ec562319bfacc745e8d8a52") assert.equal(true, false, "Unexpected result") } catch (err) { @@ -126,7 +125,7 @@ describe(`#IPFS`, () => { sandbox.stub(bchjs.IPFS.uppy, "upload").resolves(mockData.uploadData) const path = `${__dirname}/ipfs.js` - const result = await bchjs.IPFS.uploadFile( + const result = await bchjs.IPFS.uploadFileServer( path, "5ec562319bfacc745e8d8a52" ) @@ -192,4 +191,253 @@ describe(`#IPFS`, () => { assert.property(result, "fileName") }) }) + + describe("#createFileModelWeb", () => { + it("should throw an error if file is undefined", async () => { + try { + let file + + await bchjs.IPFS.createFileModelWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include(err.message, `File is required`) + } + }) + it("should throw an error if file is empty", async () => { + try { + const file = {} + + await bchjs.IPFS.createFileModelWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'name' of string type` + ) + } + }) + it("should throw an error if 'name' property is not included", async () => { + try { + const file = { + size: 5000 + } + + await bchjs.IPFS.createFileModelWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'name' of string type` + ) + } + }) + + it("should throw an error if 'size' property is not included", async () => { + try { + const file = { + name: "ipfs.js" + } + + await bchjs.IPFS.createFileModelWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'size' of number type` + ) + } + }) + + it("should create a new file model", async () => { + const file = { + name: "ipfs.js", + size: 5000, + type: "text/plain" + } + sandbox + .stub(bchjs.IPFS.axios, "post") + .resolves({ data: mockData.mockNewFileModel }) + + const result = await bchjs.IPFS.createFileModelWeb(file) + // 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, "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("#uploadFileWeb", () => { + it("should throw an error if file is undefined", async () => { + try { + let file + + await bchjs.IPFS.uploadFileWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include(err.message, `File is required`) + } + }) + it("should throw an error if file is empty", async () => { + try { + const file = {} + + await bchjs.IPFS.uploadFileWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'name' of string type` + ) + } + }) + it("should throw an error if 'name' property is not included", async () => { + try { + const file = { + size: 5000, + type: "text/plain" + } + + await bchjs.IPFS.uploadFileWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'name' of string type` + ) + } + }) + it("should throw an error if 'size' property is not included", async () => { + try { + const file = { + name: "ipfs.js", + type: "text/plain" + } + + await bchjs.IPFS.uploadFileWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'size' of number type` + ) + } + }) + it("should throw an error if 'type' property is not included", async () => { + try { + const file = { + name: "ipfs.js", + size: 5000 + } + + await bchjs.IPFS.uploadFileWeb(file) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + //console.log(`err.message: ${err.message}`) + assert.include( + err.message, + `File should have the property 'type' of string type` + ) + } + }) + it("should throw an error if modelId is not included", async () => { + try { + const file = { + name: "ipfs.js", + size: 5000, + type: "text/plain" + } + await bchjs.IPFS.uploadFileWeb(file) + + 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 file = { + name: "ipfs.js", + size: 5000, + type: "text/plain" + } + await bchjs.IPFS.uploadFileWeb(file, "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 file = { + name: "ipfs.js", + size: 5000, + type: "text/plain" + } + const result = await bchjs.IPFS.uploadFileWeb( + file, + "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") + } + }) + }) })