diff --git a/packages/composer-common/api.txt b/packages/composer-common/api.txt index ebf10a9182..f9d6e31274 100644 --- a/packages/composer-common/api.txt +++ b/packages/composer-common/api.txt @@ -55,7 +55,8 @@ class IdCard { + Object getCredentials() + Object getEnrollmentCredentials() + String[] getRoles() - + Promise fromArchive(Buffer) + + Promise fromArchive() + + Promise toArchive(Object,String) } class IllegalModelException extends BaseFileException { + void constructor(String,ModelFile,Object,String,String,String,String) diff --git a/packages/composer-common/changelog.txt b/packages/composer-common/changelog.txt index 5f60954df5..32308078c9 100644 --- a/packages/composer-common/changelog.txt +++ b/packages/composer-common/changelog.txt @@ -11,8 +11,9 @@ # # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 0.10.2 {e07efe48c4f431525388c10979b4289b} 2017-07-27 +Version 0.10.2 {04641978245bd4326e314f0afb758b67} 2017-07-27 - Added IdCard.getRoles function +- Added IdCard.toArchive function Version 0.10.1 {d1fd512551ff5bb30b31f05f6817966e} 2017-07-24 - Added InvalidQueryException, BaseFileException diff --git a/packages/composer-common/lib/idcard.js b/packages/composer-common/lib/idcard.js index 78eb96bc18..15578bf436 100644 --- a/packages/composer-common/lib/idcard.js +++ b/packages/composer-common/lib/idcard.js @@ -19,6 +19,10 @@ const JSZip = require('jszip'); const Logger = require('./log/logger'); const LOG = Logger.getLog('IdCard'); +const CONNECTION_FILENAME = 'connection.json'; +const METADATA_FILENAME = 'metadata.json'; +const CREDENTIALS_DIRNAME = 'credentials'; + /** * An ID card. Encapsulates credentials and other information required to connect to a specific business network * as a specific user. @@ -140,24 +144,26 @@ class IdCard { /** * Create an IdCard from a card archive. - * @param {Buffer} buffer - the Buffer to a zip archive - * @return {Promise} Promise to the instantiated IdCard + *

+ * Valid types for zipData are any of the types supported by JSZip. + * @param {String|ArrayBuffer|Uint8Array|Buffer|Blob|Promise} zipData - card archive data. + * @return {Promise} Promise to the instantiated IdCard. */ - static fromArchive(buffer) { + static fromArchive(zipData) { const method = 'fromArchive'; - LOG.entry(method, buffer.length); + LOG.entry(method, zipData.length); - return JSZip.loadAsync(buffer).then((zip) => { + return JSZip.loadAsync(zipData).then((zip) => { let promise = Promise.resolve(); let metadata; let connection; let credentials = Object.create(null); - LOG.debug(method, 'Loading connection.json'); - const connectionFile = zip.file('connection.json'); + LOG.debug(method, 'Loading ' + CONNECTION_FILENAME); + const connectionFile = zip.file(CONNECTION_FILENAME); if (!connectionFile) { - throw Error('Required file not found: connection.json'); + throw Error('Required file not found: ' + CONNECTION_FILENAME); } promise = promise.then(() => { @@ -166,10 +172,10 @@ class IdCard { connection = JSON.parse(connectionContent); }); - LOG.debug(method, 'Loading metadata.json'); - const metadataFile = zip.file('metadata.json'); + LOG.debug(method, 'Loading ' + METADATA_FILENAME); + const metadataFile = zip.file(METADATA_FILENAME); if (!metadataFile) { - throw Error('Required file not found: metadata.json'); + throw Error('Required file not found: ' + METADATA_FILENAME); } promise = promise.then(() => { @@ -193,8 +199,8 @@ class IdCard { }); }; - LOG.debug(method, 'Loading credentials'); - loadDirectoryToObject('credentials', credentials); + LOG.debug(method, 'Loading ' + CREDENTIALS_DIRNAME); + loadDirectoryToObject(CREDENTIALS_DIRNAME, credentials); return promise.then(() => { const idCard = new IdCard(metadata, connection, credentials); @@ -204,6 +210,39 @@ class IdCard { }); } + /** + * Generate a card archive representing this ID card. + *

+ * The default value for the options.type parameter is arraybuffer. See JSZip documentation + * for other valid values. + * @param {Object} [options] - JSZip generation options. + * @param {String} [options.type] - type of the resulting ZIP file data. + * @return {Promise} Promise of the generated ZIP file; by default an {@link ArrayBuffer}. + */ + toArchive(options) { + const method = 'fromArchive'; + LOG.entry(method, options); + + const zipOptions = Object.assign({ type: 'arraybuffer' }, options); + const zip = new JSZip(); + + const connectionContents = JSON.stringify(this.connectionProfile); + zip.file(CONNECTION_FILENAME, connectionContents); + + const metadataContents = JSON.stringify(this.metadata); + zip.file(METADATA_FILENAME, metadataContents); + + Object.keys(this.credentials).forEach(credentialName => { + const filename = CREDENTIALS_DIRNAME + '/' + credentialName; + const credentialData = this.credentials[credentialName]; + zip.file(filename, credentialData); + }); + + const result = zip.generateAsync(zipOptions); + LOG.exit(method, result); + return result; + } + } module.exports = IdCard; diff --git a/packages/composer-common/test/idcard.js b/packages/composer-common/test/idcard.js index cb3805c734..bbd30de859 100644 --- a/packages/composer-common/test/idcard.js +++ b/packages/composer-common/test/idcard.js @@ -83,41 +83,41 @@ describe('IdCard', function() { it('should throw error on missing connection.json', function() { return readIdCardAsync('missing-connection').then((readBuffer) => { - return IdCard.fromArchive(readBuffer).then(function resolved(card) { - throw Error('Card loaded without error'); - }, function rejected(error) { - error.message.should.include('connection.json'); - }); + return IdCard.fromArchive(readBuffer); + }).then(function resolved(card) { + throw Error('Card loaded without error'); + }, function rejected(error) { + error.message.should.include('connection.json'); }); }); it('should throw error on missing name field in connection.json', function() { return readIdCardAsync('missing-connection-name').then((readBuffer) => { - return IdCard.fromArchive(readBuffer).then(function resolved(card) { - throw Error('Card loaded without error'); - }, function rejected(error) { - error.message.should.include('name'); - }); + return IdCard.fromArchive(readBuffer); + }).then(function resolved(card) { + throw Error('Card loaded without error'); + }, function rejected(error) { + error.message.should.include('name'); }); }); it('should throw error on missing metadata.json', function() { return readIdCardAsync('missing-metadata').then((readBuffer) => { - return IdCard.fromArchive(readBuffer).then(function resolved(card) { - throw Error('Card loaded without error'); - }, function rejected(error) { - error.message.should.include('metadata.json'); - }); + return IdCard.fromArchive(readBuffer); + }).then(function resolved(card) { + throw Error('Card loaded without error'); + }, function rejected(error) { + error.message.should.include('metadata.json'); }); }); it('should throw error on missing name field in metadata', function() { return readIdCardAsync('missing-metadata-name').then((readBuffer) => { - return IdCard.fromArchive(readBuffer).then(function resolved(card) { - throw Error('Card loaded without error'); - }, function rejected(error) { - error.message.should.include('name'); - }); + return IdCard.fromArchive(readBuffer); + }).then(function resolved(card) { + throw Error('Card loaded without error'); + }, function rejected(error) { + error.message.should.include('name'); }); }); @@ -223,6 +223,48 @@ describe('IdCard', function() { roles.should.be.empty; }); }); + }); + + describe('#toArchive', function() { + const minimalMetadata = { name: 'minimal'}; + const minimalConnectionProfile = { name: 'minimal' }; + const emptyCredentials = { }; + const validCredentials = { + public: 'public-key-data', + private: 'private-key-data' + }; + + const minimalCard = new IdCard(minimalMetadata, minimalConnectionProfile, emptyCredentials); + const credentialsCard = new IdCard(minimalMetadata, minimalConnectionProfile, validCredentials); + + it('should export a valid minimal ID card', function() { + return minimalCard.toArchive().then(cardArchive => { + return IdCard.fromArchive(cardArchive); + }).then(card => { + card.should.deep.equal(minimalCard); + }); + }); + it('should export credentials', function() { + return credentialsCard.toArchive().then(cardArchive => { + return IdCard.fromArchive(cardArchive); + }).then(card => { + card.should.deep.equal(credentialsCard); + }); + }); + + it('should export to an ArrayBuffer by default', function() { + return minimalCard.toArchive().then(cardArchive => { + cardArchive.should.be.an.instanceof(ArrayBuffer); + }); + }); + + it('should export to a Node Buffer if requested', function() { + const options = { type: 'nodebuffer' }; + return minimalCard.toArchive(options).then(cardArchive => { + cardArchive.should.be.an.instanceof(Buffer); + }); + }); }); + }); \ No newline at end of file