/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const AssetRegistry = require('./assetregistry');
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
const ConnectionProfileManager = require('composer-common').ConnectionProfileManager;
const EventEmitter = require('events');
const fs = require('fs');
const FSConnectionProfileStore = require('composer-common').FSConnectionProfileStore;
const Logger = require('composer-common').Logger;
const ParticipantRegistry = require('./participantregistry');
const Resource = require('composer-common').Resource;
const TransactionDeclaration = require('composer-common').TransactionDeclaration;
const TransactionRegistry = require('./transactionregistry');
const Util = require('composer-common').Util;
const uuid = require('uuid');
const LOG = Logger.getLog('BusinessNetworkConnection');
/**
* Use this class to connect to and then interact with a deployed BusinessNetworkDefinition.
* Use the AdminConnection class in the composer-admin module to deploy BusinessNetworksDefinitions.
* @extends EventEmitter
* @see See [EventEmitter]{@link module:composer-client.EventEmitter}
* @class
* @memberof module:composer-client
*/
class BusinessNetworkConnection extends EventEmitter {
/**
* Create an instance of the BusinessNetworkConnection class.
* must be called to connect to a deployed BusinessNetworkDefinition.
* @param {Object} [options] - an optional set of options to configure the instance.
* @param {Object} [options.fs] - specify an fs implementation to use.
* @param {boolean} [options.developmentMode] - specify whether or not the instance
* is in development mode. Use only for testing purposes!
*/
constructor(options) {
super();
options = options || {};
this.developmentMode = options.developmentMode || false;
this.connection = null;
this.connectionProfileStore = new FSConnectionProfileStore(options.fs || fs);
this.connectionProfileManager = new ConnectionProfileManager(this.connectionProfileStore);
this.connection = null;
this.securityContext = null;
this.businessNetwork = null;
}
/**
* Returns the currently connected BusinessNetworkDefinition
* @example
* // Get the Business Network Definition
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getBusinessNetwork();
* })
* .then(function(BusinessNetworkDefinition){
* // Retrieved Business Network Definition
* });
* @returns {BusinessNetworkDefinition} the business network
*/
getBusinessNetwork() {
return this.businessNetwork;
}
/**
* Get a list of all existing asset registries.
* @example
* // Get all asset registries
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getAllAssetRegistries();
* })
* .then(function(assetRegistries){
* // Retrieved Asset Registries
* });
* @param {SecurityContext} securityContext - The user's security context
* @return {Promise} - A promise that will be resolved with a list of existing
* asset registries
*/
getAllAssetRegistries() {
Util.securityCheck(this.securityContext);
return AssetRegistry.getAllAssetRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Get an existing asset registry.
* @example
* // Get a asset registry
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getAssetRegistry('businessNetworkIdentifier.registryId');
* })
* .then(function(assetRegistry){
* // Retrieved Asset Registry
* });
* @param {string} id - The unique identifier of the asset registry
* @return {Promise} - A promise that will be resolved with the existing asset
* registry, or rejected if the asset registry does not exist.
*/
getAssetRegistry(id) {
Util.securityCheck(this.securityContext);
return AssetRegistry.getAssetRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Determine whether a asset registry exists.
* @example
* // Determine whether an asset registry exists
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.existsAssetRegistry('businessNetworkIdentifier.registryId');
* })
* .then(function(exists){
* // if (exists === true) {
* // logic here...
* //}
* });
* @param {string} id - The unique identifier of the asset registry
* @return {Promise} - A promise that will be resolved with a boolean indicating whether the asset
* registry exists.
*/
existsAssetRegistry(id) {
Util.securityCheck(this.securityContext);
return AssetRegistry.existsAssetRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Add a new asset registry.
* @example
* // Add a new asset registry
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.addAssetRegistry('registryId','registryName');
* });
* @param {string} id - The unique identifier of the asset registry
* @param {string} name - The name of the asset registry
* @return {Promise} - A promise that will be resolved with the new asset
* registry after it has been added.
*/
addAssetRegistry(id, name) {
Util.securityCheck(this.securityContext);
return AssetRegistry.addAssetRegistry(this.securityContext, id, name, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Get a list of all existing participant registries.
* @example
* // Get all participant registries
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getAllParticipantRegistries();
* })
* .then(function(participantRegistries){
* // Retrieved Participant Registries
* });
* @param {SecurityContext} securityContext - The user's security context
* @return {Promise} - A promise that will be resolved with a list of existing
* participant registries
*/
getAllParticipantRegistries() {
Util.securityCheck(this.securityContext);
return ParticipantRegistry.getAllParticipantRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Get an existing participant registry.
* @example
* // Get a participant registry
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getParticipantRegistry('businessNetworkIdentifier.registryId');
* })
* .then(function(participantRegistry){
* // Retrieved Participant Registry
* });
* @param {string} id - The unique identifier of the participant registry
* @return {Promise} - A promise that will be resolved with the existing participant
* registry, or rejected if the participant registry does not exist.
*/
getParticipantRegistry(id) {
Util.securityCheck(this.securityContext);
return ParticipantRegistry.getParticipantRegistry(this.securityContext, id, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Add a new participant registry.
* @example
* // Add a new participant registry
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.addParticipantRegistry('registryId','registryName');
* });
* @param {string} id - The unique identifier of the participant registry
* @param {string} name - The name of the participant registry
* @return {Promise} - A promise that will be resolved with the new participant
* registry after it has been added.
*/
addParticipantRegistry(id, name) {
Util.securityCheck(this.securityContext);
return ParticipantRegistry.addParticipantRegistry(this.securityContext, id, name, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer());
}
/**
* Get the transaction registry.
* @example
* // Get the transaction registry
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.getTransactionRegistry();
* })
* .then(function(transactionRegistry){
* // Retrieved Transaction Registry
* });
* @return {Promise} - A promise that will be resolved to the {@link TransactionRegistry}
*/
getTransactionRegistry() {
Util.securityCheck(this.securityContext);
return TransactionRegistry
.getAllTransactionRegistries(this.securityContext, this.getBusinessNetwork().getModelManager(), this.getBusinessNetwork().getFactory(), this.getBusinessNetwork().getSerializer())
.then((transactionRegistries) => {
if (transactionRegistries.length >= 1) {
return transactionRegistries[0];
} else {
throw new Error('Failed to find the default transaction registry');
}
});
}
/**
* Connects to a business network using a connection profile, and authenticates to the Hyperledger Fabric.
* @example
* // Connect and log in to HLF
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* // Connected
* });
* @param {string} connectionProfile - The name of the connection profile
* @param {string} businessNetwork - The identifier of the business network
* @param {string} enrollmentID the enrollment ID of the user
* @param {string} enrollmentSecret the enrollment secret of the user
* @return {Promise} A promise to a BusinessNetworkDefinition that indicates the connection is complete
*/
connect(connectionProfile, businessNetwork, enrollmentID, enrollmentSecret) {
return this.connectionProfileManager.connect(connectionProfile, businessNetwork)
.then((connection) => {
this.connection = connection;
return connection.login(enrollmentID, enrollmentSecret);
})
.then((securityContext) => {
this.securityContext = securityContext;
return this.connection.ping(this.securityContext);
})
.then(() => {
return Util.queryChainCode(this.securityContext, 'getBusinessNetwork', []);
})
.then((buffer) => {
let businessNetworkJSON = JSON.parse(buffer.toString());
let businessNetworkArchive = Buffer.from(businessNetworkJSON.data, 'base64');
return BusinessNetworkDefinition.fromArchive(businessNetworkArchive);
})
.then((businessNetwork) => {
this.businessNetwork = businessNetwork;
return this.businessNetwork;
});
}
/**
* Disconnects from the Hyperledger Fabric.
* @example
* // Disconnects from HLF
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.disconnect();
* })
* .then(function(){
* // Disconnected.
* });
* @return {Promise} A promise that will be resolved when the connection is
* terminated.
*/
disconnect() {
if (!this.connection) {
return Promise.resolve();
}
return this.connection.disconnect()
.then(() => {
this.connection = null;
this.securityContext = null;
this.businessNetwork = null;
});
}
/**
* Submit a transaction for processing by the currently connected business network.
* @example
* // Submits a transaction
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* var factory = businessNetworkDefinition.getBusinessNetwork().getFactory();
* var transaction = factory.newTransaction('network.transactions', 'TransactionType');
* return businessNetworkDefinition.submitTransaction(transaction);
* })
* .then(function(){
* // Submitted a transaction.
* });
* @param {Resource} transaction - The transaction to submit. Use {@link
* Factory#newTransaction newTransaction} to create this object.
* @return {Promise} A promise that will be fulfilled when the transaction has
* been processed.
*/
submitTransaction(transaction) {
const self = this;
Util.securityCheck(this.securityContext);
if (!transaction) {
throw new Error('transaction not specified');
}
let classDeclaration = transaction.getClassDeclaration();
if (!(classDeclaration instanceof TransactionDeclaration)) {
throw new Error(classDeclaration.getFullyQualifiedName() + ' is not a transaction');
}
let id = transaction.getIdentifier();
if (id === null || id === undefined) {
id = uuid.v4();
transaction.setIdentifier(id);
}
let timestamp = transaction.timestamp;
if (timestamp === null || timestamp === undefined) {
timestamp = transaction.timestamp = new Date();
}
let data = self.getBusinessNetwork().getSerializer().toJSON(transaction);
return self.getTransactionRegistry(self.securityContext)
.then((transactionRegistry) => {
return Util.invokeChainCode(self.securityContext, 'submitTransaction', [transactionRegistry.id, JSON.stringify(data)]);
});
}
/**
* Test the connection to the runtime and verify that the version of the
* runtime is compatible with this level of the client node.js module.
* @example
* // Test the connection to the runtime
* var businessNetwork = new BusinessNetworkConnection();
* return businessNetwork.connect('testprofile', 'businessNetworkIdentifier', 'WebAppAdmin', 'DJY27pEnl16d')
* .then(function(businessNetworkDefinition){
* return businessNetworkDefinition.ping();
* })
* .then(function(){
* // Connection tested.
* });
* @return {Promise} A promise that will be fufilled when the connection has
* been tested. The promise will be rejected if the version is incompatible.
*/
ping() {
Util.securityCheck(this.securityContext);
return this.connection.ping(this.securityContext);
}
/**
* Issue an identity with the specified user ID and map it to the specified
* participant.
* @param {Resource|string} participant The participant, or the fully qualified
* identifier of the participant. The participant must already exist.
* @param {string} userID The user ID for the identity.
* @param {object} [options] Options for the new identity.
* @param {boolean} [options.issuer] Whether or not the new identity should have
* permissions to create additional new identities. False by default.
* @return {Promise} A promise that will be fulfilled when the identity has
* been added to the specified participant. The promise will be rejected if
* the participant does not exist, or if the identity is already mapped to
* another participant.
*/
issueIdentity(participant, userID, options) {
const method = 'issueIdentity';
LOG.entry(method, participant, userID);
if (!participant) {
throw new Error('participant not specified');
} else if (!userID) {
throw new Error('userID not specified');
}
let participantFQI;
if (participant instanceof Resource) {
participantFQI = participant.getFullyQualifiedIdentifier();
} else {
participantFQI = participant;
}
Util.securityCheck(this.securityContext);
return this.connection.createIdentity(this.securityContext, userID, options)
.then((identity) => {
return Util.invokeChainCode(this.securityContext, 'addParticipantIdentity', [participantFQI, userID])
.then(() => {
LOG.exit(method, identity);
return identity;
});
});
}
/**
* Revoke the specified identity by removing any existing mapping to a participant.
* @param {string} identity The identity, for example the enrollment ID.
* @return {Promise} A promise that will be fulfilled when the identity has
* been removed from the specified participant. The promise will be rejected if
* the participant does not exist, or if the identity is not mapped to the
* participant.
*/
revokeIdentity(identity) {
const method = 'revokeIdentity';
LOG.entry(method, identity);
if (!identity) {
throw new Error('identity not specified');
}
Util.securityCheck(this.securityContext);
// It is not currently possible to revoke the certificate, so we just call
// the runtime to remove the mapping.
return Util.invokeChainCode(this.securityContext, 'removeIdentity', [identity])
.then(() => {
LOG.exit(method);
});
}
}
module.exports = BusinessNetworkConnection;