'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter05/controller/restapi/features/composer/Z2B_Services.js b/Chapter05/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter05/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter05/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter05/controller/restapi/features/composer/autoLoad.js b/Chapter05/controller/restapi/features/composer/autoLoad.js
index ce3d81b..0206a5d 100644
--- a/Chapter05/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter05/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,8 +125,10 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
- .catch((error) => {
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
+ .catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
})
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,7 +190,7 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
}
else {console.log('error with assetRegistry.add', error.message);}
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter05/controller/restapi/features/composer/hlcClient.js b/Chapter05/controller/restapi/features/composer/hlcClient.js
index bc5bfb6..a0a8559 100644
--- a/Chapter05/controller/restapi/features/composer/hlcClient.js
+++ b/Chapter05/controller/restapi/features/composer/hlcClient.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -244,7 +245,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -329,7 +330,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -338,7 +339,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -353,7 +354,121 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ let event = {};
+ event.type = _event.$type;
+ event.orderID = _event.orderID;
+ event.ID = _event.buyerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ event.ID = _event.sellerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.financeCoID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ event.ID = _event.sellerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.providerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ event.ID = _event.sellerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.providerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.shipperID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ event.ID = _event.sellerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.providerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.shipperID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.financeCoID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ break;
+ case 'PaymentAuthorized':
+ event.ID = _event.sellerID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ event.ID = _event.financeCoID;
+ svc.send(locals, 'Alert',JSON.stringify(event));
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter05/controller/restapi/features/composer/queryBlockChain.js b/Chapter05/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter05/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter05/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter05/controller/restapi/router.js b/Chapter05/controller/restapi/router.js
index a8e73d8..5d20aa6 100644
--- a/Chapter05/controller/restapi/router.js
+++ b/Chapter05/controller/restapi/router.js
@@ -13,26 +13,30 @@
*/
-var express = require('express');
-var router = express.Router();
-var format = require('date-format');
+'use strict';
+
+let express = require('express');
+let router = express.Router();
+let format = require('date-format');
+
+let multi_lingual = require('./features/multi_lingual');
+let resources = require('./features/resources');
+let getCreds = require('./features/getCredentials');
+let hlcAdmin = require('./features/composer/hlcAdmin');
+let hlcClient = require('./features/composer/hlcClient');
+let setup = require('./features/composer/autoLoad');
+let hlcFabric = require('./features/composer/queryBlockChain');
-var multi_lingual = require('./features/multi_lingual');
-var resources = require('./features/resources');
-var getCreds = require('./features/getCredentials');
-var hlcAdmin = require('./features/composer/hlcAdmin');
-var hlcClient = require('./features/composer/hlcClient');
-var setup = require('./features/composer/autoLoad');
-var hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
-var count = 0;
+let count = 0;
/**
* This is a request tracking function which logs to the terminal window each request coming in to the web serve and
* increments a counter to allow the requests to be sequenced.
diff --git a/Chapter05/favicon.ico b/Chapter05/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter05/favicon.ico and /dev/null differ
diff --git a/Chapter05/index.js b/Chapter05/index.js
index cb64e45..cf79cb7 100644
--- a/Chapter05/index.js
+++ b/Chapter05/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter05');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,15 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
+
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -75,29 +122,17 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
}
-
-/**
- * display using console.log the properties of each property in the inbound object
- * @param {displayObjectProperties} _string - string name of object
- * @param {displayObjectProperties} _object - the object to be parsed
- * @utility
- */
-function displayObjectValues (_string, _object)
-{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));}
-}
diff --git a/Chapter05/network/lib/sample.js b/Chapter05/network/lib/sample.js
index e16ab45..cf652c9 100644
--- a/Chapter05/network/lib/sample.js
+++ b/Chapter05/network/lib/sample.js
@@ -160,11 +160,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -175,11 +175,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -190,11 +190,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter06/Documentation/answers/composer/hlcClient_complete.js b/Chapter06/Documentation/answers/composer/hlcClient_complete.js
index 00e58c9..e79d87b 100644
--- a/Chapter06/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter06/Documentation/answers/composer/hlcClient_complete.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -218,7 +219,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -303,7 +304,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -312,7 +313,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -327,7 +328,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter06/Documentation/answers/js/z2b-buyer_complete.js b/Chapter06/Documentation/answers/js/z2b-buyer_complete.js
index 535f938..f1a1e93 100644
--- a/Chapter06/Documentation/answers/js/z2b-buyer_complete.js
+++ b/Chapter06/Documentation/answers/js/z2b-buyer_complete.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,37 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {
+ setupBuyer(page);
+ });
}
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ else
+ {
+ $.when($.get(toLoad)).done(function (page)
+ {
+ setupBuyer(page);
+ });
}
}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#body').empty();
$('#body').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +77,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +120,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +208,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +290,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{
@@ -286,18 +309,26 @@ function formatOrders(_target, _orders)
// iterate through the page and make all of the different parts of the page active.
//
for (let each in _orders)
- {(function(_idx, _arr)
- { $('#b_btn_'+_idx).on('click', function ()
- {
- let options = {};
- options.action = $('#b_action'+_idx).find(':selected').text();
- options.orderNo = $('#b_order'+_idx).text();
- options.participant = $('#buyer').val();
- if ((options.action === 'Dispute') || (options.action === 'Resolve')) {options.reason = $('#b_reason'+_idx).val();}
- $('#buyer_messages').prepend(formatMessage(options.action+textPrompts.orderProcess.processing_msg.format(options.action, options.orderNo)+options.orderNo));
- $.when($.post('/composer/client/orderAction', options)).done(function (_results)
- { $('#buyer_messages').prepend(formatMessage(_results.result)); });
- });
- })(each, _orders)
+ {(function(_idx, _arr)
+ { $('#b_btn_'+_idx).on('click', function ()
+ {
+ let options = {};
+ options.action = $('#b_action'+_idx).find(':selected').text();
+ options.orderNo = $('#b_order'+_idx).text();
+ options.participant = $('#buyer').val();
+ if ((options.action === 'Dispute') || (options.action === 'Resolve'))
+ {options.reason = $('#b_reason'+_idx).val();}
+ $('#buyer_messages').prepend(formatMessage(options.action+textPrompts.orderProcess.processing_msg.format(options.action, options.orderNo)+options.orderNo));
+ $.when($.post('/composer/client/orderAction', options)).done(function (_results)
+ { $('#buyer_messages').prepend(formatMessage(_results.result)); });
+ });
+ // use the notifyMe function to determine if this order is in the alert array.
+ // if it is, the highlight the $('#b_status'+_idx) html element by adding the 'highlight' class
+ if (notifyMe(b_alerts, _arr[_idx].id)) {$('#b_status'+_idx).addClass('highlight'); }
+ })(each, _orders);
}
+ // reset the b_alerts array to a new array
+ b_alerts = new Array();
+ // call the toggleAlerts function to reset the alert icon
+ toggleAlert($('#buyer_notify'), b_alerts, b_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter06/HTML/favicon.ico b/Chapter06/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter06/HTML/favicon.ico and b/Chapter06/HTML/favicon.ico differ
diff --git a/Chapter06/HTML/js/z2b-admin.js b/Chapter06/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter06/HTML/js/z2b-admin.js
+++ b/Chapter06/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter06/HTML/js/z2b-buyer.js b/Chapter06/HTML/js/z2b-buyer.js
index c91fdd5..4923fbc 100644
--- a/Chapter06/HTML/js/z2b-buyer.js
+++ b/Chapter06/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,41 +32,66 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
+ {
+ console.log('deferredMemberLoad')
+ $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
}
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ else
+ {
+ console.log('no deferredMemberLoad')
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
}
}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
-
- // update the text on the page using the prompt data for the selected language
+ $('#body').empty();
+ $('#body').append(page);
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
-
// enable the buttons to process an onClick event
-
- // build the buyer select HTML element
+ let _create = $('#newOrder');
+ let _list = $('#orderStatus');
+ let _orderDiv = $('#'+orderDiv);
+ _create.on('click', function(){displayOrderForm();});
+ _list.on('click', function(){listOrders();});
+ $('#buyer').empty();
+ // build the buer select HTML element
for (let each in buyers)
{(function(_idx, _arr)
{$('#buyer').append('');})(each, buyers);
}
// display the name of the current buyer
-
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ var _loc = $('#company')
+ $('#company')[0].innerText = buyers[0].companyName;
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -75,13 +104,26 @@ function displayOrderForm()
// get the order creation web page and also get all of the items that a user can select
$.when($.get(toLoad), $.get('/composer/client/getItemTable')).done(function (page, _items)
{
-
+ itemTable = _items[0].items;
+ let _orderDiv = $('#'+orderDiv);
+ _orderDiv.empty();
+ _orderDiv.append(page[0]);
// update the page with the appropriate text for the selected language
-
+ updatePage('createOrder');
+ $('#seller').empty();
// populate the seller HTML select object. This string was built during the memberLoad or deferredMemberLoad function call
-
+ $('#seller').append(s_string);
+ $('#seller').val($('#seller option:first').val());
+ $('#orderNo').append('xxx');
+ $('#status').append('New Order');
+ $('#today').append(new Date().toISOString());
+ $('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
-
+ let _str = '';
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
+ $('#items').empty();
+ $('#items').append(_str);
+ $('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
// hide the submit new order function until an item has been selected
$('#submitNewOrder').hide();
$('#submitNewOrder').on('click', function ()
@@ -96,19 +138,27 @@ function displayOrderForm()
});
// function to call when an item has been selected
$('#addItem').on('click', function ()
- {
+ { let _ptr = $('#items').find(':selected').val();
// remove the just selected item so that it cannot be added twice.
-
+ $('#items').find(':selected').remove();
// build a new item detail row in the display window
-
+ let _item = itemTable[_ptr];
+ let len = newItems.length;
+ _str = '
'+_item.itemNo+'
'+_item.itemDescription+'
';
+ $('#itemTable').append(_str);
// set the initial item count to 1
-
+ $('#count'+len).val(1);
// set the initial price to the price of one item
-
+ $('#price'+len).append('$'+_item.unitPrice+'.00');
// add an entry into an array for this newly added item
-
+ let _newItem = _item;
+ _newItem.extendedPrice = _item.unitPrice;
+ newItems[len] = _newItem;
+ newItems[len].quantity=1;
+ totalAmount += _newItem.extendedPrice;
// update the order amount with this new item
-
+ $('#amount').empty();
+ $('#amount').append('$'+totalAmount+'.00');
// function to update item detail row and total amount if itemm count is changed
$('#count'+len).on('change', function ()
{let len = this.id.substring(5);
@@ -133,7 +183,7 @@ function listOrders()
{
let options = {};
// get the users email address
-
+ options.id = $('#buyer').find(':selected').text();
// get their password from the server. This is clearly not something we would do in production, but enables us to demo more easily
// $.when($.post('/composer/admin/getSecret', options)).done(function(_mem)
// {
@@ -188,41 +238,51 @@ function formatOrders(_target, _orders)
r_string = ' '+textPrompts.orderProcess.Dispute.prompt+'';
break;
case orderStatus.Delivered.code:
-
- break;
+ _date = _arr[_idx].delivered;
+ _action += '';
+ r_string = ' '+textPrompts.orderProcess.Dispute.prompt+'';
+ break;
case orderStatus.Dispute.code:
-
- break;
+ _date = _arr[_idx].disputeOpened + ' '+_arr[_idx].dispute;
+ _action += '';
+ r_string = ' '+textPrompts.orderProcess.Resolve.prompt+'';
+ break;
case orderStatus.Resolve.code:
-
- break;
+ _date = _arr[_idx].disputeResolved + ' '+_arr[_idx].resolve;
+ _action += '';
+ break;
case orderStatus.Created.code:
-
- break;
+ _date = _arr[_idx].created;
+ _action += ''
+ _action += ''
+ break;
case orderStatus.Backordered.code:
-
- break;
+ _date = _arr[_idx].dateBackordered + ' '+_arr[_idx].backorder;
+ _action += ''
+ break;
case orderStatus.ShipRequest.code:
-
- break;
+ _date = _arr[_idx].requestShipment;
+ break;
case orderStatus.Authorize.code:
-
- break;
+ _date = _arr[_idx].approved;
+ break;
case orderStatus.Bought.code:
-
- break;
+ _date = _arr[_idx].bought;
+ _action += ''
+ break;
case orderStatus.Delivering.code:
-
- break;
+ _date = _arr[_idx].delivering;
+ break;
case orderStatus.Ordered.code:
-
- break;
+ _date = _arr[_idx].ordered;
+ _action += ''
+ break;
case orderStatus.Cancelled.code:
-
- break;
+ _date = _arr[_idx].cancelled;
+ break;
case orderStatus.Paid.code:
-
- break;
+ _date = _arr[_idx].paid;
+ break;
default:
break;
}
@@ -230,7 +290,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter06/controller/restapi/features/composer/Z2B_Services.js b/Chapter06/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter06/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter06/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter06/controller/restapi/features/composer/autoLoad.js b/Chapter06/controller/restapi/features/composer/autoLoad.js
index ce3d81b..0206a5d 100644
--- a/Chapter06/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter06/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,8 +125,10 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
- .catch((error) => {
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
+ .catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
})
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,7 +190,7 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
}
else {console.log('error with assetRegistry.add', error.message);}
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter06/controller/restapi/features/composer/hlcAdmin.js b/Chapter06/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter06/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter06/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter06/controller/restapi/features/composer/queryBlockChain.js b/Chapter06/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter06/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter06/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter06/controller/restapi/router.js b/Chapter06/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter06/controller/restapi/router.js
+++ b/Chapter06/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter06/favicon.ico b/Chapter06/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter06/favicon.ico and /dev/null differ
diff --git a/Chapter06/index.js b/Chapter06/index.js
index cb64e45..cf79cb7 100644
--- a/Chapter06/index.js
+++ b/Chapter06/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter05');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,15 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
+
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -75,29 +122,17 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
}
-
-/**
- * display using console.log the properties of each property in the inbound object
- * @param {displayObjectProperties} _string - string name of object
- * @param {displayObjectProperties} _object - the object to be parsed
- * @utility
- */
-function displayObjectValues (_string, _object)
-{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));}
-}
diff --git a/Chapter06/network/lib/sample.js b/Chapter06/network/lib/sample.js
index b243bc9..cf20b90 100644
--- a/Chapter06/network/lib/sample.js
+++ b/Chapter06/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter07/Documentation/answers/composer/hlcClient_complete.js b/Chapter07/Documentation/answers/composer/hlcClient_complete.js
index d484aa5..307708d 100644
--- a/Chapter07/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter07/Documentation/answers/composer/hlcClient_complete.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -229,7 +230,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -314,7 +315,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -323,7 +324,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -338,7 +339,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter07/Documentation/answers/js/z2b-seller_complete.js b/Chapter07/Documentation/answers/js/z2b-seller_complete.js
index ecc7209..19072a8 100644
--- a/Chapter07/Documentation/answers/js/z2b-seller_complete.js
+++ b/Chapter07/Documentation/answers/js/z2b-seller_complete.js
@@ -16,36 +16,38 @@
'use strict';
let sellerOrderDiv = 'sellerOrderDiv';
-
+let s_alerts = [];
+let s_notify = '#seller_notify';
+let s_count = '#seller_count';
+let s_id;
/**
* load the administration Seller Experience
*/
function loadSellerUX ()
{
let toLoad = 'seller.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupSeller(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupSeller(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupSeller(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupSeller(page);});
}
}
/**
* load the administration User Experience
* @param {String} page - page to load
- * @param {Integer} port - web socket port to use
*/
-function setupSeller(page, port)
+function setupSeller(page)
{
$('#body').empty();
$('#body').append(page);
+ if (s_alerts.length == 0)
+ {$(s_notify).removeClass('on'); $(s_notify).addClass('off'); }
+ else {$(s_notify).removeClass('off'); $(s_notify).addClass('on'); }
updatePage('seller');
- msgPort = port.port;
- wsDisplay('seller_messages', msgPort);
let _clear = $('#seller_clear');
let _list = $('#sellerOrderStatus');
let _orderDiv = $('#'+sellerOrderDiv);
@@ -58,9 +60,15 @@ function setupSeller(page, port)
$('#seller').append(s_string);
$('#sellerCompany').empty();
$('#sellerCompany').append(sellers[0].companyName);
- $('#seller').on('change', function() {
+ s_id = sellers[0].id;
+ z2bSubscribe('Seller', s_id);
+ // create a function to execute when the user selects a different provider
+ $('#seller').on('change', function() {
$('#sellerCompany').empty(); _orderDiv.empty(); $('#seller_messages').empty();
$('#sellerCompany').append(findMember($('#seller').find(':selected').val(),sellers).companyName);
+ z2bUnSubscribe(s_id);
+ s_id = findMember($('#seller').find(':selected').text(),sellers).id;
+ z2bSubscribe('Seller', s_id);
});
}
/**
@@ -151,7 +159,7 @@ function formatSellerOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -166,7 +174,7 @@ function formatSellerOrders(_target, _orders)
_target.append(_str);
for (let each in _orders)
{(function(_idx, _arr)
- { $('#s_btn_'+_idx).on('click', function ()
+ { $('#s_btn_'+_idx).on('click', function ()
{
let options = {};
options.action = $('#s_action'+_idx).find(':selected').text();
@@ -178,6 +186,9 @@ function formatSellerOrders(_target, _orders)
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
{ $('#seller_messages').prepend(formatMessage(_results.result)); });
});
+ if (notifyMe(s_alerts, _arr[_idx].id)) {$('#s_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ s_alerts = new Array();
+ toggleAlert($('#seller_notify'), s_alerts, s_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter07/HTML/favicon.ico b/Chapter07/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter07/HTML/favicon.ico and b/Chapter07/HTML/favicon.ico differ
diff --git a/Chapter07/HTML/js/z2b-admin.js b/Chapter07/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter07/HTML/js/z2b-admin.js
+++ b/Chapter07/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter07/HTML/js/z2b-buyer.js b/Chapter07/HTML/js/z2b-buyer.js
index 535f938..f1a1e93 100644
--- a/Chapter07/HTML/js/z2b-buyer.js
+++ b/Chapter07/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,37 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {
+ setupBuyer(page);
+ });
}
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ else
+ {
+ $.when($.get(toLoad)).done(function (page)
+ {
+ setupBuyer(page);
+ });
}
}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#body').empty();
$('#body').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +77,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +120,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +208,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +290,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{
@@ -286,18 +309,26 @@ function formatOrders(_target, _orders)
// iterate through the page and make all of the different parts of the page active.
//
for (let each in _orders)
- {(function(_idx, _arr)
- { $('#b_btn_'+_idx).on('click', function ()
- {
- let options = {};
- options.action = $('#b_action'+_idx).find(':selected').text();
- options.orderNo = $('#b_order'+_idx).text();
- options.participant = $('#buyer').val();
- if ((options.action === 'Dispute') || (options.action === 'Resolve')) {options.reason = $('#b_reason'+_idx).val();}
- $('#buyer_messages').prepend(formatMessage(options.action+textPrompts.orderProcess.processing_msg.format(options.action, options.orderNo)+options.orderNo));
- $.when($.post('/composer/client/orderAction', options)).done(function (_results)
- { $('#buyer_messages').prepend(formatMessage(_results.result)); });
- });
- })(each, _orders)
+ {(function(_idx, _arr)
+ { $('#b_btn_'+_idx).on('click', function ()
+ {
+ let options = {};
+ options.action = $('#b_action'+_idx).find(':selected').text();
+ options.orderNo = $('#b_order'+_idx).text();
+ options.participant = $('#buyer').val();
+ if ((options.action === 'Dispute') || (options.action === 'Resolve'))
+ {options.reason = $('#b_reason'+_idx).val();}
+ $('#buyer_messages').prepend(formatMessage(options.action+textPrompts.orderProcess.processing_msg.format(options.action, options.orderNo)+options.orderNo));
+ $.when($.post('/composer/client/orderAction', options)).done(function (_results)
+ { $('#buyer_messages').prepend(formatMessage(_results.result)); });
+ });
+ // use the notifyMe function to determine if this order is in the alert array.
+ // if it is, the highlight the $('#b_status'+_idx) html element by adding the 'highlight' class
+ if (notifyMe(b_alerts, _arr[_idx].id)) {$('#b_status'+_idx).addClass('highlight'); }
+ })(each, _orders);
}
+ // reset the b_alerts array to a new array
+ b_alerts = new Array();
+ // call the toggleAlerts function to reset the alert icon
+ toggleAlert($('#buyer_notify'), b_alerts, b_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter07/HTML/js/z2b-events.js b/Chapter07/HTML/js/z2b-events.js
index 3da467d..282f3ea 100644
--- a/Chapter07/HTML/js/z2b-events.js
+++ b/Chapter07/HTML/js/z2b-events.js
@@ -16,6 +16,41 @@
'use strict';
+let wsSocket;
+
+/**
+ * load the four initial user roles into a single page.
+ */
+function singleUX ()
+{
+ let toLoad = 'singleUX.html';
+ if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (_page, _res)
+ {
+ $('#body').empty();
+ $('#body').append(_page);
+ loadBuyerUX();
+ loadSellerUX();
+ loadProviderUX();
+ loadShipperUX();
+ // Initialize Registration for all Z2B Business Events
+ goEventInitialize();
+ });
+ }
+ else{
+ $.when($.get(toLoad)).done(function(_page)
+ {
+ $('#body').empty();
+ $('#body').append(_page);
+ loadBuyerUX();
+ loadSellerUX();
+ loadProviderUX();
+ loadShipperUX();
+ // Initialize Registration for all Z2B Business Events
+ goEventInitialize();
+ });
+ }
+}
/**
* load all of the members in the network for use in the different user experiences. This is a synchronous routine and is executed autormatically on web app start.
* However, if this is a newly created network, then there are no members to retrieve and this will create four empty arrays
@@ -33,20 +68,29 @@ function memberLoad ()
$.when($.post('/composer/admin/getMembers', options), $.post('/composer/admin/getMembers', options2),
$.post('/composer/admin/getMembers', options3), $.post('/composer/admin/getMembers', options4)).done(function (_sellers, _buyers, _providers, _shippers)
{
- buyers = _buyers[0].members;
- sellers = _sellers[0].members;
- s_string = _getMembers(sellers);
- providers = _providers[0].members;
- p_string = _getMembers(providers);
- shippers = _shippers[0].members;
- sh_string = _getMembers(shippers);
+ buyers = dropDummy(_buyers[0].members);
+ sellers = dropDummy(_sellers[0].members);
+ providers = dropDummy(_providers[0].members);
+ shippers = dropDummy(_shippers[0].members);
+ s_string = _getMembers(sellers);
+ p_string = _getMembers(providers);
+ sh_string = _getMembers(shippers);
+
});
}
-
+/**
+ * dropDummy() removes 'noop@dummy' from memberlist
+ * @param {String} _in - member id to ignore
+ */
+function dropDummy(_in)
+{
+ let _a = new Array();
+ for (let each in _in){(function(_idx, _arr){if (_arr[_idx].id.slice(0,10) !== 'noop@dummy'){_a.push(_arr[_idx]);}})(each, _in);}
+ return _a;
+}
/**
* load all of the members in the network for use in the different user experiences. This routine is designed for use if the network has been newly deployed and the web app was
* started before the autoLoad function was run on the newly deployed network (which, by default, is empty).
- * @returns {Promise} - promise upon completino of loadding member objects.
*/
function deferredMemberLoad()
{
@@ -62,28 +106,178 @@ function deferredMemberLoad()
$.when($.post('/composer/admin/getMembers', options), $.post('/composer/admin/getMembers', options2),
$.post('/composer/admin/getMembers', options3), $.post('/composer/admin/getMembers', options4)).done(function (_sellers, _buyers, _providers, _shippers)
{
- buyers = _buyers[0].members;
- sellers = _sellers[0].members;
- s_string = _getMembers(sellers);
- providers = _providers[0].members;
- p_string = _getMembers(providers);
- shippers = _shippers[0].members;
- sh_string = _getMembers(shippers);
- d_prompts.resolve();
+ buyers = dropDummy(_buyers[0].members);
+ sellers = dropDummy(_sellers[0].members);
+ providers = dropDummy(_providers[0].members);
+ shippers = dropDummy(_shippers[0].members);
+ s_string = _getMembers(sellers);
+ p_string = _getMembers(providers);
+ sh_string = _getMembers(shippers);
+ d_prompts.resolve();
}).fail(d_prompts.reject);
return d_prompts.promise();
}
/**
* return an option list for use in an HTML ';
return _str;
+}
+/**
+ * set up the server to listen for all events
+ */
+function goEventInitialize()
+{
+ $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log('getChainEvents results: ', _res);});
+}
+
+/**
+ * @param {Event} _event - inbound Event
+ * @param {String} _id - subscriber target
+ * @param {String} _orderID - inbound order id
+ */
+function addNotification(_event, _id, _orderID)
+{
+ let method = 'addNotification';
+ console.log(method+' _event'+_event+' id: '+_id+' orderID: '+_orderID);
+ let type = getSubscriber(_id);
+ if (type === 'none') {return;}
+ switch(type)
+ {
+ case 'Buyer':
+ b_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(b_notify, b_alerts, b_count);
+ break;
+ case 'Seller':
+ s_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(s_notify, s_alerts, s_count);
+ break;
+ case 'Provider':
+ p_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(p_notify, p_alerts, p_count);
+ break;
+ case 'Shipper':
+ sh_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(sh_notify, sh_alerts, sh_count);
+ break;
+ case 'FinanceCo':
+ f_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(f_notify, f_alerts, f_count);
+ break;
+ default:
+ console.log(method+' default entered for: '+type);
+ break;
+ }
+}
+/**
+ *
+ * @param {jQuery} _target - jquery object to update
+ * @param {Array} _array - array of alerts for this member
+ * @param {jQuery} _count - jQuery object to hold alert count
+ */
+function toggleAlert(_target, _array, _count)
+{
+ if (_array.length < 1)
+ {$(_target).removeClass('on'); $(_target).addClass('off'); }
+ else {$(_count).empty(); $(_count).append(_array.length);
+ $(_target).removeClass('off'); $(_target).addClass('on'); }
+
+}
+/**
+ * check to see if _id is subscribing
+ * @param {Integer} _id - member id to seek
+ * @returns {String} - type of member
+ */
+function getSubscriber(_id)
+{
+ let type = 'none';
+ for (let each in subscribers){(function(_idx, _arr){if (_arr[_idx].id === _id){type=_arr[_idx].type;}})(each, subscribers);}
+ return(type);
+}
+/**
+ * subscribe to events
+ * @param {String} _type - member type
+ * @param {String} _id - member id
+ */
+function z2bSubscribe(_type, _id)
+{
+ subscribers.push({'type': _type, 'id': _id});
+}
+/**
+ * Unsubscribe to events
+ * @param {String} _id - member id to remove
+ */
+function z2bUnSubscribe(_id)
+{
+ let _s1 = subscribers;
+ let _s2 = [];
+ for (let each in _s1) {(function(_idx, _arr){if (_arr[_idx] != _id){_s2.push(_arr[_idx]);}})(each, _s1);}
+ subscribers = _s2;
+}
+/**
+ * notifyMe
+ * @param {Array} _alerts - array of alerts
+ * @param {String} _id - orderID
+ * @returns {Boolean} - true if found, false if not found
+ */
+function notifyMe (_alerts, _id)
+{
+ let b_h = false;
+ for (let each in _alerts) {(function(_idx, _arr){if (_id === _arr[_idx].order){b_h = true;}})(each, _alerts);}
+ return b_h;
+}
+/**
+ * connect to web socket
+ */
+function wsConnect()
+{
+ let method = 'wsConnect';
+ if (!window.WebSocket) {console.log('this browser does not support web sockets');}
+ let content = $('#body');
+ let blockchain = $('#blockchain');
+ // updated from ws: to wss: to support access over https
+ if (host_address.slice(0,9) === 'localhost')
+ {
+ wsSocket = new WebSocket('ws://'+host_address);
+ }else
+ {
+ wsSocket = new WebSocket('wss://'+host_address);
+ }
+ wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ', error);};
+ wsSocket.onopen = function ()
+ {console.log ('connect.onOpen initiated to: '+host_address); wsSocket.send('connected to client');};
+ wsSocket.onmessage = function (message)
+ {
+ let incoming
+ incoming = message.data;
+ // console.log(method+ ' incoming is: '+incoming);
+ while (incoming instanceof Object === false){incoming = JSON.parse(incoming);}
+ switch (incoming.type)
+ {
+ case 'Message':
+ content.append(formatMessage(incoming.data));
+ break;
+ case 'Alert':
+ let event = JSON.parse(incoming.data);
+ addNotification(event.type, event.ID, event.orderID);
+ break;
+ case 'BlockChain':
+ _blctr ++;
+ if (incoming.data !== 'connected')
+ {
+ $(blockchain).append('block '+incoming.data.header.number+' Hash: '+incoming.data.header.data_hash+'');
+ if (_blctr > 4) {let leftPos = $(blockchain).scrollLeft(); $(blockchain).animate({scrollLeft: leftPos + 300}, 250);}
+ }
+ break;
+ default:
+ console.log('Can Not Process message type: ',incoming.type);
+ }
+ };
}
\ No newline at end of file
diff --git a/Chapter07/HTML/js/z2b-initiate.js b/Chapter07/HTML/js/z2b-initiate.js
index bc5076a..7ba4493 100644
--- a/Chapter07/HTML/js/z2b-initiate.js
+++ b/Chapter07/HTML/js/z2b-initiate.js
@@ -20,7 +20,13 @@ let connectionProfileName = 'z2b-test-profile';
let networkFile = 'zerotoblockchain-network.bna';
let businessNetwork = 'zerotoblockchain-network';
-let buyers, sellers, providers, shippers;
+let host_address = window.location.host;
+
+let buyers = new Array();
+let sellers= new Array();
+let providers= new Array();
+let shippers= new Array();
+
let s_string, p_string, sh_string;
let orderStatus = {
@@ -53,4 +59,6 @@ function initPage ()
memberLoad();
// goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring
getChainEvents();
+ // get the asynch port
+ wsConnect();
}
diff --git a/Chapter07/HTML/js/z2b-utilities.js b/Chapter07/HTML/js/z2b-utilities.js
index ae1637f..7f2c962 100644
--- a/Chapter07/HTML/js/z2b-utilities.js
+++ b/Chapter07/HTML/js/z2b-utilities.js
@@ -6,68 +6,72 @@
* 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,
+ * 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.
*/
-// z2c-utilities.js
-
+// z2c-utilities.js
+'use strict';
/**
* creates a set of utilities inside the named space: z2c
* All utilities are accessed as z2c.functionName()
* @namespace - z2c
*/
-languages = {}, // getSupportedLanguages
-selectedLanguage = {},
-language = "",
-textLocations = {}, // getTextLocations
-textPrompts = {}, // getSelectedPromots
+let languages = {}, // getSupportedLanguages
+ selectedLanguage = {},
+ language = '',
+ textLocations = {}, // getTextLocations
+ textPrompts = {}, // getSelectedPromots
+ subscribers = new Array(); // subscribers to business events
/**
* get the value associated with a cookie named in the input
* Refer to this by {@link getCookieValue}.
* @param {String} _name - the name of the cookie to find
-* @namespace
+* @returns {String} - cookie value
+* @namespace
*/
function getCookieValue(_name)
{
- var name = _name+"=";
- var cookie_array= document.cookie.split(";");
- for (each in cookie_array)
- { var c = cookie_array[each].trim();
- if(c.indexOf(name) == 0) return(c.substring(name.length, c.length));
- }
- return("");
+ let name = _name+'=';
+ let cookie_array= document.cookie.split(';');
+ for (let each in cookie_array)
+ {
+ let c = cookie_array[each].trim();
+ if(c.indexOf(name) === 0) {return(c.substring(name.length, c.length));}
+ }
+ return('');
}
/**
* trims a string by removing all leading and trailing spaces
* trims the final period, if it exists, from a string.
* Refer to this by {@link trimStrip}.
-* @param {String} _string String to be trimmed and stripped of trailing period
-* @namespace
+* @param {String} _string - String to be trimmed and stripped of trailing period
+* @returns {String} - trimmed string
+* @namespace
*/
function trimStrip(_string)
{
- var str = _string.trim();
- var len = str.length;
- if(str.endsWith(".")) {str=str.substring(0,len-1);}
- return(str);
+ let str = _string.trim();
+ let len = str.length;
+ if(str.endsWith('.')) {str=str.substring(0,len-1);}
+ return(str);
}
/**
* replaces text on an html page based on the anchors and text provided in a JSON textPrompts object
* Refer to this by {@link updatePage}.
* @param {String} _page - a string representing the name of the html page to be updated
-* @namespace
+* @namespace
*/
function updatePage(_page)
{
- for (each in textPrompts[_page]){(function(_idx, _array)
- {$("#"+_idx).empty();$("#"+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
+ for (let each in textPrompts[_page]){(function(_idx, _array)
+ {$('#'+_idx).empty();$('#'+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
}
/**
@@ -75,7 +79,8 @@ function updatePage(_page)
* Refer to this by {@link getDisplaytext}.
* @param {String} _page - string representing the name of the html page to be updated
* @param {String} _item - string representing the html named item to be updated
-* @namespace
+* @returns {String} - text to be placed on web page
+* @namespace
*/
function getDisplaytext(_page, _item)
{return (textPrompts[_page][_item]);}
@@ -85,54 +90,56 @@ function getDisplaytext(_page, _item)
* Refer to this by {@link goMultiLingual}.
* @param {String} _language - language to be used in this session
* @param {String} _page - string representing html page to be updated in the selected language
-* @namespace
+* @namespace
*/
function goMultiLingual(_language, _page)
{ language = _language;
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {languages = _res;
- selectedLanguage = languages[_language];
- var options = {}; options.language = _language;
- $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
- {textLocations = _locations;
- textPrompts = JSON.parse(_prompts[0]);
- updatePage(_page);
+ $.when($.get('/api/getSupportedLanguages')).done(function(_res)
+ {languages = _res;
+ selectedLanguage = languages[_language];
+ let options = {}; options.language = _language;
+ $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
+ {textLocations = _locations;
+ textPrompts = JSON.parse(_prompts[0]);
+ updatePage(_page);
+ });
+ let _choices = $('#lang_choices');
+ _choices.empty(); let _str = '';
+ for (let each in _res)
+ {(function(_idx, _array)
+ {if (_array[_idx].active === 'yes')
+ {_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter07/controller/restapi/features/composer/Z2B_Services.js b/Chapter07/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter07/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter07/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter07/controller/restapi/features/composer/autoLoad.js b/Chapter07/controller/restapi/features/composer/autoLoad.js
index ce3d81b..da256aa 100644
--- a/Chapter07/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter07/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,7 +125,9 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
.catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,8 +190,8 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
- }
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
+ }
else {console.log('error with assetRegistry.add', error.message);}
});
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter07/controller/restapi/features/composer/hlcAdmin.js b/Chapter07/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter07/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter07/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter07/controller/restapi/features/composer/queryBlockChain.js b/Chapter07/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter07/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter07/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter07/controller/restapi/router.js b/Chapter07/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter07/controller/restapi/router.js
+++ b/Chapter07/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter07/favicon.ico b/Chapter07/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter07/favicon.ico and /dev/null differ
diff --git a/Chapter07/index.js b/Chapter07/index.js
index 70319d6..cf79cb7 100644
--- a/Chapter07/index.js
+++ b/Chapter07/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter07');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,14 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
+
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -74,16 +122,16 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
diff --git a/Chapter07/network/lib/sample.js b/Chapter07/network/lib/sample.js
index b243bc9..cf20b90 100644
--- a/Chapter07/network/lib/sample.js
+++ b/Chapter07/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter08/Documentation/answers/composer/hlcClient_complete.js b/Chapter08/Documentation/answers/composer/hlcClient_complete.js
index f43ff92..1998295 100644
--- a/Chapter08/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter08/Documentation/answers/composer/hlcClient_complete.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -236,7 +237,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -321,7 +322,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -330,7 +331,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -345,7 +346,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter08/Documentation/answers/js/z2b-provider_complete.js b/Chapter08/Documentation/answers/js/z2b-provider_complete.js
index 99d667f..4a9e800 100644
--- a/Chapter08/Documentation/answers/js/z2b-provider_complete.js
+++ b/Chapter08/Documentation/answers/js/z2b-provider_complete.js
@@ -17,6 +17,10 @@
'use strict';
let providerOrderDiv = 'providerOrderDiv';
+let p_alerts = [];
+let p_notify = '#provider_notify';
+let p_count = '#provider_count';
+let p_id;
/**
* load the Provider User Experience
@@ -24,30 +28,28 @@ let providerOrderDiv = 'providerOrderDiv';
function loadProviderUX ()
{
let toLoad = 'provider.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupProvider(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupProvider(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupProvider(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupProvider(page);});
}
}
/**
* load the Provider User Experience
* @param {String} page - the name of the page to load
- * @param {Integer} port - the port number to use
*/
-function setupProvider(page, port)
+function setupProvider(page)
{
$('#body').empty();
$('#body').append(page);
+ if (p_alerts.length === 0)
+ {$(p_notify).removeClass('on'); $(p_notify).addClass('off'); }
+ else {$(p_notify).removeClass('off'); $(p_notify).addClass('on'); }
updatePage('provider');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('provider_messages', msgPort);
let _clear = $('#provider_clear');
let _list = $('#providerOrderStatus');
let _orderDiv = $('#'+providerOrderDiv);
@@ -57,9 +59,15 @@ function setupProvider(page, port)
$('#provider').append(p_string);
$('#providerCompany').empty();
$('#providerCompany').append(providers[0].companyName);
+ p_id = providers[0].id;
+ z2bSubscribe('Provider', p_id);
+ // create a function to execute when the user selects a different provider
$('#provider').on('change', function() {
$('#providerCompany').empty(); _orderDiv.empty(); $('#provider_messages').empty();
$('#providerCompany').append(findMember($('#provider').find(':selected').val(),providers).companyName);
+ z2bUnSubscribe(p_id);
+ p_id = findMember($('#provider').find(':selected').text(),providers).id;
+ z2bSubscribe('Provider', p_id);
});
}
/**
@@ -137,9 +145,6 @@ function formatProviderOrders(_target, _orders)
_action += '';
b_string += ' '+textPrompts.orderProcess.Refund.prompt+'';
break;
- case orderStatus.Cancelled.code:
- _date = _arr[_idx].cancelled;
- break;
case orderStatus.Paid.code:
_date = _arr[_idx].paid;
break;
@@ -150,7 +155,7 @@ function formatProviderOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
';
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -171,7 +176,7 @@ function formatProviderOrders(_target, _orders)
options.orderNo = $('#p_order'+_idx).text();
options.participant = $('#provider').val();
options.shipper = $('#shippers'+_idx).find(':selected').val();
- if ((options.action == 'Resolve') || (options.action === 'Refund') || (options.action === 'BackOrder')) {options.reason = $('#p_reason'+_idx).val();}
+ if ((options.action === 'Resolve') || (options.action === 'Refund') || (options.action === 'BackOrder')) {options.reason = $('#p_reason'+_idx).val();}
console.log(options);
$('#provider_messages').prepend(formatMessage(options.action+textPrompts.orderProcess.processing_msg.format(options.action, options.orderNo)+options.orderNo));
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
@@ -179,6 +184,9 @@ function formatProviderOrders(_target, _orders)
$('#provider_messages').prepend(formatMessage(_results.result));
});
});
+ if (notifyMe(_arr[_idx].id)) {$('#p_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ p_alerts = new Array();
+ toggleAlert($('#provider_notify'), p_alerts, p_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter08/HTML/favicon.ico b/Chapter08/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter08/HTML/favicon.ico and b/Chapter08/HTML/favicon.ico differ
diff --git a/Chapter08/HTML/js/z2b-admin.js b/Chapter08/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter08/HTML/js/z2b-admin.js
+++ b/Chapter08/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter08/HTML/js/z2b-buyer.js b/Chapter08/HTML/js/z2b-buyer.js
index 535f938..2a4cffc 100644
--- a/Chapter08/HTML/js/z2b-buyer.js
+++ b/Chapter08/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,32 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
+ }
}
-}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#body').empty();
$('#body').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +72,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +203,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +285,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -166,7 +174,7 @@ function formatSellerOrders(_target, _orders)
_target.append(_str);
for (let each in _orders)
{(function(_idx, _arr)
- { $('#s_btn_'+_idx).on('click', function ()
+ { $('#s_btn_'+_idx).on('click', function ()
{
let options = {};
options.action = $('#s_action'+_idx).find(':selected').text();
@@ -178,6 +186,9 @@ function formatSellerOrders(_target, _orders)
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
{ $('#seller_messages').prepend(formatMessage(_results.result)); });
});
+ if (notifyMe(s_alerts, _arr[_idx].id)) {$('#s_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ s_alerts = new Array();
+ toggleAlert($('#seller_notify'), s_alerts, s_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter08/HTML/js/z2b-utilities.js b/Chapter08/HTML/js/z2b-utilities.js
index ae1637f..7f2c962 100644
--- a/Chapter08/HTML/js/z2b-utilities.js
+++ b/Chapter08/HTML/js/z2b-utilities.js
@@ -6,68 +6,72 @@
* 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,
+ * 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.
*/
-// z2c-utilities.js
-
+// z2c-utilities.js
+'use strict';
/**
* creates a set of utilities inside the named space: z2c
* All utilities are accessed as z2c.functionName()
* @namespace - z2c
*/
-languages = {}, // getSupportedLanguages
-selectedLanguage = {},
-language = "",
-textLocations = {}, // getTextLocations
-textPrompts = {}, // getSelectedPromots
+let languages = {}, // getSupportedLanguages
+ selectedLanguage = {},
+ language = '',
+ textLocations = {}, // getTextLocations
+ textPrompts = {}, // getSelectedPromots
+ subscribers = new Array(); // subscribers to business events
/**
* get the value associated with a cookie named in the input
* Refer to this by {@link getCookieValue}.
* @param {String} _name - the name of the cookie to find
-* @namespace
+* @returns {String} - cookie value
+* @namespace
*/
function getCookieValue(_name)
{
- var name = _name+"=";
- var cookie_array= document.cookie.split(";");
- for (each in cookie_array)
- { var c = cookie_array[each].trim();
- if(c.indexOf(name) == 0) return(c.substring(name.length, c.length));
- }
- return("");
+ let name = _name+'=';
+ let cookie_array= document.cookie.split(';');
+ for (let each in cookie_array)
+ {
+ let c = cookie_array[each].trim();
+ if(c.indexOf(name) === 0) {return(c.substring(name.length, c.length));}
+ }
+ return('');
}
/**
* trims a string by removing all leading and trailing spaces
* trims the final period, if it exists, from a string.
* Refer to this by {@link trimStrip}.
-* @param {String} _string String to be trimmed and stripped of trailing period
-* @namespace
+* @param {String} _string - String to be trimmed and stripped of trailing period
+* @returns {String} - trimmed string
+* @namespace
*/
function trimStrip(_string)
{
- var str = _string.trim();
- var len = str.length;
- if(str.endsWith(".")) {str=str.substring(0,len-1);}
- return(str);
+ let str = _string.trim();
+ let len = str.length;
+ if(str.endsWith('.')) {str=str.substring(0,len-1);}
+ return(str);
}
/**
* replaces text on an html page based on the anchors and text provided in a JSON textPrompts object
* Refer to this by {@link updatePage}.
* @param {String} _page - a string representing the name of the html page to be updated
-* @namespace
+* @namespace
*/
function updatePage(_page)
{
- for (each in textPrompts[_page]){(function(_idx, _array)
- {$("#"+_idx).empty();$("#"+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
+ for (let each in textPrompts[_page]){(function(_idx, _array)
+ {$('#'+_idx).empty();$('#'+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
}
/**
@@ -75,7 +79,8 @@ function updatePage(_page)
* Refer to this by {@link getDisplaytext}.
* @param {String} _page - string representing the name of the html page to be updated
* @param {String} _item - string representing the html named item to be updated
-* @namespace
+* @returns {String} - text to be placed on web page
+* @namespace
*/
function getDisplaytext(_page, _item)
{return (textPrompts[_page][_item]);}
@@ -85,54 +90,56 @@ function getDisplaytext(_page, _item)
* Refer to this by {@link goMultiLingual}.
* @param {String} _language - language to be used in this session
* @param {String} _page - string representing html page to be updated in the selected language
-* @namespace
+* @namespace
*/
function goMultiLingual(_language, _page)
{ language = _language;
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {languages = _res;
- selectedLanguage = languages[_language];
- var options = {}; options.language = _language;
- $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
- {textLocations = _locations;
- textPrompts = JSON.parse(_prompts[0]);
- updatePage(_page);
+ $.when($.get('/api/getSupportedLanguages')).done(function(_res)
+ {languages = _res;
+ selectedLanguage = languages[_language];
+ let options = {}; options.language = _language;
+ $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
+ {textLocations = _locations;
+ textPrompts = JSON.parse(_prompts[0]);
+ updatePage(_page);
+ });
+ let _choices = $('#lang_choices');
+ _choices.empty(); let _str = '';
+ for (let each in _res)
+ {(function(_idx, _array)
+ {if (_array[_idx].active === 'yes')
+ {_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter08/controller/restapi/features/composer/Z2B_Services.js b/Chapter08/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter08/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter08/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter08/controller/restapi/features/composer/autoLoad.js b/Chapter08/controller/restapi/features/composer/autoLoad.js
index ce3d81b..da256aa 100644
--- a/Chapter08/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter08/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,7 +125,9 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
.catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,8 +190,8 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
- }
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
+ }
else {console.log('error with assetRegistry.add', error.message);}
});
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter08/controller/restapi/features/composer/hlcAdmin.js b/Chapter08/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter08/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter08/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter08/controller/restapi/features/composer/queryBlockChain.js b/Chapter08/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter08/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter08/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter08/controller/restapi/router.js b/Chapter08/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter08/controller/restapi/router.js
+++ b/Chapter08/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter08/favicon.ico b/Chapter08/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter08/favicon.ico and /dev/null differ
diff --git a/Chapter08/index.js b/Chapter08/index.js
index 70319d6..cf79cb7 100644
--- a/Chapter08/index.js
+++ b/Chapter08/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter07');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,14 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
+
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -74,16 +122,16 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
diff --git a/Chapter08/network/lib/sample.js b/Chapter08/network/lib/sample.js
index ddb951a..0d948b6 100644
--- a/Chapter08/network/lib/sample.js
+++ b/Chapter08/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter09/Documentation/answers/composer/hlcClient_complete.js b/Chapter09/Documentation/answers/composer/hlcClient_complete.js
index 5b909c1..044a4fe 100644
--- a/Chapter09/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter09/Documentation/answers/composer/hlcClient_complete.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -241,7 +242,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -326,7 +327,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -335,7 +336,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -350,7 +351,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter09/Documentation/answers/js/z2b-shipper_complete.js b/Chapter09/Documentation/answers/js/z2b-shipper_complete.js
index 37ba88b..78c1f6c 100644
--- a/Chapter09/Documentation/answers/js/z2b-shipper_complete.js
+++ b/Chapter09/Documentation/answers/js/z2b-shipper_complete.js
@@ -17,6 +17,10 @@
'use strict';
let shipperOrderDiv = 'shipperOrderDiv';
+let sh_alerts = [];
+let sh_notify = '#shipper_notify';
+let sh_count = '#shipper_count';
+let sh_id;
/**
* load the shipper User Experience
@@ -24,29 +28,27 @@ let shipperOrderDiv = 'shipperOrderDiv';
function loadShipperUX ()
{
let toLoad = 'shipper.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupShipper(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupShipper(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupShipper(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupShipper(page);});
}
}
/**
- *
+ *
* @param {String} page - the page to load
- * @param {Integer} port - the web socket to use
*/
-function setupShipper(page, port)
+function setupShipper(page)
{
$('#body').empty();
$('#body').append(page);
+ if (sh_alerts.length === 0)
+ {$(sh_notify).removeClass('on'); $(sh_notify).addClass('off'); }
+ else {$(sh_notify).removeClass('off'); $(sh_notify).addClass('on'); }
updatePage('shipper');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('shipper_messages', msgPort);
let _clear = $('#shipper_clear');
let _list = $('#shipperOrderStatus');
let _orderDiv = $('#'+shipperOrderDiv);
@@ -56,9 +58,15 @@ function setupShipper(page, port)
$('#shipper').append(sh_string);
$('#shipperCompany').empty();
$('#shipperCompany').append(providers[0].companyName);
+ sh_id = shippers[0].id;
+ z2bSubscribe('Shipper', sh_id);
+ // create a function to execute when the user selects a different provider
$('#shipper').on('change', function() {
$('#shipperCompany').empty(); _orderDiv.empty(); $('#shipper_messages').empty();
$('#shipperCompany').append(findMember($('#shipper').find(':selected').val(),shippers).companyName);
+ z2bUnSubscribe(sh_id);
+ sh_id = findMember($('#shipper').find(':selected').text(),shippers).id;
+ z2bSubscribe('Shipper', sh_id);
});
}
/**
@@ -87,7 +95,7 @@ function listShipperOrders()
function formatShipperOrders(_target, _orders)
{
_target.empty();
- let _str = ''; let _date = ''; let _statusText;
+ let _str = ''; let _date = ''; let _statusText;
for (let each in _orders)
{(function(_idx, _arr)
{ let _action = '
';
_str+= '
'+textPrompts.orderProcess.itemno+'
'+textPrompts.orderProcess.description+'
'+textPrompts.orderProcess.qty+'
'+textPrompts.orderProcess.price+'
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -173,6 +181,9 @@ function formatShipperOrders(_target, _orders)
$('#shipper_messages').prepend(formatMessage(_results.result));
});
});
+ if (notifyMe(sh_alerts, _arr[_idx].id)) {$("#sh_status"+_idx).addClass('highlight'); }
})(each, _orders);
}
-}
\ No newline at end of file
+ sh_alerts = new Array();
+ toggleAlert($('#shipper_notify'), sh_alerts, sh_alerts.length);
+ }
\ No newline at end of file
diff --git a/Chapter09/HTML/favicon.ico b/Chapter09/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter09/HTML/favicon.ico and b/Chapter09/HTML/favicon.ico differ
diff --git a/Chapter09/HTML/js/z2b-admin.js b/Chapter09/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter09/HTML/js/z2b-admin.js
+++ b/Chapter09/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter09/HTML/js/z2b-buyer.js b/Chapter09/HTML/js/z2b-buyer.js
index 535f938..2a4cffc 100644
--- a/Chapter09/HTML/js/z2b-buyer.js
+++ b/Chapter09/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,32 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
+ }
}
-}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#body').empty();
$('#body').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +72,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +203,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +285,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -166,7 +174,7 @@ function formatSellerOrders(_target, _orders)
_target.append(_str);
for (let each in _orders)
{(function(_idx, _arr)
- { $('#s_btn_'+_idx).on('click', function ()
+ { $('#s_btn_'+_idx).on('click', function ()
{
let options = {};
options.action = $('#s_action'+_idx).find(':selected').text();
@@ -178,6 +186,9 @@ function formatSellerOrders(_target, _orders)
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
{ $('#seller_messages').prepend(formatMessage(_results.result)); });
});
+ if (notifyMe(s_alerts, _arr[_idx].id)) {$('#s_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ s_alerts = new Array();
+ toggleAlert($('#seller_notify'), s_alerts, s_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter09/HTML/js/z2b-utilities.js b/Chapter09/HTML/js/z2b-utilities.js
index ae1637f..7f2c962 100644
--- a/Chapter09/HTML/js/z2b-utilities.js
+++ b/Chapter09/HTML/js/z2b-utilities.js
@@ -6,68 +6,72 @@
* 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,
+ * 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.
*/
-// z2c-utilities.js
-
+// z2c-utilities.js
+'use strict';
/**
* creates a set of utilities inside the named space: z2c
* All utilities are accessed as z2c.functionName()
* @namespace - z2c
*/
-languages = {}, // getSupportedLanguages
-selectedLanguage = {},
-language = "",
-textLocations = {}, // getTextLocations
-textPrompts = {}, // getSelectedPromots
+let languages = {}, // getSupportedLanguages
+ selectedLanguage = {},
+ language = '',
+ textLocations = {}, // getTextLocations
+ textPrompts = {}, // getSelectedPromots
+ subscribers = new Array(); // subscribers to business events
/**
* get the value associated with a cookie named in the input
* Refer to this by {@link getCookieValue}.
* @param {String} _name - the name of the cookie to find
-* @namespace
+* @returns {String} - cookie value
+* @namespace
*/
function getCookieValue(_name)
{
- var name = _name+"=";
- var cookie_array= document.cookie.split(";");
- for (each in cookie_array)
- { var c = cookie_array[each].trim();
- if(c.indexOf(name) == 0) return(c.substring(name.length, c.length));
- }
- return("");
+ let name = _name+'=';
+ let cookie_array= document.cookie.split(';');
+ for (let each in cookie_array)
+ {
+ let c = cookie_array[each].trim();
+ if(c.indexOf(name) === 0) {return(c.substring(name.length, c.length));}
+ }
+ return('');
}
/**
* trims a string by removing all leading and trailing spaces
* trims the final period, if it exists, from a string.
* Refer to this by {@link trimStrip}.
-* @param {String} _string String to be trimmed and stripped of trailing period
-* @namespace
+* @param {String} _string - String to be trimmed and stripped of trailing period
+* @returns {String} - trimmed string
+* @namespace
*/
function trimStrip(_string)
{
- var str = _string.trim();
- var len = str.length;
- if(str.endsWith(".")) {str=str.substring(0,len-1);}
- return(str);
+ let str = _string.trim();
+ let len = str.length;
+ if(str.endsWith('.')) {str=str.substring(0,len-1);}
+ return(str);
}
/**
* replaces text on an html page based on the anchors and text provided in a JSON textPrompts object
* Refer to this by {@link updatePage}.
* @param {String} _page - a string representing the name of the html page to be updated
-* @namespace
+* @namespace
*/
function updatePage(_page)
{
- for (each in textPrompts[_page]){(function(_idx, _array)
- {$("#"+_idx).empty();$("#"+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
+ for (let each in textPrompts[_page]){(function(_idx, _array)
+ {$('#'+_idx).empty();$('#'+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
}
/**
@@ -75,7 +79,8 @@ function updatePage(_page)
* Refer to this by {@link getDisplaytext}.
* @param {String} _page - string representing the name of the html page to be updated
* @param {String} _item - string representing the html named item to be updated
-* @namespace
+* @returns {String} - text to be placed on web page
+* @namespace
*/
function getDisplaytext(_page, _item)
{return (textPrompts[_page][_item]);}
@@ -85,54 +90,56 @@ function getDisplaytext(_page, _item)
* Refer to this by {@link goMultiLingual}.
* @param {String} _language - language to be used in this session
* @param {String} _page - string representing html page to be updated in the selected language
-* @namespace
+* @namespace
*/
function goMultiLingual(_language, _page)
{ language = _language;
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {languages = _res;
- selectedLanguage = languages[_language];
- var options = {}; options.language = _language;
- $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
- {textLocations = _locations;
- textPrompts = JSON.parse(_prompts[0]);
- updatePage(_page);
+ $.when($.get('/api/getSupportedLanguages')).done(function(_res)
+ {languages = _res;
+ selectedLanguage = languages[_language];
+ let options = {}; options.language = _language;
+ $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
+ {textLocations = _locations;
+ textPrompts = JSON.parse(_prompts[0]);
+ updatePage(_page);
+ });
+ let _choices = $('#lang_choices');
+ _choices.empty(); let _str = '';
+ for (let each in _res)
+ {(function(_idx, _array)
+ {if (_array[_idx].active === 'yes')
+ {_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter09/controller/restapi/features/composer/Z2B_Services.js b/Chapter09/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter09/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter09/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter09/controller/restapi/features/composer/autoLoad.js b/Chapter09/controller/restapi/features/composer/autoLoad.js
index ce3d81b..da256aa 100644
--- a/Chapter09/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter09/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,7 +125,9 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
.catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,8 +190,8 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
- }
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
+ }
else {console.log('error with assetRegistry.add', error.message);}
});
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter09/controller/restapi/features/composer/hlcAdmin.js b/Chapter09/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter09/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter09/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter09/controller/restapi/features/composer/queryBlockChain.js b/Chapter09/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter09/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter09/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter09/controller/restapi/router.js b/Chapter09/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter09/controller/restapi/router.js
+++ b/Chapter09/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter09/favicon.ico b/Chapter09/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter09/favicon.ico and /dev/null differ
diff --git a/Chapter09/index.js b/Chapter09/index.js
index 677c0e8..cf79cb7 100644
--- a/Chapter09/index.js
+++ b/Chapter09/index.js
@@ -14,29 +14,29 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
-
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
-app.use(busboy());
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
+app.use(busboy());
// the session secret is a text string of arbitrary length which is
// used to encode and decode cookies sent between the browser and the server
@@ -52,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter07');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -62,14 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
+
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -77,16 +122,16 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
diff --git a/Chapter09/network/lib/sample.js b/Chapter09/network/lib/sample.js
index ddb951a..0d948b6 100644
--- a/Chapter09/network/lib/sample.js
+++ b/Chapter09/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter10/Documentation/answers/composer/hlcClient_complete.js b/Chapter10/Documentation/answers/composer/hlcClient_complete.js
index bc5bfb6..fc7955c 100644
--- a/Chapter10/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter10/Documentation/answers/composer/hlcClient_complete.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -244,7 +245,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -329,7 +330,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -338,7 +339,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -353,7 +354,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter10/Documentation/answers/js/z2b-financeCo_complete.js b/Chapter10/Documentation/answers/js/z2b-financeCo_complete.js
index a74a755..40ff08e 100644
--- a/Chapter10/Documentation/answers/js/z2b-financeCo_complete.js
+++ b/Chapter10/Documentation/answers/js/z2b-financeCo_complete.js
@@ -19,6 +19,11 @@
let financeCOorderDiv = 'financeCOorderDiv';
let orders = [];
const financeCoID = 'easymoney@easymoneyinc.com';
+const financeCoName = 'The Global Financier';
+let f_notify = '#financeCo_notify';
+let f_id = 'easymoney@easymoneyinc.com';
+let f_count = '#financeCo_count';
+let f_alerts;
/**
* load the finance company User Experience
@@ -26,33 +31,34 @@ const financeCoID = 'easymoney@easymoneyinc.com';
function loadFinanceCoUX ()
{
let toLoad = 'financeCo.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupFinanceCo(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupFinanceCo(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupFinanceCo(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupFinanceCo(page);});
}
}
/**
* @param {String} page HTML page to load
- * @param {Integer} port Websocket port to use
*/
-function setupFinanceCo(page, port)
+function setupFinanceCo(page)
{
$('#body').empty();
$('#body').append(page);
+ f_alerts = [];
+ if (f_alerts.length === 0)
+ {$(f_notify).removeClass('on'); $(f_notify).addClass('off'); }
+ else
+ {$(f_notify).removeClass('off'); $(f_notify).addClass('on'); }
updatePage( 'financeCo');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('finance_messages', msgPort);
let _clear = $('#financeCOclear');
let _list = $('#financeCOorderStatus');
let _orderDiv = $('#'+financeCOorderDiv);
_clear.on('click', function(){_orderDiv.empty();});
_list.on('click', function(){listFinanceOrders();});
+ z2bSubscribe('FinanceCo', f_id);
}
/**
* lists all orders for the selected financier
@@ -75,13 +81,13 @@ function listFinanceOrders()
* used by the listOrders() function
* formats the orders for a financier. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Integer} _orders - array with order objects
*/
function formatFinanceOrders(_target, _orders)
{
_target.empty();
- let _str = ''; let _date = '';
+ let _str = ''; let _date = '';
for (let each in _orders)
{(function(_idx, _arr)
{ let _action = '
';
_str+= formatDetail(_idx, _arr[_idx]);
})(each, _orders);
}
@@ -171,11 +177,17 @@ function formatFinanceOrders(_target, _orders)
$('#finance_messages').prepend(formatMessage(_results.result));
});
});
+ if (notifyMe(f_alerts, _arr[_idx].id)) {$("#f_status"+_idx).addClass('highlight'); }
})(each, _orders);
}
+ f_alerts = new Array();
+ toggleAlert($('#financeCo_notify'), f_alerts, f_alerts.length);
}
/**
* format the accordian with the details for this order
+ * @param {Integer} _cur - offset into order array
+ * @param {JSON} _order - JSON object with current order data
+ * @returns {String} - html string to append to browser page
*/
function formatDetail(_cur, _order)
{
diff --git a/Chapter10/HTML/favicon.ico b/Chapter10/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter10/HTML/favicon.ico and b/Chapter10/HTML/favicon.ico differ
diff --git a/Chapter10/HTML/js/z2b-admin.js b/Chapter10/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter10/HTML/js/z2b-admin.js
+++ b/Chapter10/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter10/HTML/js/z2b-buyer.js b/Chapter10/HTML/js/z2b-buyer.js
index 535f938..2a4cffc 100644
--- a/Chapter10/HTML/js/z2b-buyer.js
+++ b/Chapter10/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,32 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
+ }
}
-}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#body').empty();
$('#body').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +72,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +203,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +285,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -166,7 +174,7 @@ function formatSellerOrders(_target, _orders)
_target.append(_str);
for (let each in _orders)
{(function(_idx, _arr)
- { $('#s_btn_'+_idx).on('click', function ()
+ { $('#s_btn_'+_idx).on('click', function ()
{
let options = {};
options.action = $('#s_action'+_idx).find(':selected').text();
@@ -178,6 +186,9 @@ function formatSellerOrders(_target, _orders)
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
{ $('#seller_messages').prepend(formatMessage(_results.result)); });
});
+ if (notifyMe(s_alerts, _arr[_idx].id)) {$('#s_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ s_alerts = new Array();
+ toggleAlert($('#seller_notify'), s_alerts, s_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter10/HTML/js/z2b-shipper.js b/Chapter10/HTML/js/z2b-shipper.js
index 37ba88b..78c1f6c 100644
--- a/Chapter10/HTML/js/z2b-shipper.js
+++ b/Chapter10/HTML/js/z2b-shipper.js
@@ -17,6 +17,10 @@
'use strict';
let shipperOrderDiv = 'shipperOrderDiv';
+let sh_alerts = [];
+let sh_notify = '#shipper_notify';
+let sh_count = '#shipper_count';
+let sh_id;
/**
* load the shipper User Experience
@@ -24,29 +28,27 @@ let shipperOrderDiv = 'shipperOrderDiv';
function loadShipperUX ()
{
let toLoad = 'shipper.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupShipper(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupShipper(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupShipper(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupShipper(page);});
}
}
/**
- *
+ *
* @param {String} page - the page to load
- * @param {Integer} port - the web socket to use
*/
-function setupShipper(page, port)
+function setupShipper(page)
{
$('#body').empty();
$('#body').append(page);
+ if (sh_alerts.length === 0)
+ {$(sh_notify).removeClass('on'); $(sh_notify).addClass('off'); }
+ else {$(sh_notify).removeClass('off'); $(sh_notify).addClass('on'); }
updatePage('shipper');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('shipper_messages', msgPort);
let _clear = $('#shipper_clear');
let _list = $('#shipperOrderStatus');
let _orderDiv = $('#'+shipperOrderDiv);
@@ -56,9 +58,15 @@ function setupShipper(page, port)
$('#shipper').append(sh_string);
$('#shipperCompany').empty();
$('#shipperCompany').append(providers[0].companyName);
+ sh_id = shippers[0].id;
+ z2bSubscribe('Shipper', sh_id);
+ // create a function to execute when the user selects a different provider
$('#shipper').on('change', function() {
$('#shipperCompany').empty(); _orderDiv.empty(); $('#shipper_messages').empty();
$('#shipperCompany').append(findMember($('#shipper').find(':selected').val(),shippers).companyName);
+ z2bUnSubscribe(sh_id);
+ sh_id = findMember($('#shipper').find(':selected').text(),shippers).id;
+ z2bSubscribe('Shipper', sh_id);
});
}
/**
@@ -87,7 +95,7 @@ function listShipperOrders()
function formatShipperOrders(_target, _orders)
{
_target.empty();
- let _str = ''; let _date = ''; let _statusText;
+ let _str = ''; let _date = ''; let _statusText;
for (let each in _orders)
{(function(_idx, _arr)
{ let _action = '
';
_str+= '
'+textPrompts.orderProcess.itemno+'
'+textPrompts.orderProcess.description+'
'+textPrompts.orderProcess.qty+'
'+textPrompts.orderProcess.price+'
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -173,6 +181,9 @@ function formatShipperOrders(_target, _orders)
$('#shipper_messages').prepend(formatMessage(_results.result));
});
});
+ if (notifyMe(sh_alerts, _arr[_idx].id)) {$("#sh_status"+_idx).addClass('highlight'); }
})(each, _orders);
}
-}
\ No newline at end of file
+ sh_alerts = new Array();
+ toggleAlert($('#shipper_notify'), sh_alerts, sh_alerts.length);
+ }
\ No newline at end of file
diff --git a/Chapter10/HTML/js/z2b-utilities.js b/Chapter10/HTML/js/z2b-utilities.js
index ae1637f..7f2c962 100644
--- a/Chapter10/HTML/js/z2b-utilities.js
+++ b/Chapter10/HTML/js/z2b-utilities.js
@@ -6,68 +6,72 @@
* 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,
+ * 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.
*/
-// z2c-utilities.js
-
+// z2c-utilities.js
+'use strict';
/**
* creates a set of utilities inside the named space: z2c
* All utilities are accessed as z2c.functionName()
* @namespace - z2c
*/
-languages = {}, // getSupportedLanguages
-selectedLanguage = {},
-language = "",
-textLocations = {}, // getTextLocations
-textPrompts = {}, // getSelectedPromots
+let languages = {}, // getSupportedLanguages
+ selectedLanguage = {},
+ language = '',
+ textLocations = {}, // getTextLocations
+ textPrompts = {}, // getSelectedPromots
+ subscribers = new Array(); // subscribers to business events
/**
* get the value associated with a cookie named in the input
* Refer to this by {@link getCookieValue}.
* @param {String} _name - the name of the cookie to find
-* @namespace
+* @returns {String} - cookie value
+* @namespace
*/
function getCookieValue(_name)
{
- var name = _name+"=";
- var cookie_array= document.cookie.split(";");
- for (each in cookie_array)
- { var c = cookie_array[each].trim();
- if(c.indexOf(name) == 0) return(c.substring(name.length, c.length));
- }
- return("");
+ let name = _name+'=';
+ let cookie_array= document.cookie.split(';');
+ for (let each in cookie_array)
+ {
+ let c = cookie_array[each].trim();
+ if(c.indexOf(name) === 0) {return(c.substring(name.length, c.length));}
+ }
+ return('');
}
/**
* trims a string by removing all leading and trailing spaces
* trims the final period, if it exists, from a string.
* Refer to this by {@link trimStrip}.
-* @param {String} _string String to be trimmed and stripped of trailing period
-* @namespace
+* @param {String} _string - String to be trimmed and stripped of trailing period
+* @returns {String} - trimmed string
+* @namespace
*/
function trimStrip(_string)
{
- var str = _string.trim();
- var len = str.length;
- if(str.endsWith(".")) {str=str.substring(0,len-1);}
- return(str);
+ let str = _string.trim();
+ let len = str.length;
+ if(str.endsWith('.')) {str=str.substring(0,len-1);}
+ return(str);
}
/**
* replaces text on an html page based on the anchors and text provided in a JSON textPrompts object
* Refer to this by {@link updatePage}.
* @param {String} _page - a string representing the name of the html page to be updated
-* @namespace
+* @namespace
*/
function updatePage(_page)
{
- for (each in textPrompts[_page]){(function(_idx, _array)
- {$("#"+_idx).empty();$("#"+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
+ for (let each in textPrompts[_page]){(function(_idx, _array)
+ {$('#'+_idx).empty();$('#'+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
}
/**
@@ -75,7 +79,8 @@ function updatePage(_page)
* Refer to this by {@link getDisplaytext}.
* @param {String} _page - string representing the name of the html page to be updated
* @param {String} _item - string representing the html named item to be updated
-* @namespace
+* @returns {String} - text to be placed on web page
+* @namespace
*/
function getDisplaytext(_page, _item)
{return (textPrompts[_page][_item]);}
@@ -85,54 +90,56 @@ function getDisplaytext(_page, _item)
* Refer to this by {@link goMultiLingual}.
* @param {String} _language - language to be used in this session
* @param {String} _page - string representing html page to be updated in the selected language
-* @namespace
+* @namespace
*/
function goMultiLingual(_language, _page)
{ language = _language;
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {languages = _res;
- selectedLanguage = languages[_language];
- var options = {}; options.language = _language;
- $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
- {textLocations = _locations;
- textPrompts = JSON.parse(_prompts[0]);
- updatePage(_page);
+ $.when($.get('/api/getSupportedLanguages')).done(function(_res)
+ {languages = _res;
+ selectedLanguage = languages[_language];
+ let options = {}; options.language = _language;
+ $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
+ {textLocations = _locations;
+ textPrompts = JSON.parse(_prompts[0]);
+ updatePage(_page);
+ });
+ let _choices = $('#lang_choices');
+ _choices.empty(); let _str = '';
+ for (let each in _res)
+ {(function(_idx, _array)
+ {if (_array[_idx].active === 'yes')
+ {_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter10/controller/restapi/features/composer/Z2B_Services.js b/Chapter10/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter10/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter10/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter10/controller/restapi/features/composer/autoLoad.js b/Chapter10/controller/restapi/features/composer/autoLoad.js
index ce3d81b..da256aa 100644
--- a/Chapter10/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter10/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,7 +125,9 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
.catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,8 +190,8 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
- }
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
+ }
else {console.log('error with assetRegistry.add', error.message);}
});
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter10/controller/restapi/features/composer/hlcAdmin.js b/Chapter10/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter10/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter10/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter10/controller/restapi/features/composer/queryBlockChain.js b/Chapter10/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter10/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter10/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter10/controller/restapi/router.js b/Chapter10/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter10/controller/restapi/router.js
+++ b/Chapter10/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter10/favicon.ico b/Chapter10/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter10/favicon.ico and /dev/null differ
diff --git a/Chapter10/index.js b/Chapter10/index.js
index 70319d6..cf79cb7 100644
--- a/Chapter10/index.js
+++ b/Chapter10/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter07');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,14 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
+
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -74,16 +122,16 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
diff --git a/Chapter10/network/lib/sample.js b/Chapter10/network/lib/sample.js
index ddb951a..0d948b6 100644
--- a/Chapter10/network/lib/sample.js
+++ b/Chapter10/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter11/Documentation/answers/js/z2b-events_complete.js b/Chapter11/Documentation/answers/js/z2b-events_complete.js
index 7b22165..282f3ea 100644
--- a/Chapter11/Documentation/answers/js/z2b-events_complete.js
+++ b/Chapter11/Documentation/answers/js/z2b-events_complete.js
@@ -16,6 +16,8 @@
'use strict';
+let wsSocket;
+
/**
* load the four initial user roles into a single page.
*/
@@ -23,15 +25,16 @@ function singleUX ()
{
let toLoad = 'singleUX.html';
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (_page, _port, _res)
- {
- msgPort = _port.port;
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (_page, _res)
+ {
$('#body').empty();
$('#body').append(_page);
loadBuyerUX();
loadSellerUX();
loadProviderUX();
loadShipperUX();
+ // Initialize Registration for all Z2B Business Events
+ goEventInitialize();
});
}
else{
@@ -43,6 +46,8 @@ function singleUX ()
loadSellerUX();
loadProviderUX();
loadShipperUX();
+ // Initialize Registration for all Z2B Business Events
+ goEventInitialize();
});
}
}
@@ -63,15 +68,26 @@ function memberLoad ()
$.when($.post('/composer/admin/getMembers', options), $.post('/composer/admin/getMembers', options2),
$.post('/composer/admin/getMembers', options3), $.post('/composer/admin/getMembers', options4)).done(function (_sellers, _buyers, _providers, _shippers)
{
- buyers = _buyers[0].members;
- sellers = _sellers[0].members;
- s_string = _getMembers(sellers);
- providers = _providers[0].members;
- p_string = _getMembers(providers);
- shippers = _shippers[0].members;
- sh_string = _getMembers(shippers);
+ buyers = dropDummy(_buyers[0].members);
+ sellers = dropDummy(_sellers[0].members);
+ providers = dropDummy(_providers[0].members);
+ shippers = dropDummy(_shippers[0].members);
+ s_string = _getMembers(sellers);
+ p_string = _getMembers(providers);
+ sh_string = _getMembers(shippers);
+
});
}
+/**
+ * dropDummy() removes 'noop@dummy' from memberlist
+ * @param {String} _in - member id to ignore
+ */
+function dropDummy(_in)
+{
+ let _a = new Array();
+ for (let each in _in){(function(_idx, _arr){if (_arr[_idx].id.slice(0,10) !== 'noop@dummy'){_a.push(_arr[_idx]);}})(each, _in);}
+ return _a;
+}
/**
* load all of the members in the network for use in the different user experiences. This routine is designed for use if the network has been newly deployed and the web app was
* started before the autoLoad function was run on the newly deployed network (which, by default, is empty).
@@ -90,27 +106,178 @@ function deferredMemberLoad()
$.when($.post('/composer/admin/getMembers', options), $.post('/composer/admin/getMembers', options2),
$.post('/composer/admin/getMembers', options3), $.post('/composer/admin/getMembers', options4)).done(function (_sellers, _buyers, _providers, _shippers)
{
- buyers = _buyers[0].members;
- sellers = _sellers[0].members;
+ buyers = dropDummy(_buyers[0].members);
+ sellers = dropDummy(_sellers[0].members);
+ providers = dropDummy(_providers[0].members);
+ shippers = dropDummy(_shippers[0].members);
s_string = _getMembers(sellers);
- providers = _providers[0].members;
p_string = _getMembers(providers);
- shippers = _shippers[0].members;
sh_string = _getMembers(shippers);
d_prompts.resolve();
}).fail(d_prompts.reject);
- return d_prompts.promise();
+ return d_prompts.promise();
}
/**
* return an option list for use in an HTML ';
return _str;
+}
+/**
+ * set up the server to listen for all events
+ */
+function goEventInitialize()
+{
+ $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log('getChainEvents results: ', _res);});
+}
+
+/**
+ * @param {Event} _event - inbound Event
+ * @param {String} _id - subscriber target
+ * @param {String} _orderID - inbound order id
+ */
+function addNotification(_event, _id, _orderID)
+{
+ let method = 'addNotification';
+ console.log(method+' _event'+_event+' id: '+_id+' orderID: '+_orderID);
+ let type = getSubscriber(_id);
+ if (type === 'none') {return;}
+ switch(type)
+ {
+ case 'Buyer':
+ b_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(b_notify, b_alerts, b_count);
+ break;
+ case 'Seller':
+ s_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(s_notify, s_alerts, s_count);
+ break;
+ case 'Provider':
+ p_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(p_notify, p_alerts, p_count);
+ break;
+ case 'Shipper':
+ sh_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(sh_notify, sh_alerts, sh_count);
+ break;
+ case 'FinanceCo':
+ f_alerts.push({'event': _event, 'order': _orderID});
+ toggleAlert(f_notify, f_alerts, f_count);
+ break;
+ default:
+ console.log(method+' default entered for: '+type);
+ break;
+ }
+}
+/**
+ *
+ * @param {jQuery} _target - jquery object to update
+ * @param {Array} _array - array of alerts for this member
+ * @param {jQuery} _count - jQuery object to hold alert count
+ */
+function toggleAlert(_target, _array, _count)
+{
+ if (_array.length < 1)
+ {$(_target).removeClass('on'); $(_target).addClass('off'); }
+ else {$(_count).empty(); $(_count).append(_array.length);
+ $(_target).removeClass('off'); $(_target).addClass('on'); }
+
+}
+/**
+ * check to see if _id is subscribing
+ * @param {Integer} _id - member id to seek
+ * @returns {String} - type of member
+ */
+function getSubscriber(_id)
+{
+ let type = 'none';
+ for (let each in subscribers){(function(_idx, _arr){if (_arr[_idx].id === _id){type=_arr[_idx].type;}})(each, subscribers);}
+ return(type);
+}
+/**
+ * subscribe to events
+ * @param {String} _type - member type
+ * @param {String} _id - member id
+ */
+function z2bSubscribe(_type, _id)
+{
+ subscribers.push({'type': _type, 'id': _id});
+}
+/**
+ * Unsubscribe to events
+ * @param {String} _id - member id to remove
+ */
+function z2bUnSubscribe(_id)
+{
+ let _s1 = subscribers;
+ let _s2 = [];
+ for (let each in _s1) {(function(_idx, _arr){if (_arr[_idx] != _id){_s2.push(_arr[_idx]);}})(each, _s1);}
+ subscribers = _s2;
+}
+/**
+ * notifyMe
+ * @param {Array} _alerts - array of alerts
+ * @param {String} _id - orderID
+ * @returns {Boolean} - true if found, false if not found
+ */
+function notifyMe (_alerts, _id)
+{
+ let b_h = false;
+ for (let each in _alerts) {(function(_idx, _arr){if (_id === _arr[_idx].order){b_h = true;}})(each, _alerts);}
+ return b_h;
+}
+/**
+ * connect to web socket
+ */
+function wsConnect()
+{
+ let method = 'wsConnect';
+ if (!window.WebSocket) {console.log('this browser does not support web sockets');}
+ let content = $('#body');
+ let blockchain = $('#blockchain');
+ // updated from ws: to wss: to support access over https
+ if (host_address.slice(0,9) === 'localhost')
+ {
+ wsSocket = new WebSocket('ws://'+host_address);
+ }else
+ {
+ wsSocket = new WebSocket('wss://'+host_address);
+ }
+ wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ', error);};
+ wsSocket.onopen = function ()
+ {console.log ('connect.onOpen initiated to: '+host_address); wsSocket.send('connected to client');};
+ wsSocket.onmessage = function (message)
+ {
+ let incoming
+ incoming = message.data;
+ // console.log(method+ ' incoming is: '+incoming);
+ while (incoming instanceof Object === false){incoming = JSON.parse(incoming);}
+ switch (incoming.type)
+ {
+ case 'Message':
+ content.append(formatMessage(incoming.data));
+ break;
+ case 'Alert':
+ let event = JSON.parse(incoming.data);
+ addNotification(event.type, event.ID, event.orderID);
+ break;
+ case 'BlockChain':
+ _blctr ++;
+ if (incoming.data !== 'connected')
+ {
+ $(blockchain).append('block '+incoming.data.header.number+' Hash: '+incoming.data.header.data_hash+'');
+ if (_blctr > 4) {let leftPos = $(blockchain).scrollLeft(); $(blockchain).animate({scrollLeft: leftPos + 300}, 250);}
+ }
+ break;
+ default:
+ console.log('Can Not Process message type: ',incoming.type);
+ }
+ };
}
\ No newline at end of file
diff --git a/Chapter11/HTML/favicon.ico b/Chapter11/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter11/HTML/favicon.ico and b/Chapter11/HTML/favicon.ico differ
diff --git a/Chapter11/HTML/js/z2b-admin.js b/Chapter11/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter11/HTML/js/z2b-admin.js
+++ b/Chapter11/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter11/HTML/js/z2b-buyer.js b/Chapter11/HTML/js/z2b-buyer.js
index 29b99c0..793ff73 100644
--- a/Chapter11/HTML/js/z2b-buyer.js
+++ b/Chapter11/HTML/js/z2b-buyer.js
@@ -15,6 +15,10 @@
// z2c-buyer.js
'use strict';
+let b_notify = '#buyer_notify';
+let b_count = '#buyer_count';
+let b_id = '';
+let b_alerts;
let orderDiv = 'orderDiv';
let itemTable = {};
@@ -28,31 +32,32 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
+ }
}
-}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#buyerbody').empty();
$('#buyerbody').append(page);
- // update the text on the page using the prompt data for the selected language
+ // empty the buyer alerts array
+ b_alerts = [];
+ // if there are no alerts, then remove the 'on' class and add the 'off' class
+ if (b_alerts.length === 0)
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
+ else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
+ // update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -67,8 +72,21 @@ function setupBuyer(page, port)
}
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
- // create a function to execute when the user selects a different buyer
- $('#buyer').on('change', function() { _orderDiv.empty(); $('#buyer_messages').empty(); $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName; });
+ // save the current buyer id as b_id
+ b_id = buyers[0].id;
+ // subscribe to events
+ z2bSubscribe('Buyer', b_id);
+ // create a function to execute when the user selects a different buyer
+ $('#buyer').on('change', function()
+ { _orderDiv.empty(); $('#buyer_messages').empty();
+ $('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
+ // unsubscribe the current buyer
+ z2bUnSubscribe(b_id);
+ // get the new buyer id
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
+ // subscribe the new buyer
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -97,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -185,8 +203,8 @@ function listOrders()
* used by the listOrders() function
* formats the orders for a buyer. Orders to be formatted are provided in the _orders array
* output replaces the current contents of the html element identified by _target
- * @param _target - string with div id prefaced by #
- * @param _orders - array with order objects
+ * @param {String} _target - string with div id prefaced by #
+ * @param {Array} _orders - array with order objects
*/
function formatOrders(_target, _orders)
{
@@ -267,7 +285,7 @@ function formatOrders(_target, _orders)
_action += '';
if (_idx > 0) {_str += '';}
_str += '
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -166,7 +174,7 @@ function formatSellerOrders(_target, _orders)
_target.append(_str);
for (let each in _orders)
{(function(_idx, _arr)
- { $('#s_btn_'+_idx).on('click', function ()
+ { $('#s_btn_'+_idx).on('click', function ()
{
let options = {};
options.action = $('#s_action'+_idx).find(':selected').text();
@@ -178,6 +186,9 @@ function formatSellerOrders(_target, _orders)
$.when($.post('/composer/client/orderAction', options)).done(function (_results)
{ $('#seller_messages').prepend(formatMessage(_results.result)); });
});
+ if (notifyMe(s_alerts, _arr[_idx].id)) {$('#s_status'+_idx).addClass('highlight'); }
})(each, _orders);
}
+ s_alerts = new Array();
+ toggleAlert($('#seller_notify'), s_alerts, s_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter11/HTML/js/z2b-shipper.js b/Chapter11/HTML/js/z2b-shipper.js
index 04c13a6..049d021 100644
--- a/Chapter11/HTML/js/z2b-shipper.js
+++ b/Chapter11/HTML/js/z2b-shipper.js
@@ -17,6 +17,10 @@
'use strict';
let shipperOrderDiv = 'shipperOrderDiv';
+let sh_alerts = [];
+let sh_notify = '#shipper_notify';
+let sh_count = '#shipper_count';
+let sh_id;
/**
* load the shipper User Experience
@@ -24,29 +28,27 @@ let shipperOrderDiv = 'shipperOrderDiv';
function loadShipperUX ()
{
let toLoad = 'shipper.html';
- getPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupShipper(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupShipper(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupShipper(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupShipper(page);});
}
}
/**
*
* @param {String} page - the page to load
- * @param {Integer} port - the web socket to use
*/
-function setupShipper(page, port)
+function setupShipper(page)
{
$('#shipperbody').empty();
$('#shipperbody').append(page);
+ if (sh_alerts.length === 0)
+ {$(sh_notify).removeClass('on'); $(sh_notify).addClass('off'); }
+ else {$(sh_notify).removeClass('off'); $(sh_notify).addClass('on'); }
updatePage('shipper');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('shipper_messages', msgPort);
let _clear = $('#shipper_clear');
let _list = $('#shipperOrderStatus');
let _orderDiv = $('#'+shipperOrderDiv);
@@ -56,9 +58,15 @@ function setupShipper(page, port)
$('#shipper').append(sh_string);
$('#shipperCompany').empty();
$('#shipperCompany').append(providers[0].companyName);
+ sh_id = shippers[0].id;
+ z2bSubscribe('Shipper', sh_id);
+ // create a function to execute when the user selects a different provider
$('#shipper').on('change', function() {
$('#shipperCompany').empty(); _orderDiv.empty(); $('#shipper_messages').empty();
$('#shipperCompany').append(findMember($('#shipper').find(':selected').val(),shippers).companyName);
+ z2bUnSubscribe(sh_id);
+ sh_id = findMember($('#shipper').find(':selected').text(),shippers).id;
+ z2bSubscribe('Shipper', sh_id);
});
}
/**
@@ -87,7 +95,7 @@ function listShipperOrders()
function formatShipperOrders(_target, _orders)
{
_target.empty();
- let _str = ''; let _date = ''; let _statusText;
+ let _str = ''; let _date = ''; let _statusText;
for (let each in _orders)
{(function(_idx, _arr)
{ let _action = '
';
_str+= '
'+textPrompts.orderProcess.itemno+'
'+textPrompts.orderProcess.description+'
'+textPrompts.orderProcess.qty+'
'+textPrompts.orderProcess.price+'
'
for (let every in _arr[_idx].items)
{(function(_idx2, _arr2)
@@ -173,6 +181,9 @@ function formatShipperOrders(_target, _orders)
$('#shipper_messages').prepend(formatMessage(_results.result));
});
});
+ if (notifyMe(sh_alerts, _arr[_idx].id)) {$("#sh_status"+_idx).addClass('highlight'); }
})(each, _orders);
}
-}
\ No newline at end of file
+ sh_alerts = new Array();
+ toggleAlert($('#shipper_notify'), sh_alerts, sh_alerts.length);
+ }
\ No newline at end of file
diff --git a/Chapter11/HTML/js/z2b-utilities.js b/Chapter11/HTML/js/z2b-utilities.js
index ae1637f..7f2c962 100644
--- a/Chapter11/HTML/js/z2b-utilities.js
+++ b/Chapter11/HTML/js/z2b-utilities.js
@@ -6,68 +6,72 @@
* 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,
+ * 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.
*/
-// z2c-utilities.js
-
+// z2c-utilities.js
+'use strict';
/**
* creates a set of utilities inside the named space: z2c
* All utilities are accessed as z2c.functionName()
* @namespace - z2c
*/
-languages = {}, // getSupportedLanguages
-selectedLanguage = {},
-language = "",
-textLocations = {}, // getTextLocations
-textPrompts = {}, // getSelectedPromots
+let languages = {}, // getSupportedLanguages
+ selectedLanguage = {},
+ language = '',
+ textLocations = {}, // getTextLocations
+ textPrompts = {}, // getSelectedPromots
+ subscribers = new Array(); // subscribers to business events
/**
* get the value associated with a cookie named in the input
* Refer to this by {@link getCookieValue}.
* @param {String} _name - the name of the cookie to find
-* @namespace
+* @returns {String} - cookie value
+* @namespace
*/
function getCookieValue(_name)
{
- var name = _name+"=";
- var cookie_array= document.cookie.split(";");
- for (each in cookie_array)
- { var c = cookie_array[each].trim();
- if(c.indexOf(name) == 0) return(c.substring(name.length, c.length));
- }
- return("");
+ let name = _name+'=';
+ let cookie_array= document.cookie.split(';');
+ for (let each in cookie_array)
+ {
+ let c = cookie_array[each].trim();
+ if(c.indexOf(name) === 0) {return(c.substring(name.length, c.length));}
+ }
+ return('');
}
/**
* trims a string by removing all leading and trailing spaces
* trims the final period, if it exists, from a string.
* Refer to this by {@link trimStrip}.
-* @param {String} _string String to be trimmed and stripped of trailing period
-* @namespace
+* @param {String} _string - String to be trimmed and stripped of trailing period
+* @returns {String} - trimmed string
+* @namespace
*/
function trimStrip(_string)
{
- var str = _string.trim();
- var len = str.length;
- if(str.endsWith(".")) {str=str.substring(0,len-1);}
- return(str);
+ let str = _string.trim();
+ let len = str.length;
+ if(str.endsWith('.')) {str=str.substring(0,len-1);}
+ return(str);
}
/**
* replaces text on an html page based on the anchors and text provided in a JSON textPrompts object
* Refer to this by {@link updatePage}.
* @param {String} _page - a string representing the name of the html page to be updated
-* @namespace
+* @namespace
*/
function updatePage(_page)
{
- for (each in textPrompts[_page]){(function(_idx, _array)
- {$("#"+_idx).empty();$("#"+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
+ for (let each in textPrompts[_page]){(function(_idx, _array)
+ {$('#'+_idx).empty();$('#'+_idx).append(getDisplaytext(_page, _idx));})(each, textPrompts[_page])}
}
/**
@@ -75,7 +79,8 @@ function updatePage(_page)
* Refer to this by {@link getDisplaytext}.
* @param {String} _page - string representing the name of the html page to be updated
* @param {String} _item - string representing the html named item to be updated
-* @namespace
+* @returns {String} - text to be placed on web page
+* @namespace
*/
function getDisplaytext(_page, _item)
{return (textPrompts[_page][_item]);}
@@ -85,54 +90,56 @@ function getDisplaytext(_page, _item)
* Refer to this by {@link goMultiLingual}.
* @param {String} _language - language to be used in this session
* @param {String} _page - string representing html page to be updated in the selected language
-* @namespace
+* @namespace
*/
function goMultiLingual(_language, _page)
{ language = _language;
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {languages = _res;
- selectedLanguage = languages[_language];
- var options = {}; options.language = _language;
- $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
- {textLocations = _locations;
- textPrompts = JSON.parse(_prompts[0]);
- updatePage(_page);
+ $.when($.get('/api/getSupportedLanguages')).done(function(_res)
+ {languages = _res;
+ selectedLanguage = languages[_language];
+ let options = {}; options.language = _language;
+ $.when($.get('/api/getTextLocations'),$.post('/api/selectedPrompts', options)).done(function(_locations, _prompts)
+ {textLocations = _locations;
+ textPrompts = JSON.parse(_prompts[0]);
+ updatePage(_page);
+ });
+ let _choices = $('#lang_choices');
+ _choices.empty(); let _str = '';
+ for (let each in _res)
+ {(function(_idx, _array)
+ {if (_array[_idx].active === 'yes')
+ {_str += '
'}
- })(each, _res)}
- _choices.append(_str);
- });
}
/**
* get SupportedLanguages returns an html menu object with available languages
* Refer to this by {@link getSupportedLanguages}.
-* @namespace
+* @namespace
*/
function getSupportedLanguages()
{
- $.when($.get("/api/getSupportedLanguages")).done(function(_res)
- {
- languages = _res; console.log(_res); var _choices = $("#lang_choices");
- _choices.empty(); var _str = "";
- for (each in _res)
- {(function(_idx, _array)
- {if (_array[_idx].active == "yes")
- {_str += '
';}
+ })(each, _res);
+ }
+ _choices.append(_str);
+ });
}
/**
* returns a JSON object with the pages and objects which support text replacement
* Refer to this by {@link getTextLocations}.
-* @namespace
+* @namespace
*/
function getTextLocationsfunction ()
{$.when($.get('/api/getTextLocations')).done(function(_res){textLocations = _res; console.log(_res); });}
@@ -140,38 +147,39 @@ function getTextLocationsfunction ()
/**
* returns a JSON object with the text to be used to update identified pages and objects
* Refer to this by {@link getSelectedPrompts}.
-* @param {String} _inbound
-* @namespace
+* @param {String} _inbound - page or object to receive updated text
+* @namespace
*/
function getSelectedPrompts(_inbound)
{ selectedLanguage=languages[_inbound];
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function(_res){textPrompts = _res; console.log(_res); });
}
/**
* retrieves the prompts for the requested language from the server
* Refer to this by {@link qOnSelectedPrompts}.
* @param {String} _inbound - string representing the requested language
-* @namespace
+* @returns {Promise} - returns promise when selected prompts have been retrieved from server
+* @namespace
*/
function qOnSelectedPrompts(_inbound)
{
- var d_prompts = $.Deferred();
- var options = {}; options.language = _inbound;
- $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
- return d_prompts.promise();
+ let d_prompts = $.Deferred();
+ let options = {}; options.language = _inbound;
+ $.when($.post('/api/selectedPrompts', options)).done(function (p) {d_prompts.resolve(p);}).fail(d_prompts.reject);
+ return d_prompts.promise();
}
/**
* function to display the properties of an object using console.log
* Refer to this by {@link displayObjectProperties}.
* @param {Object} _obj - the object whose properties are to be displayed
-* @namespace
+* @namespace
*/
function displayObjectProperties(_obj)
{
- for(var propt in _obj){ console.log("object property: "+propt ); }
+ for(let propt in _obj){ console.log('object property: '+propt ); }
}
/**
@@ -179,12 +187,12 @@ function displayObjectProperties(_obj)
* Refer to this by {@link displayObjectValues}.
* @param {String} _string - an arbitrary string to preface the printing of the object property name and value. often used to display the name of the object being printed
* @param {Object} _object - the object to be introspected
-* @namespace
+* @namespace
*/
function displayObjectValues(_string, _object)
{
- for (prop in _object){
- console.log(_string+prop+": "+(((typeof(_object[prop]) == 'object') || (typeof(_object[prop]) == 'function')) ? typeof(_object[prop]) : _object[prop]));
+ for (let prop in _object){
+ console.log(_string+prop+': '+(((typeof(_object[prop]) === 'object') || (typeof(_object[prop]) === 'function')) ? typeof(_object[prop]) : _object[prop]));
}
}
@@ -201,124 +209,126 @@ function displayObjectValues(_string, _object)
*/
String.prototype.format = function(i, safe, arg) {
+/**
+ * the format function added to String.prototype
+ * @returns {String} - returns original string with {x} replaced by provided text
+ */
+ function format() {
+ let str = this, len = arguments.length+1;
- function format() {
- var str = this, len = arguments.length+1;
-
- // For each {0} {1} {n...} replace with the argument in that position. If
- // the argument is an object or an array it will be stringified to JSON.
- for (i=0; i < len; arg = arguments[i++]) {
- safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
- str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ // For each {0} {1} {n...} replace with the argument in that position. If
+ // the argument is an object or an array it will be stringified to JSON.
+ for (i=0; i < len; arg = arguments[i++]) {
+ safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
+ str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
+ }
+ return str;
}
- return str;
- }
- // Save a reference of what may already exist under the property native.
- // Allows for doing something like: if("".format.native) { /* use native */ }
- format.native = String.prototype.format;
-
- // Replace the prototype property
- return format;
+ // Save a reference of what may already exist under the property native.
+ // Allows for doing something like: if(''.format.native) { /* use native */ }
+ format.native = String.prototype.format;
+ // Replace the prototype property
+ return format;
}();
/**
* display the hyperledger apis as currently understood
* Refer to this by {@link showAPIDocs}.
- *
+ *
*/
function showAPIDocs()
{
- $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
- {
- var _target = $("#body");
- _target.empty(); _target.append(_page[0]);
- displayAPI(_res[0]);
- });
+ $.when($.get('/resources/getDocs'),$.get('hfcAPI.html')).done(function(_res, _page)
+ {
+ let _target = $('#body');
+ _target.empty(); _target.append(_page[0]);
+ displayAPI(_res[0]);
+ });
}
/**
- *
- * @param {JSON} _api
+ *
+ * @param {JSON} _api
* Refer to this by {@link displayAPI}.
- *
+ *
*/
function displayAPI(_api)
{
- var _exports = _api.hfcExports;
- var _classes = _api.hfcClasses;
- var _eTarget = $("#hfc_exports");
- var _cTarget = $("#hfc_classes");
- var _str = "";
- for (each in _exports) {
- (function(_idx, _arr){
- _curObj = Object.getOwnPropertyNames(_arr[_idx]);
- _str += "
";
- })(every, _arr[_idx][_curObj[0]])
- }
- })(each, _classes);
- }
- _cTarget.append(_str);
+ let _exports = _api.hfcExports;
+ let _classes = _api.hfcClasses;
+ let _eTarget = $('#hfc_exports');
+ let _cTarget = $('#hfc_classes');
+ let _str = '';
+ for (let each in _exports) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ _str += '
'+_curObj+'
'+_arr[_idx][_curObj]+'
';
+ })(each, _exports);
+ }
+ _eTarget.append(_str);
+ _str = '';
+ for (let each in _classes) {
+ (function(_idx, _arr){
+ let _curObj = Object.getOwnPropertyNames(_arr[_idx]);
+ for (let every in _arr[_idx][_curObj[0]]){
+ (function(_idx2, _arr2)
+ {
+ let _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]);
+ _str+= '
'+_curObj[0]+'
'+_curObj2+'
'+_arr2[_idx2][_curObj2[0]]+'
';
+ })(every, _arr[_idx][_curObj[0]]);
+ }
+ })(each, _classes);
+ }
+ _cTarget.append(_str);
}
/**
* format messages for display
+ * @param {String} _msg - text to be enclosed in html message format
+ * @returns {String} - html formatted message
*/
function formatMessage(_msg) {return '
'+_msg+'
';}
+
/**
- * get the web socket port
+ * closes all accordians in this div
+ * @param {String} target - formatted jQuery string pointing to div with all accordians to collapse
*/
-function getPort ()
+function accOff(target)
{
- if (msgPort == null)
- { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});}
+ let thisElement = $(target);
+ let childNodes = thisElement.children();
+ for (let each in childNodes)
+ {let node = '#'+childNodes[each].id;
+ if (node !== '#')
+ {
+ if($(node).hasClass('on')) {$(node).removeClass('on');}
+ $(node).addClass('off');
+ }
+ }
}
+
/**
* toggle an accordian window
+ * @param {String} _parent - Div holding all accordians
+ * @param {String} _body - Div which only appears when accordian is expanded
+ * @param {HTMLDiv} _header - Div which appears when accordian is collapsed
*/
function accToggle(_parent, _body, _header)
{
- var parent = "#"+_parent;
- var body="#"+_body;
- var header = _header;
- if ($(body).hasClass("on"))
- {$(body).removeClass("on"); $(body).addClass("off");
- $(parent).removeClass("on"); $(parent).addClass("off");
- }else
- {
- accOff(parent);
- $(body).removeClass("off"); $(body).addClass("on");
- $(parent).removeClass("off"); $(parent).addClass("on");
- }
-}
-/**
- *
- */
-function accOff(target)
-{
- var thisElement = $(target);
- var childNodes = thisElement.children();
- for (each in childNodes)
- {var node = "#"+childNodes[each].id;
- if (node != '#')
- {
- if($(node).hasClass("on")) {$(node).removeClass("on");}
- $(node).addClass("off");
- }
- }
+ let parent = '#'+_parent;
+ let body='#'+_body;
+ if ($(body).hasClass('on'))
+ {
+ $(body).removeClass('on'); $(body).addClass('off');
+ $(parent).removeClass('on'); $(parent).addClass('off');
+ }else
+ {
+ accOff(parent);
+ $(body).removeClass('off'); $(body).addClass('on');
+ $(parent).removeClass('off'); $(parent).addClass('on');
+ }
}
diff --git a/Chapter11/controller/restapi/features/composer/Z2B_Services.js b/Chapter11/controller/restapi/features/composer/Z2B_Services.js
index 21dbb85..c4f8aa4 100644
--- a/Chapter11/controller/restapi/features/composer/Z2B_Services.js
+++ b/Chapter11/controller/restapi/features/composer/Z2B_Services.js
@@ -13,16 +13,18 @@
*/
'use strict';
-var fs = require('fs');
-var path = require('path');
+let fs = require('fs');
+let path = require('path');
const sleep = require('sleep');
-const ws = require('websocket');
-const http = require('http');
+// const ws = require('websocket');
+// const http = require('http');
+// const url = require('url');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
+const util = require('./Z2B_Utilities');
app.set('port', appEnv.port);
@@ -37,7 +39,7 @@ app.set('port', appEnv.port);
* @class
* @memberof module:Z2Blockchain
*/
-var Z2Blockchain = {
+let Z2Blockchain = {
/**
* create an empty order. This is used by any server side routine that needs to create an new
@@ -105,8 +107,8 @@ var Z2Blockchain = {
/**
* update item quantity. used by the autoLoad process.
* @param {item_number} _itemNo - item number to find
- * @param {vendor_array} _itemArray - item array from order
* @param {item_number} _qty - quantity to change * @utility
+ * @param {vendor_array} _itemArray - item array from order
*/
setItem: function (_itemNo, _qty, _itemArray)
{
@@ -115,46 +117,78 @@ var Z2Blockchain = {
},
/**
* supplemental routine to resubmit orders when MVCC_READ_CONFLICT encountered
+ * @param {object} _con - web socket connection
* @param {transaction} _item - transaction to process
* @param {order_object} _id - order id
- * @param {bnc} businessNetworkConnection - already created business network connection
+ * @param {BusinessNetworkConnection} businessNetworkConnection - already created business network connection
+ * @returns {promise} promise
*/
loadTransaction: function (_con, _item, _id, businessNetworkConnection)
{
+ let method = 'loadTransaction';
return businessNetworkConnection.submitTransaction(_item)
.then(() => {
- console.log('loadTransaction: order '+_id+' successfully added');
- _con.sendUTF('loadTransaction: order '+_id+' successfully added');
+ console.log(method+': order '+_id+' successfully added ');
+ this.send(_con, 'Message', 'Order '+_id+' successfully added');
})
.catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{sleep.sleep(5);
- console.log(_id+" loadTransaction retrying submit transaction for: "+_id);
- this.loadTransaction(_con,_item, _id, businessNetworkConnection);
+ console.log(_id+' loadTransaction retrying submit transaction for: '+_id);
+ this.loadTransaction(_con, _item, _id, businessNetworkConnection);
}
});
},
/**
* add an order to a registry. This adds an Asset and does not execute a transaction
- * @param {order_object} _order - order_object to process
- * @param {assetRegistry} _registry - registry into which asset (order) should be placed
+ * @param {order_object} _con - websocket
+ * @param {assetRegistry} _order - order_object to process
+ * @param {networkTransaction} _registry - registry into which asset (order) should be placed
* @param {networkTransaction} _createNew - transaction to be processed after order successfully added
- * @param {businessNetworkConnection} _bnc - business network connection to use */
- addOrder: function (_con, _order, _registry, _createNew, _bnc)
- {
- return _registry.add(_order)
- .then(() => {
- this.loadTransaction(_con,_createNew, _order.orderNumber, _bnc);
- })
- .catch((error) => {
- if (error.message.search('MVCC_READ_CONFLICT') != -1)
- {console.log(_order.orderNumber+" addOrder retrying assetRegistry.add for: "+_order.orderNumber);
- this.addOrder(_con,_order, _registry, _createNew, _bnc);
- }
- else {console.log('error with assetRegistry.add', error)}
- });
+ * @param {businessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+addOrder: function (_con, _order, _registry, _createNew, _bnc)
+{
+ let method = 'addOrder';
+ return _registry.add(_order)
+ .then(() => {
+ this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(_order.orderNumber+' addOrder retrying assetRegistry.add for: '+_order.orderNumber);
+ this.addOrder(_con, _order, _registry, _createNew, _bnc);
+ }
+ else {console.log(method+' error with assetRegistry.add', error);}
+ });
},
+/**
+ * repeats the bind identity request
+ * @param {WebSocket} _con - order_object to process
+ * @param {String} _id - registry into which asset (order) should be placed
+ * @param {String} _cert - transaction to be processed after order successfully added
+ * @param {BusinessNetworkConnection} _bnc - business network connection to use
+ * @returns {promise} promise
+ */
+bindIdentity: function (_con, _id, _cert, _bnc)
+{
+ let method = 'bindIdentity';
+ console.log(method+' retrying bindIdentity for: '+_id);
+ return _bnc.bindIdentity(_id, _cert)
+ .then(() => {
+ console.log(method+' Succeeded for: '+_id);
+ })
+ .catch((error) => {
+ if (error.message.search('MVCC_READ_CONFLICT') !== -1)
+ {console.log(' bindIdentity retrying _bnc.bindIdentity(_id, _cert) for: '+_id);
+ this.bindIdentity(_con, _id, _cert, _bnc);
+ }
+ else {console.log(method+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);}
+ });
+},
+
/**
* saves the member table with ids and secrets
* @param {array} _table - array of JSON objects to save to file
@@ -166,23 +200,26 @@ var Z2Blockchain = {
let _mem = '{"members": [';
for (let each in _table)
{(function(_idx, _arr)
- {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table)}
+ {if(_idx>0){_mem += ', ';} _mem +=JSON.stringify(_arr[_idx]);})(each, _table);}
_mem += ']}';
fs.writeFileSync(newFile, _mem, options);
},
/**
* saves the item table * @param {array} _table - array of JSON objects to save to file
+ * @param {JSON} _table - data to be saved
*/
- saveItemTable: function (_table)
- {
- let options = { flag : 'w' };
- let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
- let _mem = '{"items": [';
- for (let each in _table)
- {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table)}
- _mem += ']}';
- fs.writeFileSync(newFile, _mem, options);
- },
+saveItemTable: function (_table)
+{
+ console.log('_table: ', _table);
+ let options = { flag : 'w' };
+ let newFile = path.join(path.dirname(require.main.filename),'startup','itemList.txt');
+ let _mem = '{"items": [';
+ for (let each in _table)
+ {(function(_idx, _arr){if(_idx>0){_mem += ', ';} _mem += JSON.stringify(_arr[_idx]);})(each, _table);}
+ _mem += ']}';
+ console.log('_mem: ', _mem);
+ fs.writeFileSync(newFile, _mem, options);
+},
/**
* update an empty order with 4 items. update the amount field based on the sum of the line items
* @param {addItems} _inbound - Order created with factory.newResource(NS, 'Order',.orderNumber)
@@ -205,7 +242,7 @@ var Z2Blockchain = {
_arr[_idx].extendedPrice = _item.unitPrice*_arr[_idx].quantity;
_amount += _arr[_idx].extendedPrice;
_items.push(JSON.stringify(_arr[_idx]));
- })(each, _inbound.items)}
+ })(each, _inbound.items);}
return ({'items': _items, 'amount': _amount});
},
/**
@@ -213,22 +250,23 @@ var Z2Blockchain = {
* was not initially working. This function is no longer in use.
* @param {Order} _order - the inbound Order item retrieved from a registry
* @return JSON object order elements
+ * @return {Order} JSON object order elements
* @function
*/
- getOrderData: function (_order)
- {
- let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
- 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
- var _obj = {};
- for (let each in orderElements){(function(_idx, _arr)
- { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements)}
- _obj.buyer = _order.buyer.$identifier;
- _obj.seller = _order.seller.$identifier;
- _obj.provider = _order.seller.$provider;
- _obj.shipper = _order.seller.$shipper;
- _obj.financeCo = _order.seller.$financeCo;
- return (_obj);
- },
+getOrderData: function (_order)
+{
+ let orderElements = ['items', 'status', 'amount', 'created', 'cancelled', 'bought', 'ordered', 'dateBackordered', 'requestShipment', 'delivered', 'delivering', 'approved',
+ 'disputeOpened', 'disputeResolved', 'paymentRequested', 'orderRefunded', 'paid', 'dispute', 'resolve', 'backorder', 'refund'];
+ let _obj = {};
+ for (let each in orderElements){(function(_idx, _arr)
+ { _obj[_arr[_idx]] = _order[_arr[_idx]]; })(each, orderElements);}
+ _obj.buyer = _order.buyer.$identifier;
+ _obj.seller = _order.seller.$identifier;
+ _obj.provider = _order.seller.$provider;
+ _obj.shipper = _order.seller.$shipper;
+ _obj.financeCo = _order.seller.$financeCo;
+ return (_obj);
+},
/**
* JSON object of available order status types and codes. This is used by nodejs
@@ -252,67 +290,15 @@ var Z2Blockchain = {
Refunded: {code: 13, text: 'Order Refunded'}
},
/**
- * the user experience is enhanced if the browser can be notified of aysnchronous events.
- * the createMessateSockt function creates a web socket service to which the browser can
- * attach.
- * @param {integer} _port - port number to use for this socket connection
- * @returns {websocket} - web socket connection to be used on the server side.
- */
- m_connection: null,
- m_socketAddr: null,
- m_socket: null,
- createMessageSocket: function (_port)
- {
- var port = (typeof(_port) == 'undefined' || _port == null) ? app.get('port')+1 : _port
- if (this.m_socket == null)
- {
- this.m_socketAddr = port;
- this.m_socket= new ws.server({httpServer: http.createServer().listen(this.m_socketAddr)});
- var _this = this;
- this.m_socket.on('request', function(request)
- {
- _this.m_connection = request.accept(null, request.origin);
- _this.m_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.m_connection.sendUTF('connected');
- _this.m_connection.on('close', function(m_connection) {console.log('m_connection closed'); });
- });
- });
- }
- return {conn: this.m_connection, socket: this.m_socketAddr};
- },
-/**
- * the cs_connection is used to display blockchain information to the web browser over
- * a sepaarate port from the user experience socket.
- * @returns {websocket} - web socket connection to be used on the server side.
+ * New code to support sending messages to socket clients
+ * @param {Object} _locals - shared variables and functions from index.js
+ * @param {String} type - type of event message to put on channel
+ * @param {Event} event - event message
*/
-
- cs_connection: null,
- cs_socketAddr: null,
- cs_socket: null,
- createChainSocket: function ()
- {
- var port = app.get('port')+2;
- if (this.cs_socket == null)
- {
- this.cs_socketAddr = port;
- this.cs_socket= new ws.server({httpServer: http.createServer().listen(this.cs_socketAddr)});
- var _this = this;
- this.cs_socket.on('request', function(request)
- {
- _this.cs_connection = request.accept(null, request.origin);
- _this.cs_connection.on('message', function(message)
- {
- console.log(message.utf8Data);
- _this.cs_connection.sendUTF('connected');
- _this.cs_connection.on('close', function(cs_connection) {console.log('cs_connection closed'); });
- });
- });
- }
- return {conn: this.cs_connection, socket: this.cs_socketAddr};
- }
-
+send: function (_locals, type, event)
+{
+ _locals.processMessages({'type': type, 'data': event} );
}
+};
module.exports = Z2Blockchain;
\ No newline at end of file
diff --git a/Chapter11/controller/restapi/features/composer/autoLoad.js b/Chapter11/controller/restapi/features/composer/autoLoad.js
index ce3d81b..da256aa 100644
--- a/Chapter11/controller/restapi/features/composer/autoLoad.js
+++ b/Chapter11/controller/restapi/features/composer/autoLoad.js
@@ -34,32 +34,13 @@ const financeCoID = 'easymoney@easymoneyinc.com';
const svc = require('./Z2B_Services');
const config = require('../../../env.json');
+
/**
* itemTable and memberTable are used by the server to reduce load time requests
* for member secrets and item information
*/
let itemTable = new Array();
let memberTable = new Array();
-let socketAddr;
-
-
-
-
-
-/**
- * getPort is used to return the port number for socket interactions so that
- * the browser can receive asynchronous notifications of work in process.
- * This helps the user understand the current status of the auto load process.
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- *
- * @function
- */
-exports.getPort = function(req, res, next) {
- let _conn = svc.createMessageSocket();
- res.send({'port': _conn.socket});
-};
/**
* autoLoad reads the memberList.json file from the Startup folder and adds members,
@@ -78,9 +59,9 @@ exports.autoLoad = function(req, res, next) {
// connect to the network
let businessNetworkConnection;
let factory; let participant;
- svc.createMessageSocket();
- socketAddr = svc.m_socketAddr;
- let adminConnection = new AdminConnection();
+// svc.createMessageSocket();
+// socketAddr = svc.m_socketAddr;
+let adminConnection = new AdminConnection();
// connection prior to V0.15
// adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW)
// connection in v0.15
@@ -110,7 +91,7 @@ exports.autoLoad = function(req, res, next) {
return participantRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] member with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id);
@@ -118,7 +99,7 @@ exports.autoLoad = function(req, res, next) {
participantRegistry.add(participant)
.then(() => {
console.log('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
- svc.m_connection.sendUTF('['+_idx+'] '+_arr[_idx].companyName+' successfully added');
+ svc.send(req.app.locals, 'Message', '['+_idx+'] '+_arr[_idx].companyName+' successfully added');
})
.then(() => {
// an identity is required before a member can take action in the network.
@@ -144,7 +125,9 @@ exports.autoLoad = function(req, res, next) {
config.connectionProfile.keyValStore = _home+config.connectionProfile.keyValStore;
let tempCard = new hlc_idCard(_meta, config.connectionProfile);
return adminConnection.importCard(result.userID, tempCard)
- .then ((_res) => { if (_res) {console.log('card updated');} else {console.log('card imported');} })
+ .then ((_res) => {
+ if (_res) {console.log('card updated');} else {console.log('card imported');}
+ })
.catch((error) => {
console.error('adminConnection.importCard failed. ',error.message);
});
@@ -172,7 +155,7 @@ exports.autoLoad = function(req, res, next) {
return assetRegistry.get(_arr[_idx].id)
.then((_res) => {
console.log('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
- svc.m_connection.sendUTF('['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
+ svc.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type);
})
.catch((error) => {
// first, an Order Object is created
@@ -186,8 +169,8 @@ exports.autoLoad = function(req, res, next) {
const createNew = factory.newTransaction(config.composer.NS, 'CreateOrder');
order.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer);
order.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller);
- order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummy');
- order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummy');
+ order.provider = factory.newRelationship(config.composer.NS, 'Provider', 'noop@dummyProvider');
+ order.shipper = factory.newRelationship(config.composer.NS, 'Shipper', 'noop@dummyShipper');
order.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.financeCo = factory.newRelationship(config.composer.NS, 'FinanceCo', financeCoID);
createNew.order = factory.newRelationship(config.composer.NS, 'Order', order.$identifier);
@@ -199,7 +182,7 @@ exports.autoLoad = function(req, res, next) {
.then(() => {
// then a createOrder transaction is processed which uses the chaincode
// establish the order with it's initial transaction state.
- svc.loadTransaction(svc.m_connection, createNew, order.orderNumber, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection);
})
.catch((error) => {
// in the development environment, because of how timing is set up, it is normal to
@@ -207,8 +190,8 @@ exports.autoLoad = function(req, res, next) {
// logical transaction error.
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log('AL: '+_arr[_idx].id+' retrying assetRegistry.add for: '+_arr[_idx].id);
- svc.addOrder(svc.m_connection, order, assetRegistry, createNew, businessNetworkConnection);
- }
+ svc.addOrder(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection);
+ }
else {console.log('error with assetRegistry.add', error.message);}
});
});
@@ -220,7 +203,7 @@ exports.autoLoad = function(req, res, next) {
.catch((error) => {console.log('error with business network Connect', error.message);});
})
.catch((error) => {console.log('error with adminConnect', error.message);});
- res.send({'port': socketAddr});
+ res.send({'result': 'Success'});
};
/**
diff --git a/Chapter11/controller/restapi/features/composer/hlcAdmin.js b/Chapter11/controller/restapi/features/composer/hlcAdmin.js
index 8e9e8b9..46b188c 100644
--- a/Chapter11/controller/restapi/features/composer/hlcAdmin.js
+++ b/Chapter11/controller/restapi/features/composer/hlcAdmin.js
@@ -24,8 +24,7 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
-// const svc = require('./Z2B_Services');
-// const mod = 'hlcAdmin.js';
+
/**
* display the admin and network info
diff --git a/Chapter11/controller/restapi/features/composer/hlcClient.js b/Chapter11/controller/restapi/features/composer/hlcClient.js
index bc5bfb6..fc7955c 100644
--- a/Chapter11/controller/restapi/features/composer/hlcClient.js
+++ b/Chapter11/controller/restapi/features/composer/hlcClient.js
@@ -18,11 +18,12 @@ let fs = require('fs');
let path = require('path');
const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection;
const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition;
-// const config = require('../../../env.json');
+const config = require('../../../env.json');
const NS = 'org.acme.Z2BTestNetwork';
let itemTable = null;
const svc = require('./Z2B_Services');
const financeCoID = 'easymoney@easymoneyinc.com';
+let bRegistered = false;
/**
* get orders for buyer with ID = _id
@@ -244,7 +245,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -329,7 +330,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -338,7 +339,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -353,7 +354,97 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
+
+/**
+ * _monitor
+ * @param {WebSocket} _conn - web socket to use for member event posting
+ * @param {WebSocket} _f_conn - web sockect to use for FinanceCo event posting
+ * @param {Event} _event - the event just emitted
+ *
+ */
+function _monitor(locals, _event)
+{
+ let method = '_monitor';
+ console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
+ // create an event object and give it the event type, the orderID, the buyer id and the eventID
+ // send that event back to the requestor
+ // ========> Your Code Goes Here <=========
+
+ // using switch/case logic, send events back to each participant who should be notified.
+ // for example, when a seller requests payment, they should be notified when the transaction has completed
+ // and the financeCo should be notified at the same time.
+ // so you would use the _conn connection to notify the seller and the
+ // _f_conn connection to notify the financeCo
+
+ switch (_event.$type)
+ {
+ case 'Created':
+ break;
+ case 'Bought':
+ case 'PaymentRequested':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'Ordered':
+ case 'Cancelled':
+ case 'Backordered':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'ShipRequest':
+ case 'DeliveryStarted':
+ case 'DeliveryCompleted':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'DisputeOpened':
+ case 'Resolved':
+ case 'Refunded':
+ case 'Paid':
+ // ========> Your Code Goes Here <=========
+ break;
+ case 'PaymentAuthorized':
+ // ========> Your Code Goes Here <=========
+ break;
+ default:
+ break;
+ }
+
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter11/controller/restapi/features/composer/queryBlockChain.js b/Chapter11/controller/restapi/features/composer/queryBlockChain.js
index 9b6a396..75b67d3 100644
--- a/Chapter11/controller/restapi/features/composer/queryBlockChain.js
+++ b/Chapter11/controller/restapi/features/composer/queryBlockChain.js
@@ -12,8 +12,9 @@
* limitations under the License.
*/
-var path = require('path');
-var fs = require('fs');
+'use strict';
+let path = require('path');
+let fs = require('fs');
const express = require('express');
const app = express();
const cfenv = require('cfenv');
@@ -24,10 +25,10 @@ const hfc = require('fabric-client');
const hfcEH = require('fabric-client/lib/EventHub');
const svc = require('./Z2B_Services');
-const util = require('./Z2B_Utilities');
-const financeCoID = 'easymoney@easymoneyinc.com';
+// const util = require('./Z2B_Utilities');
+// const financeCoID = 'easymoney@easymoneyinc.com';
const config = require('../../../env.json');
-var chainEvents = false;
+let chainEvents = false;
@@ -38,11 +39,13 @@ var chainEvents = false;
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainInfo = function(req, res, next) {
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
- console.log(wallet_path);
+exports.getChainInfo = function(req, res, next)
+{
+ let method='getChainInfo';
+ let HOST_NAME = req.headers.host;
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
Promise.resolve().then(() => {
//
// As of 9/28/2017 there is a known and unresolved bug in HyperLedger Fabric
@@ -61,29 +64,43 @@ exports.getChainInfo = function(req, res, next) {
// change PeerAdmin in following line to adminID
return client.getUserContext(config.composer.PeerAdmin, true);})
.then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ { console.error('User not defined, or not enrolled - error');}
+ if (HOST_NAME.slice(0,9) === 'localhost')
+ {
+ console.log(method+" running locally");
channel = client.newChannel(config.fabric.channelName);
channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- })
+ }else
+ {
+ console.log(method+" running remotely, not supported in Chapter 12");
+ }
+ })
.then(() => {
return channel.queryInfo()
.then((blockchainInfo) => {
if (blockchainInfo) {
- res.send({"result": "success", "currentHash": blockchainInfo.currentBlockHash.toString("hex"), blockchain: blockchainInfo});
+ res.send({'result': 'success', 'currentHash': blockchainInfo.currentBlockHash.toString('hex'), blockchain: blockchainInfo});
} else {
console.log('response_payload is null');
- res.send({"result": "uncertain", "message": 'response_payload is null'});
+ res.send({'result': 'uncertain', 'message': 'response_payload is null'});
}
})
.catch((_err) => {
- console.log("queryInfo failed with _err = ", _err);
- res.send({"result": "failed", "message": _err.message});
- });
+ console.log('queryInfo failed with _err = ', _err);
+ res.send({'result': 'failed', 'message': _err.message});
+ });
});
- });
-}
+ });
+};
/**
* get chain events
@@ -92,37 +109,55 @@ exports.getChainInfo = function(req, res, next) {
* @param {express.next} next - an express service to enable post processing prior to responding to the client
* @function
*/
-exports.getChainEvents = function(req, res, next) {
+exports.getChainEvents = function(req, res, next)
+{
+ let method = 'getChainEvents';
+ let HOST_NAME = req.headers.host;
if (chainEvents) {res.send({'port': svc.cs_socketAddr});}
else
{
- var channel = {};
- var client = null;
- var wallet_path = path.join(__dirname, 'creds');
+ let channel = {};
+ let client = null;
+ let wallet_path = path.join(__dirname, 'creds');
+
Promise.resolve().then(() => {
client = new hfc();
return hfc.newDefaultKeyValueStore({ path: wallet_path })
.then((wallet) => {
client.setStateStore(wallet);
// change PeerAdmin in following line to adminID
- return client.getUserContext(config.composer.PeerAdmin, true);})
- .then((user) => {
- if (user === null || user === undefined || user.isEnrolled() === false)
- {console.error("User not defined, or not enrolled - error");}
- channel = client.newChannel(config.fabric.channelName);
- channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
- channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
- // change Admin in following line to admin
- var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
- var adminPEM = fs.readFileSync(pemPath).toString();
- var bcEvents = new hfcEH(client);
- bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ return client.getUserContext(config.composer.PeerAdmin, true);
+ })
+ .then((user) => {
+ if (user === null || user === undefined || user.isEnrolled() === false)
+ {console.error(method+': User not defined, or not enrolled - error');}
+ // This routine as written will only work with the kubernetes deployed blockchain. It will not work with the docker image.
+ // adding a check for localhost only tells you that the nodejs portion is running on your local system. This does not
+ // also tell you that the blockchain is/is not running in your local docker environment.
+ // To support switching between local docker and remote kubernetes on cluster, an extra config element would be required.
+ // The logical place for this is in the env.json file. If you do that, then the code in this routine will need to be updated so that each
+ // place where the remote addresseses are loaded (from hlf1_profile) will need to be replaced with the local profiles.
+ // You will find the local profile definitions in the env.json file and use of these definitions can be found in Chapter 12 of this tutorial
+ // get the channel name
+ channel = client.newChannel(config.fabric.channelName);
+ //get the request URL for the Peer0 container
+ channel.addPeer(client.newPeer(config.fabric.peerRequestURL));
+ // get the orderer URL
+ channel.addOrderer(client.newOrderer(config.fabric.ordererURL));
+ // change Admin in following line to admin
+ var pemPath = path.join(__dirname,'creds','admin@org.hyperledger.composer.system-cert.pem');
+ var adminPEM = fs.readFileSync(pemPath).toString();
+ var bcEvents = new hfcEH(client);
+ bcEvents.setPeerAddr(config.fabric.peerEventURL, {pem: adminPEM});
+ bcEvents.registerBlockEvent(
+ function(event){svc.send(req.app.locals, 'BlockChain', event);},
+ function(error){console.log(method+': registerBlockEvent error: ', error);}
+ );
bcEvents.connect();
- svc.createChainSocket();
- bcEvents.registerBlockEvent(function(event) {svc.cs_connection.sendUTF(JSON.stringify(event));});
- chainEvents = true;
- res.send({'port': svc.cs_socketAddr});
- })
- });
- }
-}
+ chainEvents = true;
+ res.send({'port': svc.cs_socketAddr});
+ })
+ .catch((err) => { console.log(method+': getUserContext failed: ',err);});
+ });
+ }
+};
diff --git a/Chapter11/controller/restapi/router.js b/Chapter11/controller/restapi/router.js
index 6fa15bd..14883e7 100644
--- a/Chapter11/controller/restapi/router.js
+++ b/Chapter11/controller/restapi/router.js
@@ -25,13 +25,15 @@ let hlcAdmin = require('./features/composer/hlcAdmin');
let hlcClient = require('./features/composer/hlcClient');
let setup = require('./features/composer/autoLoad');
let hlcFabric = require('./features/composer/queryBlockChain');
-router.post('/setup/autoLoad*', setup.autoLoad);
-router.get('/setup/getPort*', setup.getPort);
+
router.get('/fabric/getChainInfo', hlcFabric.getChainInfo);
router.get('/fabric/getChainEvents', hlcFabric.getChainEvents);
router.get('/fabric/getHistory', hlcAdmin.getHistory);
+router.post('/setup/autoLoad*', setup.autoLoad);
+router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents);
+
module.exports = router;
let count = 0;
/**
@@ -71,6 +73,7 @@ router.get('/composer/admin/getAllProfiles*', hlcAdmin.getAllProfiles);
router.get('/composer/admin/listAsAdmin*', hlcAdmin.listAsAdmin);
router.get('/composer/admin/getRegistries*', hlcAdmin.getRegistries);
+
router.post('/composer/admin/createProfile*', hlcAdmin.createProfile);
router.post('/composer/admin/deleteProfile*', hlcAdmin.deleteProfile);
router.post('/composer/admin/deploy*', hlcAdmin.deploy);
@@ -90,6 +93,7 @@ router.post('/composer/admin/checkCard*', hlcAdmin.checkCard);
router.post('/composer/admin/createCard*', hlcAdmin.createCard);
router.post('/composer/admin/issueIdentity*', hlcAdmin.issueIdentity);
+
// router requests specific to the Buyer
router.get('/composer/client/getItemTable*', hlcClient.getItemTable);
router.post('/composer/client/getMyOrders*', hlcClient.getMyOrders);
diff --git a/Chapter11/favicon.ico b/Chapter11/favicon.ico
deleted file mode 100755
index fc9e25d..0000000
Binary files a/Chapter11/favicon.ico and /dev/null differ
diff --git a/Chapter11/index.js b/Chapter11/index.js
index 70319d6..cf79cb7 100644
--- a/Chapter11/index.js
+++ b/Chapter11/index.js
@@ -14,25 +14,28 @@
/*
* Zero to Blockchain */
-var express = require('express');
-var http = require('http');
-var https = require('https');
-var path = require('path');
-var fs = require('fs');
-var mime = require('mime');
-var bodyParser = require('body-parser');
-var cfenv = require('cfenv');
-var cookieParser = require('cookie-parser');
-var session = require('express-session');
+'use strict';
+const express = require('express');
+const http = require('http');
+const ws = require('websocket').server;
+// const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const mime = require('mime');
+const bodyParser = require('body-parser');
+const cfenv = require('cfenv');
-var vcapServices = require('vcap_services');
-var uuid = require('uuid');
-var env = require('./controller/envV2.json');
-var sessionSecret = env.sessionSecret;
-var appEnv = cfenv.getAppEnv();
-var app = express();
-var busboy = require('connect-busboy');
+const cookieParser = require('cookie-parser');
+// const session = require('express-session');
+
+// const vcapServices = require('vcap_services');
+// const uuid = require('uuid');
+const env = require('./controller/envV2.json');
+const sessionSecret = env.sessionSecret;
+const appEnv = cfenv.getAppEnv();
+const app = express();
+const busboy = require('connect-busboy');
app.use(busboy());
// the session secret is a text string of arbitrary length which is
@@ -49,7 +52,8 @@ app.use(cookieParser(sessionSecret));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
-app.set('appName', 'z2b-chapter07');
+app.set('appName', 'z2b-chapter12');
+process.title = 'Z2B-C12';
app.set('port', appEnv.port);
app.set('views', path.join(__dirname + '/HTML'));
@@ -59,14 +63,58 @@ app.use(express.static(__dirname + '/HTML'));
app.use(bodyParser.json());
// Define your own router file in controller folder, export the router, add it into the index.js.
-// app.use('/', require("./controller/yourOwnRouter"));
-app.use('/', require("./controller/restapi/router"));
+app.use('/', require('./controller/restapi/router'));
+
+let server = http.createServer();
+let clients = [];
+app.locals.index=-1;
+/**
+ * WebSocket server
+ */
+app.locals.wsServer = new ws({httpServer: server});
+app.locals.wsServer.on('request', function(request)
+{
+ // create a connection back to the requestor
+ app.locals.connection = request.accept(null, request.origin);
+ // we need to know client index to remove them on 'close' event
+ app.locals.index = clients.push(app.locals.connection) - 1;
+ // save the newly created connection. This is so that we can support many connections to many browsers simultaneously
+ console.log((new Date()) + ' Connection accepted.');
+ app.locals.connection.on('message', function(message)
+ { let obj ={ime: (new Date()).getTime(),text: message.utf8Data};
+ // broadcast message to all connected clients
+ let json = JSON.stringify({ type:'Message', data: obj });
+ app.locals.processMessages(json);
+ });
+
+ // user disconnected
+ app.locals.connection.on('close', function(_conn) {
+ console.log((new Date()) + ' Peer '+ app.locals.connection.socket._peername.address+':'+app.locals.connection.socket._peername.port+' disconnected with reason code: "'+_conn+'".');
+ // remove user from the list of connected clients
+ // each browser connection has a unique address and socket combination
+ // When a browser session is disconnected, remove it from the array so we don't waste processing time sending messages to empty queues.
+ for (let each in clients)
+ {(function(_idx, _arr)
+ {if ((_arr[_idx].socket._peername.address === app.locals.connection.socket._peername.address) && (_arr[_idx].socket._peername.port === app.locals.connection.socket._peername.port))
+ {clients.splice(_idx, 1);}
+ })(each, clients);}
+ });
+});
-if (cfenv.getAppEnv().isLocal == true)
- { var server = app.listen(app.get('port'), function() {console.log('Listening locally on port %d', server.address().port);}); }
- else
- { var server = app.listen(app.get('port'), function() {console.log('Listening remotely on port %d', server.address().port);}); }
+/**
+ * callable function to send messages over web socket
+ * @param {JSON} _jsonMsg - json formatted content to be sent as message data
+ */
+function processMessages (_jsonMsg)
+{
+ for (let i=0; i < clients.length; i++) {clients[i].send(JSON.stringify(_jsonMsg));}
+}
+// make the processMessages function available to all modules in this app.
+app.locals.processMessages = processMessages;
+// now set up the http server
+server.on( 'request', app );
+server.listen(appEnv.port, function() {console.log('Listening locally on port %d', server.address().port);});
/**
* load any file requested on the server
* @param {express.req} req - the inbound request object from the client
@@ -74,16 +122,16 @@ if (cfenv.getAppEnv().isLocal == true)
* @function
*/
function loadSelectedFile(req, res) {
- var uri = req.originalUrl;
- var filename = __dirname + "/HTML" + uri;
+ let uri = req.originalUrl;
+ let filename = __dirname + '/HTML' + uri;
fs.readFile(filename,
function(err, data) {
if (err) {
console.log('Error loading ' + filename + ' error: ' + err);
return res.status(500).send('Error loading ' + filename);
}
- var type = mime.lookup(filename);
- res.setHeader('content-type', type);
+ let type = mime.lookup(filename);
+ res.setHeader('content-type', type);
res.writeHead(200);
res.end(data);
});
diff --git a/Chapter11/network/lib/sample.js b/Chapter11/network/lib/sample.js
index ddb951a..0d948b6 100644
--- a/Chapter11/network/lib/sample.js
+++ b/Chapter11/network/lib/sample.js
@@ -104,7 +104,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
@@ -159,11 +159,11 @@ function RequestPayment(purchase) {
{purchase.order.status = JSON.stringify(orderStatus.PayRequest);
purchase.order.financeCo = purchase.financeCo;
purchase.order.paymentRequested = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -174,11 +174,11 @@ function AuthorizePayment(purchase) {
if ((JSON.parse(purchase.order.status).text == orderStatus.PayRequest.text ) || (JSON.parse(purchase.order.status).text == orderStatus.Resolve.text ))
{purchase.order.status = JSON.stringify(orderStatus.Authorize);
purchase.order.approved = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a payment to the seller
@@ -189,11 +189,11 @@ function Pay(purchase) {
if (JSON.parse(purchase.order.status).text == orderStatus.Authorize.text )
{purchase.order.status = JSON.stringify(orderStatus.Paid);
purchase.order.paid = new Date().toISOString();
- }
- return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
- .then(function (assetRegistry) {
- return assetRegistry.update(purchase.order);
- });
+ return getAssetRegistry('org.acme.Z2BTestNetwork.Order')
+ .then(function (assetRegistry) {
+ return assetRegistry.update(purchase.order);
+ });
+ }
}
/**
* Record a dispute by the buyer
diff --git a/Chapter12/Documentation/answers/composer/hlcClient_complete.js b/Chapter12/Documentation/answers/composer/hlcClient_complete.js
index b1c4e1d..1bc701e 100644
--- a/Chapter12/Documentation/answers/composer/hlcClient_complete.js
+++ b/Chapter12/Documentation/answers/composer/hlcClient_complete.js
@@ -245,7 +245,7 @@ exports.orderAction = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(' retrying assetRegistry.update for: '+req.body.orderNo);
- svc.loadTransaction(svc.m_connection, updateOrder, req.body.orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, updateOrder, req.body.orderNo, businessNetworkConnection);
}
else
{console.log(req.body.orderNo+' submitTransaction to update status to '+req.body.action+' failed with text: ',error.message);}
@@ -330,7 +330,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{console.log(orderNo+' submitTransaction failed with text: ',error.message);}
@@ -339,7 +339,7 @@ exports.addOrder = function (req, res, next) {
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
- svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
+ svc.loadTransaction(req.app.locals, createNew, orderNo, businessNetworkConnection);
}
else
{
@@ -354,46 +354,11 @@ exports.addOrder = function (req, res, next) {
});
})
.catch((error) => {
- console.log(orderNo+' business network connection failed: text',error.message);
+ console.log(method + ' : '+orderNo+' business network connection failed: text',error.message);
res.send({'result': 'failed', 'error':' order '+orderNo+' add failed on on business network connection '+error.message});
});
};
-/**
- * Register for all of the available Z2BEvents
- * @param {express.req} req - the inbound request object from the client
- * @param {express.res} res - the outbound response object for communicating back to client
- * @param {express.next} next - an express service to enable post processing prior to responding to the client
- * @returns {Object} - returns are via res.send
-*/
-exports.init_z2bEvents = function (req, res, next)
-{
- let method = 'init_z2bEvents';
- if (bRegistered) {res.send('Already Registered');}
- else{
- bRegistered = true;
- let _conn = svc.createAlertSocket();
- let businessNetworkConnection;
- businessNetworkConnection = new BusinessNetworkConnection();
- businessNetworkConnection.setMaxListeners(50);
- //
- // v0.14
- // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
- //
- // v0.15
- return businessNetworkConnection.connect(config.composer.adminCard)
- .then(() => {
- // using the businessNetworkConnection, start monitoring for events.
- // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
- businessNetworkConnection.on('event', (event) => {_monitor(svc.al_connection, svc.f_connection, event); });
- res.send('event registration complete');
- }).catch((error) => {
- // if an error is encountered, log the error and send it back to the requestor
- console.log(method+' business network connection failed'+error.message);
- res.send(method+' business network connection failed'+error.message);
- });
- }
-};
/**
* _monitor
* @param {WebSocket} _conn - web socket to use for member event posting
@@ -401,7 +366,7 @@ exports.init_z2bEvents = function (req, res, next)
* @param {Event} _event - the event just emitted
*
*/
-function _monitor(_conn, _f_conn, _event)
+function _monitor(locals, _event)
{
let method = '_monitor';
console.log(method+ ' _event received: '+_event.$type+' for Order: '+_event.orderID);
@@ -411,7 +376,7 @@ function _monitor(_conn, _f_conn, _event)
event.type = _event.$type;
event.orderID = _event.orderID;
event.ID = _event.buyerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
// using switch/case logic, send events back to each participant who should be notified.
// for example, when a seller requests payment, they should be notified when the transaction has completed
@@ -426,49 +391,85 @@ function _monitor(_conn, _f_conn, _event)
case 'Bought':
case 'PaymentRequested':
event.ID = _event.sellerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.financeCoID;
- _f_conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
break;
case 'Ordered':
case 'Cancelled':
case 'Backordered':
event.ID = _event.sellerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.providerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
break;
case 'ShipRequest':
case 'DeliveryStarted':
case 'DeliveryCompleted':
event.ID = _event.sellerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.providerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.shipperID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
break;
case 'DisputeOpened':
case 'Resolved':
case 'Refunded':
case 'Paid':
event.ID = _event.sellerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.providerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.shipperID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.financeCoID;
- _f_conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
break;
case 'PaymentAuthorized':
event.ID = _event.sellerID;
- _conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
event.ID = _event.financeCoID;
- _f_conn.sendUTF(JSON.stringify(event));
+ svc.send(locals, 'Alert',JSON.stringify(event));
break;
default:
break;
}
-}
\ No newline at end of file
+}
+
+/**
+ * Register for all of the available Z2BEvents
+ * @param {express.req} req - the inbound request object from the client
+ * @param {express.res} res - the outbound response object for communicating back to client
+ * @param {express.next} next - an express service to enable post processing prior to responding to the client
+ * @returns {Object} - returns are via res.send
+*/
+exports.init_z2bEvents = function (req, res, next)
+{
+ let method = 'init_z2bEvents';
+ if (bRegistered) {res.send('Already Registered');}
+ else{
+ bRegistered = true;
+// svc.createAlertSocket();
+ let businessNetworkConnection;
+ businessNetworkConnection = new BusinessNetworkConnection();
+ businessNetworkConnection.setMaxListeners(50);
+ //
+ // v0.14
+ // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW)
+ //
+ // v0.15
+ return businessNetworkConnection.connect(config.composer.adminCard)
+ .then(() => {
+ // using the businessNetworkConnection, start monitoring for events.
+ // when an event is provided, call the _monitor function, passing in the al_connection, f_connection and event information
+ businessNetworkConnection.on('event', (event) => {_monitor(req.app.locals, event); });
+ res.send('event registration complete');
+ }).catch((error) => {
+ // if an error is encountered, log the error and send it back to the requestor
+ console.log(method+' business network connection failed'+error.message);
+ res.send(method+' business network connection failed'+error.message);
+ });
+ }
+};
diff --git a/Chapter12/Documentation/answers/js/z2b-buyer_complete.js b/Chapter12/Documentation/answers/js/z2b-buyer_complete.js
index 49d7795..0beb896 100644
--- a/Chapter12/Documentation/answers/js/z2b-buyer_complete.js
+++ b/Chapter12/Documentation/answers/js/z2b-buyer_complete.js
@@ -32,22 +32,20 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
}
}
-function setupBuyer(page, port)
+function setupBuyer(page)
{
// empty the hetml element that will hold this page
$('#buyerbody').empty();
@@ -60,9 +58,6 @@ function setupBuyer(page, port)
else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
// update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -120,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
diff --git a/Chapter12/Documentation/answers/js/z2b-events_complete.js b/Chapter12/Documentation/answers/js/z2b-events_complete.js
index 44c5ef5..282f3ea 100644
--- a/Chapter12/Documentation/answers/js/z2b-events_complete.js
+++ b/Chapter12/Documentation/answers/js/z2b-events_complete.js
@@ -16,8 +16,7 @@
'use strict';
-let alertPort = null;
-let financeAlertPort = null;
+let wsSocket;
/**
* load the four initial user roles into a single page.
@@ -26,8 +25,8 @@ function singleUX ()
{
let toLoad = 'singleUX.html';
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (_page, _port, _res)
- { msgPort = _port.port;
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (_page, _res)
+ {
$('#body').empty();
$('#body').append(_page);
loadBuyerUX();
@@ -85,8 +84,8 @@ function memberLoad ()
*/
function dropDummy(_in)
{
- let _a = new Array()
- for (let each in _in){(function(_idx, _arr){console.log('_arr['+_idx+'].id is: '+_arr[_idx].id); if (_arr[_idx].id !== 'noop@dummy')_a.push(_arr[_idx]);})(each, _in);}
+ let _a = new Array();
+ for (let each in _in){(function(_idx, _arr){if (_arr[_idx].id.slice(0,10) !== 'noop@dummy'){_a.push(_arr[_idx]);}})(each, _in);}
return _a;
}
/**
@@ -136,61 +135,17 @@ function _getMembers(_members)
*/
function goEventInitialize()
{
- $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log(_res);})
-}
-
-/**
- * get the alert web socket port
- */
-function getAlertPort ()
-{
- if (alertPort === null)
- {
- $.when($.get('/setup/getAlertPort')).done(function (port)
- {
- console.log('alert port is: '+port.port); alertPort = port.port;
- let wsSocket = new WebSocket('ws://localhost:'+alertPort);
- wsSocket.onopen = function () {wsSocket.send('connected to alerts');};
- wsSocket.onmessage = function (message) {
- console.log(message.data);
- let event = JSON.parse(message.data);
- addNotification(event.type, event.ID, event.orderID);
- };
- wsSocket.onerror = function (error) {console.log('Alert Socket error on wsSocket: ' + error);};
- });
- }
-}
-/**
- * get the finance alert web socket port
- */
-function getFinanceAlertPort ()
-{
- if (financeAlertPort === null)
- {
- $.when($.get('/setup/getFinanceAlertPort')).done(function (port)
- {
- console.log('finance alert port is: '+port.port); financeAlertPort = port.port;
- let wsSocket = new WebSocket('ws://localhost:'+financeAlertPort);
- wsSocket.onopen = function () {wsSocket.send('connected to finance alerts');};
- wsSocket.onmessage = function (message) {
- console.log(message.data);
- let event = JSON.parse(message.data);
- addNotification(event.type, event.ID, event.orderID);
- };
- wsSocket.onerror = function (error) {console.log('Finance Alert Socket error on wsSocket: ' + error);};
- });
- }
+ $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log('getChainEvents results: ', _res);});
}
/**
- *
* @param {Event} _event - inbound Event
* @param {String} _id - subscriber target
* @param {String} _orderID - inbound order id
*/
function addNotification(_event, _id, _orderID)
{
- let method = 'showNotification';
+ let method = 'addNotification';
console.log(method+' _event'+_event+' id: '+_id+' orderID: '+_orderID);
let type = getSubscriber(_id);
if (type === 'none') {return;}
@@ -277,4 +232,52 @@ function notifyMe (_alerts, _id)
let b_h = false;
for (let each in _alerts) {(function(_idx, _arr){if (_id === _arr[_idx].order){b_h = true;}})(each, _alerts);}
return b_h;
+}
+/**
+ * connect to web socket
+ */
+function wsConnect()
+{
+ let method = 'wsConnect';
+ if (!window.WebSocket) {console.log('this browser does not support web sockets');}
+ let content = $('#body');
+ let blockchain = $('#blockchain');
+ // updated from ws: to wss: to support access over https
+ if (host_address.slice(0,9) === 'localhost')
+ {
+ wsSocket = new WebSocket('ws://'+host_address);
+ }else
+ {
+ wsSocket = new WebSocket('wss://'+host_address);
+ }
+ wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ', error);};
+ wsSocket.onopen = function ()
+ {console.log ('connect.onOpen initiated to: '+host_address); wsSocket.send('connected to client');};
+ wsSocket.onmessage = function (message)
+ {
+ let incoming
+ incoming = message.data;
+ // console.log(method+ ' incoming is: '+incoming);
+ while (incoming instanceof Object === false){incoming = JSON.parse(incoming);}
+ switch (incoming.type)
+ {
+ case 'Message':
+ content.append(formatMessage(incoming.data));
+ break;
+ case 'Alert':
+ let event = JSON.parse(incoming.data);
+ addNotification(event.type, event.ID, event.orderID);
+ break;
+ case 'BlockChain':
+ _blctr ++;
+ if (incoming.data !== 'connected')
+ {
+ $(blockchain).append('block '+incoming.data.header.number+' Hash: '+incoming.data.header.data_hash+'');
+ if (_blctr > 4) {let leftPos = $(blockchain).scrollLeft(); $(blockchain).animate({scrollLeft: leftPos + 300}, 250);}
+ }
+ break;
+ default:
+ console.log('Can Not Process message type: ',incoming.type);
+ }
+ };
}
\ No newline at end of file
diff --git a/Chapter12/Documentation/answers/js/z2b-financeCo_complete.js b/Chapter12/Documentation/answers/js/z2b-financeCo_complete.js
index e0c3685..40ff08e 100644
--- a/Chapter12/Documentation/answers/js/z2b-financeCo_complete.js
+++ b/Chapter12/Documentation/answers/js/z2b-financeCo_complete.js
@@ -31,22 +31,19 @@ let f_alerts;
function loadFinanceCoUX ()
{
let toLoad = 'financeCo.html';
- // get the FinanceAlert Port
- getFinanceAlertPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupFinanceCo(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupFinanceCo(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupFinanceCo(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupFinanceCo(page);});
}
}
/**
* @param {String} page HTML page to load
- * @param {Integer} port Websocket port to use
*/
-function setupFinanceCo(page, port)
+function setupFinanceCo(page)
{
$('#body').empty();
$('#body').append(page);
@@ -56,9 +53,6 @@ function setupFinanceCo(page, port)
else
{$(f_notify).removeClass('off'); $(f_notify).addClass('on'); }
updatePage( 'financeCo');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('finance_messages', msgPort);
let _clear = $('#financeCOclear');
let _list = $('#financeCOorderStatus');
let _orderDiv = $('#'+financeCOorderDiv);
diff --git a/Chapter12/Documentation/answers/network/lib/sample_complete.js b/Chapter12/Documentation/answers/network/lib/sample_complete.js
index 8a4ef40..7a0c946 100644
--- a/Chapter12/Documentation/answers/network/lib/sample_complete.js
+++ b/Chapter12/Documentation/answers/network/lib/sample_complete.js
@@ -81,7 +81,7 @@ function Buy(purchase) {
* @transaction
*/
function OrderCancel(purchase) {
- if ((purchase.order.status == JSON.stringify(orderStatus.Created)) || (purchase.order.status == JSON.stringify(orderStatus.Bought)))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Created)) || (purchase.order.status == JSON.stringify(orderStatus.Bought)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.buyer = purchase.buyer;
purchase.order.seller = purchase.seller;
@@ -126,7 +126,7 @@ function OrderFromSupplier(purchase) {
* @transaction
*/
function RequestShipping(purchase) {
- if (purchase.order.status == JSON.stringify(orderStatus.Ordered))
+ if ((purchase.order.status == JSON.stringify(orderStatus.Ordered)) || (purchase.order.status == JSON.stringify(orderStatus.Backordered)))
{
purchase.order.shipper = purchase.shipper;
purchase.order.requestShipment = new Date().toISOString();
diff --git a/Chapter12/HTML/favicon.ico b/Chapter12/HTML/favicon.ico
old mode 100755
new mode 100644
index fc9e25d..3dbe425
Binary files a/Chapter12/HTML/favicon.ico and b/Chapter12/HTML/favicon.ico differ
diff --git a/Chapter12/HTML/js/z2b-admin.js b/Chapter12/HTML/js/z2b-admin.js
index 67f421a..f3c3ffc 100644
--- a/Chapter12/HTML/js/z2b-admin.js
+++ b/Chapter12/HTML/js/z2b-admin.js
@@ -18,7 +18,6 @@
let creds;
let connection;
-let msgPort = null;
let _blctr = 0;
/**
@@ -34,19 +33,6 @@ function loadAdminUX ()
listMemRegistries();
});
}
-/**
- * connect to the provided web socket
- * @param {String} _target - location to post messages
- * @param {Integer} _port - web socket port #
- */
-function wsDisplay(_target, _port)
-{
- let content = $('#'+_target);
- let wsSocket = new WebSocket('ws://localhost:'+_port);
- wsSocket.onopen = function () {wsSocket.send('connected to client');};
- wsSocket.onmessage = function (message) {content.append(formatMessage(message.data));};
- wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ' + error);};
-}
/**
* list the available business networks
*/
@@ -361,7 +347,7 @@ function preLoad()
$('#body').empty();
let options = {};
$.when($.post('/setup/autoLoad', options)).done(function (_results)
- { msgPort = _results.port; wsDisplay('body', msgPort); });
+ { console.log('Autoload Initiated'); $('#body').append('
Autoload Initiated
'); });
}
/**
@@ -754,28 +740,9 @@ function getHistorian()
*/
function getChainEvents()
{
- $.when($.get('fabric/getChainEvents')).done(function(_res)
- { let _str = '
Get Chain events requested. Sending to port: '+_res.port+'
';
- let content = $('#blockchain');
- let csSocket = new WebSocket('ws://localhost:'+_res.port);
- csSocket.onopen = function () {csSocket.send('connected to client');};
- csSocket.onmessage = function (message) {
- _blctr ++;
- if (message.data !== 'connected')
- {$(content).append('block '+JSON.parse(message.data).header.number+' Hash: '+JSON.parse(message.data).header.data_hash+'');
- if (_blctr > 4) {let leftPos = $(content).scrollLeft(); $(content).animate({scrollLeft: leftPos + 300}, 250);}
- }
- };
- csSocket.onerror = function (error) {console.log('WebSocket error: ' + error);};
- $('#admin-forms').empty();
- $('#admin-forms').append(_str);
+ $.when($.get('/fabric/getChainEvents')).done(function(_res)
+ { $('#body').append('
Get Chain events requested.
');
+ let _host = (host_address.slice(0,9) === 'localhost') ? 'localhost' : host_address;
+ console.log('getChainEvents host_address: '+_host);
});
-}
-/**
- * display blockchain updates
- */
-function displayAdminUpdate()
-{
- let toLoad = 'adminHelp.html';
- $.when($.get(toLoad)).done(function(_page){$('#admin-forms').empty(); $('#admin-forms').append(_page);});
}
\ No newline at end of file
diff --git a/Chapter12/HTML/js/z2b-buyer.js b/Chapter12/HTML/js/z2b-buyer.js
index 38cb645..793ff73 100644
--- a/Chapter12/HTML/js/z2b-buyer.js
+++ b/Chapter12/HTML/js/z2b-buyer.js
@@ -32,39 +32,32 @@ function loadBuyerUX ()
{
// get the html page to load
let toLoad = 'buyer.html';
- // get the port to use for web socket communications with the server
- getPort();
// if (buyers.length === 0) then autoLoad() was not successfully run before this web app starts, so the sie of the buyer list is zero
// assume user has run autoLoad and rebuild member list
// if autoLoad not yet run, then member list length will still be zero
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupBuyer(page[0], port[0]);});
- }
- else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupBuyer(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupBuyer(page);});
+ }
+ else{
+ $.when($.get(toLoad)).done(function (page)
+ {setupBuyer(page);});
+ }
}
-}
-
-function setupBuyer(page, port)
-{
+
+ function setupBuyer(page)
+ {
// empty the hetml element that will hold this page
$('#buyerbody').empty();
$('#buyerbody').append(page);
// empty the buyer alerts array
- // =====> Your Code Goes Here <=========
+ b_alerts = [];
// if there are no alerts, then remove the 'on' class and add the 'off' class
if (b_alerts.length === 0)
- {
- // =====> Your Code Goes Here <=========
- }
+ {$(b_notify).removeClass('on'); $(b_notify).addClass('off'); }
else {$(b_notify).removeClass('off'); $(b_notify).addClass('on'); }
// update the text on the page using the prompt data for the selected language
updatePage('buyer');
- msgPort = port.port;
- // connect to the web socket and tell the web socket where to display messages
- wsDisplay('buyer_messages', msgPort);
// enable the buttons to process an onClick event
let _create = $('#newOrder');
let _list = $('#orderStatus');
@@ -80,20 +73,20 @@ function setupBuyer(page, port)
// display the name of the current buyer
$('#company')[0].innerText = buyers[0].companyName;
// save the current buyer id as b_id
- // =====> Your Code Goes Here <=========
+ b_id = buyers[0].id;
// subscribe to events
- // =====> Your Code Goes Here <=========
+ z2bSubscribe('Buyer', b_id);
// create a function to execute when the user selects a different buyer
$('#buyer').on('change', function()
{ _orderDiv.empty(); $('#buyer_messages').empty();
$('#company')[0].innerText = findMember($('#buyer').find(':selected').text(),buyers).companyName;
// unsubscribe the current buyer
- // =====> Your Code Goes Here <=========
+ z2bUnSubscribe(b_id);
// get the new buyer id
- // =====> Your Code Goes Here <=========
+ b_id = findMember($('#buyer').find(':selected').text(),buyers).id;
// subscribe the new buyer
- // =====> Your Code Goes Here <=========
-});
+ z2bSubscribe('Buyer', b_id);
+ });
}
/**
@@ -122,7 +115,7 @@ function displayOrderForm()
$('#amount').append('$'+totalAmount+'.00');
// build a select list for the items
let _str = '';
- for (let each in itemTable){(function(_idx, _arr){_str+=''})(each, itemTable)}
+ for (let each in itemTable){(function(_idx, _arr){_str+='';})(each, itemTable);}
$('#items').empty();
$('#items').append(_str);
$('#cancelNewOrder').on('click', function (){_orderDiv.empty();});
@@ -326,11 +319,11 @@ function formatOrders(_target, _orders)
});
// use the notifyMe function to determine if this order is in the alert array.
// if it is, the highlight the $('#b_status'+_idx) html element by adding the 'highlight' class
- // =====> Your Code Goes Here <=========
-})(each, _orders);
+ if (notifyMe(b_alerts, _arr[_idx].id)) {$('#b_status'+_idx).addClass('highlight'); }
+ })(each, _orders);
}
// reset the b_alerts array to a new array
b_alerts = new Array();
// call the toggleAlerts function to reset the alert icon
- // =====> Your Code Goes Here <=========
+ toggleAlert($('#buyer_notify'), b_alerts, b_alerts.length);
}
\ No newline at end of file
diff --git a/Chapter12/HTML/js/z2b-events.js b/Chapter12/HTML/js/z2b-events.js
index fad87fa..282f3ea 100644
--- a/Chapter12/HTML/js/z2b-events.js
+++ b/Chapter12/HTML/js/z2b-events.js
@@ -16,8 +16,7 @@
'use strict';
-let alertPort = null;
-let financeAlertPort = null;
+let wsSocket;
/**
* load the four initial user roles into a single page.
@@ -26,8 +25,8 @@ function singleUX ()
{
let toLoad = 'singleUX.html';
if ((typeof(buyers) === 'undefined') || (buyers === null) || (buyers.length === 0))
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (_page, _port, _res)
- { msgPort = _port.port;
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (_page, _res)
+ {
$('#body').empty();
$('#body').append(_page);
loadBuyerUX();
@@ -35,8 +34,8 @@ function singleUX ()
loadProviderUX();
loadShipperUX();
// Initialize Registration for all Z2B Business Events
- // =====> Your Code Goes Here <=========
-});
+ goEventInitialize();
+ });
}
else{
$.when($.get(toLoad)).done(function(_page)
@@ -48,8 +47,8 @@ function singleUX ()
loadProviderUX();
loadShipperUX();
// Initialize Registration for all Z2B Business Events
- // =====> Your Code Goes Here <=========
-});
+ goEventInitialize();
+ });
}
}
/**
@@ -85,8 +84,8 @@ function memberLoad ()
*/
function dropDummy(_in)
{
- let _a = new Array()
- for (let each in _in){(function(_idx, _arr){console.log('_arr['+_idx+'].id is: '+_arr[_idx].id); if (_arr[_idx].id !== 'noop@dummy')_a.push(_arr[_idx]);})(each, _in);}
+ let _a = new Array();
+ for (let each in _in){(function(_idx, _arr){if (_arr[_idx].id.slice(0,10) !== 'noop@dummy'){_a.push(_arr[_idx]);}})(each, _in);}
return _a;
}
/**
@@ -136,61 +135,17 @@ function _getMembers(_members)
*/
function goEventInitialize()
{
- $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log(_res);})
+ $.when($.get('/composer/client/initEventRegistry')).done(function(_res){console.log('getChainEvents results: ', _res);});
}
/**
- * get the alert web socket port
- */
-function getAlertPort ()
-{
- if (alertPort === null)
- {
- $.when($.get('/setup/getAlertPort')).done(function (port)
- {
- console.log('alert port is: '+port.port); alertPort = port.port;
- let wsSocket = new WebSocket('ws://localhost:'+alertPort);
- wsSocket.onopen = function () {wsSocket.send('connected to alerts');};
- wsSocket.onmessage = function (message) {
- console.log(message.data);
- let event = JSON.parse(message.data);
- addNotification(event.type, event.ID, event.orderID);
- };
- wsSocket.onerror = function (error) {console.log('Alert Socket error on wsSocket: ' + error);};
- });
- }
-}
-/**
- * get the finance alert web socket port
- */
-function getFinanceAlertPort ()
-{
- if (financeAlertPort === null)
- {
- $.when($.get('/setup/getFinanceAlertPort')).done(function (port)
- {
- console.log('finance alert port is: '+port.port); financeAlertPort = port.port;
- let wsSocket = new WebSocket('ws://localhost:'+financeAlertPort);
- wsSocket.onopen = function () {wsSocket.send('connected to finance alerts');};
- wsSocket.onmessage = function (message) {
- console.log(message.data);
- let event = JSON.parse(message.data);
- addNotification(event.type, event.ID, event.orderID);
- };
- wsSocket.onerror = function (error) {console.log('Finance Alert Socket error on wsSocket: ' + error);};
- });
- }
-}
-
-/**
- *
* @param {Event} _event - inbound Event
* @param {String} _id - subscriber target
* @param {String} _orderID - inbound order id
*/
function addNotification(_event, _id, _orderID)
{
- let method = 'showNotification';
+ let method = 'addNotification';
console.log(method+' _event'+_event+' id: '+_id+' orderID: '+_orderID);
let type = getSubscriber(_id);
if (type === 'none') {return;}
@@ -277,4 +232,52 @@ function notifyMe (_alerts, _id)
let b_h = false;
for (let each in _alerts) {(function(_idx, _arr){if (_id === _arr[_idx].order){b_h = true;}})(each, _alerts);}
return b_h;
+}
+/**
+ * connect to web socket
+ */
+function wsConnect()
+{
+ let method = 'wsConnect';
+ if (!window.WebSocket) {console.log('this browser does not support web sockets');}
+ let content = $('#body');
+ let blockchain = $('#blockchain');
+ // updated from ws: to wss: to support access over https
+ if (host_address.slice(0,9) === 'localhost')
+ {
+ wsSocket = new WebSocket('ws://'+host_address);
+ }else
+ {
+ wsSocket = new WebSocket('wss://'+host_address);
+ }
+ wsSocket.onerror = function (error) {console.log('WebSocket error on wsSocket: ', error);};
+ wsSocket.onopen = function ()
+ {console.log ('connect.onOpen initiated to: '+host_address); wsSocket.send('connected to client');};
+ wsSocket.onmessage = function (message)
+ {
+ let incoming
+ incoming = message.data;
+ // console.log(method+ ' incoming is: '+incoming);
+ while (incoming instanceof Object === false){incoming = JSON.parse(incoming);}
+ switch (incoming.type)
+ {
+ case 'Message':
+ content.append(formatMessage(incoming.data));
+ break;
+ case 'Alert':
+ let event = JSON.parse(incoming.data);
+ addNotification(event.type, event.ID, event.orderID);
+ break;
+ case 'BlockChain':
+ _blctr ++;
+ if (incoming.data !== 'connected')
+ {
+ $(blockchain).append('block '+incoming.data.header.number+' Hash: '+incoming.data.header.data_hash+'');
+ if (_blctr > 4) {let leftPos = $(blockchain).scrollLeft(); $(blockchain).animate({scrollLeft: leftPos + 300}, 250);}
+ }
+ break;
+ default:
+ console.log('Can Not Process message type: ',incoming.type);
+ }
+ };
}
\ No newline at end of file
diff --git a/Chapter12/HTML/js/z2b-financeCo.js b/Chapter12/HTML/js/z2b-financeCo.js
index 703b3e2..40ff08e 100644
--- a/Chapter12/HTML/js/z2b-financeCo.js
+++ b/Chapter12/HTML/js/z2b-financeCo.js
@@ -31,22 +31,19 @@ let f_alerts;
function loadFinanceCoUX ()
{
let toLoad = 'financeCo.html';
- // get the FinanceAlert Port
- getFinanceAlertPort();
if (buyers.length === 0)
- { $.when($.get(toLoad), $.get('/setup/getPort'), deferredMemberLoad()).done(function (page, port, res)
- {setupFinanceCo(page[0], port[0]);});
+ { $.when($.get(toLoad), deferredMemberLoad()).done(function (page, res)
+ {setupFinanceCo(page[0]);});
}
else{
- $.when($.get(toLoad), $.get('/setup/getPort')).done(function (page, port)
- {setupFinanceCo(page[0], port[0]);});
+ $.when($.get(toLoad)).done(function (page)
+ {setupFinanceCo(page);});
}
}
/**
* @param {String} page HTML page to load
- * @param {Integer} port Websocket port to use
*/
-function setupFinanceCo(page, port)
+function setupFinanceCo(page)
{
$('#body').empty();
$('#body').append(page);
@@ -56,9 +53,6 @@ function setupFinanceCo(page, port)
else
{$(f_notify).removeClass('off'); $(f_notify).addClass('on'); }
updatePage( 'financeCo');
- console.log('port is: '+port.port);
- msgPort = port.port;
- wsDisplay('finance_messages', msgPort);
let _clear = $('#financeCOclear');
let _list = $('#financeCOorderStatus');
let _orderDiv = $('#'+financeCOorderDiv);
@@ -157,7 +151,9 @@ function formatFinanceOrders(_target, _orders)
let _button = '