From 3a6546d1e91053cf2638f2210a2f3f1a6986def9 Mon Sep 17 00:00:00 2001 From: Bob Dill Date: Tue, 4 Sep 2018 15:09:58 -0400 Subject: [PATCH 1/4] update event management in all chapters to web sockets --- .../answers/composer/hlcAdmin_complete.js | 3 +- .../answers/js/z2b-admin_complete.js | 43 +-- Chapter05/HTML/js/z2b-admin.js | 35 +- Chapter05/HTML/js/z2b-events.js | 291 +++++++++++++--- Chapter05/HTML/js/z2b-initiate.js | 73 ++-- Chapter05/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcClient.js | 125 ++++++- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter05/controller/restapi/router.js | 30 +- Chapter05/index.js | 115 ++++--- .../answers/composer/hlcClient_complete.js | 101 +++++- .../answers/js/z2b-buyer_complete.js | 97 ++++-- Chapter06/HTML/js/z2b-admin.js | 43 +-- Chapter06/HTML/js/z2b-buyer.js | 208 ++++++++---- Chapter06/HTML/js/z2b-events.js | 292 +++++++++++++--- Chapter06/HTML/js/z2b-initiate.js | 73 ++-- Chapter06/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter06/controller/restapi/router.js | 8 +- Chapter06/index.js | 115 ++++--- Chapter06/network/lib/sample.js | 2 +- .../answers/composer/hlcClient_complete.js | 101 +++++- .../answers/js/z2b-seller_complete.js | 37 +- Chapter07/HTML/js/z2b-admin.js | 43 +-- Chapter07/HTML/js/z2b-buyer.js | 97 ++++-- Chapter07/HTML/js/z2b-events.js | 234 +++++++++++-- Chapter07/HTML/js/z2b-initiate.js | 10 +- Chapter07/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter07/controller/restapi/router.js | 8 +- Chapter07/index.js | 104 ++++-- Chapter07/network/lib/sample.js | 2 +- .../answers/composer/hlcClient_complete.js | 101 +++++- .../answers/js/z2b-provider_complete.js | 38 ++- Chapter08/HTML/js/z2b-admin.js | 43 +-- Chapter08/HTML/js/z2b-buyer.js | 96 ++++-- Chapter08/HTML/js/z2b-events.js | 291 +++++++++++++--- Chapter08/HTML/js/z2b-initiate.js | 71 ++-- Chapter08/HTML/js/z2b-seller.js | 37 +- Chapter08/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter08/controller/restapi/router.js | 8 +- Chapter08/index.js | 104 ++++-- Chapter08/network/lib/sample.js | 2 +- .../answers/composer/hlcClient_complete.js | 101 +++++- .../answers/js/z2b-shipper_complete.js | 39 ++- Chapter09/HTML/js/z2b-admin.js | 43 +-- Chapter09/HTML/js/z2b-buyer.js | 96 ++++-- Chapter09/HTML/js/z2b-events.js | 291 +++++++++++++--- Chapter09/HTML/js/z2b-initiate.js | 71 ++-- Chapter09/HTML/js/z2b-provider.js | 38 ++- Chapter09/HTML/js/z2b-seller.js | 37 +- Chapter09/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter09/controller/restapi/router.js | 8 +- Chapter09/index.js | 107 ++++-- Chapter09/network/lib/sample.js | 2 +- .../answers/composer/hlcClient_complete.js | 101 +++++- .../answers/js/z2b-financeCo_complete.js | 40 ++- Chapter10/HTML/js/z2b-admin.js | 43 +-- Chapter10/HTML/js/z2b-buyer.js | 96 ++++-- Chapter10/HTML/js/z2b-events.js | 291 +++++++++++++--- Chapter10/HTML/js/z2b-financeCo.js | 42 ++- Chapter10/HTML/js/z2b-initiate.js | 71 ++-- Chapter10/HTML/js/z2b-provider.js | 38 ++- Chapter10/HTML/js/z2b-seller.js | 37 +- Chapter10/HTML/js/z2b-shipper.js | 39 ++- Chapter10/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter10/controller/restapi/router.js | 8 +- Chapter10/index.js | 104 ++++-- Chapter10/network/lib/sample.js | 2 +- .../answers/js/z2b-events_complete.js | 201 ++++++++++- Chapter11/HTML/js/z2b-admin.js | 43 +-- Chapter11/HTML/js/z2b-buyer.js | 96 ++++-- Chapter11/HTML/js/z2b-events.js | 224 ++++++++++-- Chapter11/HTML/js/z2b-financeCo.js | 40 ++- Chapter11/HTML/js/z2b-initiate.js | 10 +- Chapter11/HTML/js/z2b-provider.js | 38 ++- Chapter11/HTML/js/z2b-seller.js | 41 ++- Chapter11/HTML/js/z2b-shipper.js | 37 +- Chapter11/HTML/js/z2b-utilities.js | 320 +++++++++--------- .../restapi/features/composer/Z2B_Services.js | 214 ++++++------ .../restapi/features/composer/autoLoad.js | 49 +-- .../restapi/features/composer/hlcAdmin.js | 3 +- .../restapi/features/composer/hlcClient.js | 101 +++++- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter11/controller/restapi/router.js | 8 +- Chapter11/index.js | 104 ++++-- Chapter11/network/lib/sample.js | 2 +- .../answers/composer/hlcClient_complete.js | 111 +++--- .../answers/js/z2b-buyer_complete.js | 21 +- .../answers/js/z2b-events_complete.js | 107 +++--- .../answers/js/z2b-financeCo_complete.js | 16 +- .../answers/network/lib/sample_complete.js | 4 +- Chapter12/HTML/js/z2b-admin.js | 43 +-- Chapter12/HTML/js/z2b-buyer.js | 51 ++- Chapter12/HTML/js/z2b-events.js | 115 ++++--- Chapter12/HTML/js/z2b-financeCo.js | 20 +- Chapter12/HTML/js/z2b-initiate.js | 6 +- Chapter12/HTML/js/z2b-provider.js | 15 +- Chapter12/HTML/js/z2b-seller.js | 14 +- Chapter12/HTML/js/z2b-shipper.js | 15 +- Chapter12/HTML/js/z2b-utilities.js | 9 - Chapter12/controller/env.json | 26 +- .../restapi/features/composer/Z2B_Services.js | 269 ++++++--------- .../restapi/features/composer/autoLoad.js | 79 +---- .../cards/PeerAdmin@hlfv1/connection.json | 1 + .../PeerAdmin@hlfv1/credentials/certificate | 14 + .../PeerAdmin@hlfv1/credentials/privateKey | 5 + .../creds/cards/PeerAdmin@hlfv1/metadata.json | 1 + .../connection.json | 1 + .../metadata.json | 1 + ...8c9423e31568da0c340ca187a9b17aa9a4457-priv | 5 + ...b8c9423e31568da0c340ca187a9b17aa9a4457-pub | 4 + .../client-data/PeerAdmin@hlfv1/PeerAdmin | 1 + ...19b96554687e00c2c9daf45db7811a0c74b84-priv | 5 + ...b19b96554687e00c2c9daf45db7811a0c74b84-pub | 4 + ...c6f04260b40ac8871f96fcf1789e8ae033e89-priv | 5 + ...fc6f04260b40ac8871f96fcf1789e8ae033e89-pub | 4 + ...2c74f8388e18971793d32f21df75b6eb33e19-priv | 5 + ...92c74f8388e18971793d32f21df75b6eb33e19-pub | 4 + ...fe850d9cabb18ffd763c73d5205df5a3f9bfb-priv | 5 + ...afe850d9cabb18ffd763c73d5205df5a3f9bfb-pub | 4 + ...ed69bfdffe8457cbd790a3d6f289d56bc0220-priv | 5 + ...ced69bfdffe8457cbd790a3d6f289d56bc0220-pub | 4 + ...f96a801f7e0c3601f040f12a0915519cbda16-priv | 5 + ...df96a801f7e0c3601f040f12a0915519cbda16-pub | 4 + .../admin@zerotoblockchain-network/admin | 1 + ...68cea034e3166974615868c02140aeaa7fc54-priv | 5 + ...368cea034e3166974615868c02140aeaa7fc54-pub | 4 + .../restapi/features/composer/hlcAdmin.js | 3 +- .../restapi/features/composer/hlcClient.js | 83 ++--- .../features/composer/queryBlockChain.js | 123 ++++--- Chapter12/controller/restapi/router.js | 6 +- Chapter12/index.js | 104 ++++-- Chapter12/network/lib/sample.js | 2 +- Chapter13/network/lib/sample.js | 2 +- 155 files changed, 7438 insertions(+), 4634 deletions(-) create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/connection.json create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/certificate create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/privateKey create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/metadata.json create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/connection.json create mode 100644 Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/metadata.json create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/PeerAdmin create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-pub create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/admin create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-priv create mode 100644 Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-pub diff --git a/Chapter05/Documentation/answers/composer/hlcAdmin_complete.js b/Chapter05/Documentation/answers/composer/hlcAdmin_complete.js index 8e9e8b9..46b188c 100644 --- a/Chapter05/Documentation/answers/composer/hlcAdmin_complete.js +++ b/Chapter05/Documentation/answers/composer/hlcAdmin_complete.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/Chapter05/Documentation/answers/js/z2b-admin_complete.js b/Chapter05/Documentation/answers/js/z2b-admin_complete.js index 67f421a..f3c3ffc 100644 --- a/Chapter05/Documentation/answers/js/z2b-admin_complete.js +++ b/Chapter05/Documentation/answers/js/z2b-admin_complete.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/Chapter05/HTML/js/z2b-admin.js b/Chapter05/HTML/js/z2b-admin.js index 1ad3017..3186b1e 100644 --- a/Chapter05/HTML/js/z2b-admin.js +++ b/Chapter05/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 */ @@ -339,7 +325,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

'); }); } /** @@ -732,21 +718,10 @@ 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); }); } /** diff --git a/Chapter05/HTML/js/z2b-events.js b/Chapter05/HTML/js/z2b-events.js index f93de96..282f3ea 100644 --- a/Chapter05/HTML/js/z2b-events.js +++ b/Chapter05/HTML/js/z2b-events.js @@ -14,31 +14,79 @@ // z2c-events.js +'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 */ function memberLoad () { - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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); - }); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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 @@ -46,39 +94,190 @@ function memberLoad () */ function deferredMemberLoad() { - var d_prompts = $.Deferred(); - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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(); - }).fail(d_prompts.reject); - return d_prompts.promise(); + let d_prompts = $.Deferred(); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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; + 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/Chapter05/HTML/js/z2b-initiate.js b/Chapter05/HTML/js/z2b-initiate.js index 8f676be..fd9a9d0 100644 --- a/Chapter05/HTML/js/z2b-initiate.js +++ b/Chapter05/HTML/js/z2b-initiate.js @@ -12,42 +12,53 @@ * limitations under the License. */ -// z2c-initiate.js -var connectionProfileName = "z2b-test-profile"; -var networkFile = "zerotoblockchain-network.bna" -var businessNetwork = "zerotoblockchain-network"; - -var buyers, sellers, providers, shippers; -var s_string, p_string, sh_string; - -var orderStatus = { - Created: {code: 1, text: 'Order Created'}, - Bought: {code: 2, text: 'Order Purchased'}, - Cancelled: {code: 3, text: 'Order Cancelled'}, - Ordered: {code: 4, text: 'Order Submitted to Provider'}, - ShipRequest: {code: 5, text: 'Shipping Requested'}, - Delivered: {code: 6, text: 'Order Delivered'}, - Delivering: {code: 15, text: 'Order being Delivered'}, - Backordered: {code: 7, text: 'Order Backordered'}, - Dispute: {code: 8, text: 'Order Disputed'}, - Resolve: {code: 9, text: 'Order Dispute Resolved'}, - PayRequest: {code: 10, text: 'Payment Requested'}, - Authorize: {code: 11, text: 'Payment Approved'}, - Paid: {code: 14, text: 'Payment Processed'}, - Refund: {code: 12, text: 'Order Refund Requested'}, - Refunded: {code: 13, text: 'Order Refunded'} +// z2b-initiate.js + +'use strict'; + +let connectionProfileName = 'z2b-test-profile'; +let networkFile = 'zerotoblockchain-network.bna'; +let businessNetwork = 'zerotoblockchain-network'; + +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 = { + Created: {code: 1, text: 'Order Created'}, + Bought: {code: 2, text: 'Order Purchased'}, + Cancelled: {code: 3, text: 'Order Cancelled'}, + Ordered: {code: 4, text: 'Order Submitted to Provider'}, + ShipRequest: {code: 5, text: 'Shipping Requested'}, + Delivered: {code: 6, text: 'Order Delivered'}, + Delivering: {code: 15, text: 'Order being Delivered'}, + Backordered: {code: 7, text: 'Order Backordered'}, + Dispute: {code: 8, text: 'Order Disputed'}, + Resolve: {code: 9, text: 'Order Dispute Resolved'}, + PayRequest: {code: 10, text: 'Payment Requested'}, + Authorize: {code: 11, text: 'Payment Approved'}, + Paid: {code: 14, text: 'Payment Processed'}, + Refund: {code: 12, text: 'Order Refund Requested'}, + Refunded: {code: 13, text: 'Order Refunded'} }; /** * standard home page initialization routine * Refer to this by {@link initPage()}. */ - function initPage () +function initPage () { - // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English - goMultiLingual("US_English", "index"); - // singleUX loads the members already present in the network - memberLoad(); - // getChainEvents creates a web socket connection with the server and initiates blockchain event monitoring - getChainEvents(); + // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English + goMultiLingual('US_English', 'index'); + // singleUX loads the members already present in the network + memberLoad(); + // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring + getChainEvents(); + // get the asynch port + wsConnect(); } diff --git a/Chapter05/HTML/js/z2b-utilities.js b/Chapter05/HTML/js/z2b-utilities.js index ae1637f..3b137f0 100644 --- a/Chapter05/HTML/js/z2b-utilities.js +++ b/Chapter05/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 - +// z2b-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 += '
  • '+_array[_idx].menu+'
  • '} + })(each, _res); + } + _choices.append(_str); }); - var _choices = $("#lang_choices"); - _choices.empty(); var _str = ""; - for (each in _res) - {(function(_idx, _array) - {if (_array[_idx].active == "yes") - {_str += '
  • '+_array[_idx].menu+'
  • '} - })(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 += '
  • '+_array[_idx].menu+'
  • '} - })(each, _res)} - _choices.append(_str); - }); + $.when($.get('/api/getSupportedLanguages')).done(function(_res) + { + languages = _res; console.log(_res); let _choices = $('#lang_choices'); + _choices.empty(); let _str = ''; + for (let each in _res) + {(function(_idx, _array) + {if (_array[_idx].active === 'yes') + {_str += '
  • '+_array[_idx].menu+'
  • ';} + })(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 += ""+_curObj+""+_arr[_idx][_curObj]+""; - })(each, _exports); - } - _eTarget.append(_str); - _str = ""; - for (each in _classes) { - (function(_idx, _arr){ - _curObj = Object.getOwnPropertyNames(_arr[_idx]); - for (every in _arr[_idx][_curObj[0]]){ - (function(_idx2, _arr2) - { - _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]); - _str+= ""+_curObj[0]+""+_curObj2+""+_arr2[_idx2][_curObj2[0]]+""; - })(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/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/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 += ''; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _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/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 = ''; + $('#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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '+_item.itemNo+''+_item.itemDescription+'
    '; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _str+= '' for (let every in _arr[_idx].items) { @@ -249,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/js/z2b-events.js b/Chapter06/HTML/js/z2b-events.js index 1b67217..282f3ea 100644 --- a/Chapter06/HTML/js/z2b-events.js +++ b/Chapter06/HTML/js/z2b-events.js @@ -14,31 +14,79 @@ // z2c-events.js +'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 */ function memberLoad () { - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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); - }); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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 @@ -46,40 +94,190 @@ function memberLoad () */ function deferredMemberLoad() { - var d_prompts = $.Deferred(); - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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) - { - console.log('buyers: ',_buyers); - 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(); - }).fail(d_prompts.reject); - return d_prompts.promise(); + let d_prompts = $.Deferred(); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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; + 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/Chapter06/HTML/js/z2b-initiate.js b/Chapter06/HTML/js/z2b-initiate.js index 290ef03..fd9a9d0 100644 --- a/Chapter06/HTML/js/z2b-initiate.js +++ b/Chapter06/HTML/js/z2b-initiate.js @@ -12,42 +12,53 @@ * limitations under the License. */ -// z2c-initiate.js -var connectionProfileName = "z2b-test-profile"; -var networkFile = "zerotoblockchain-network.bna" -var businessNetwork = "zerotoblockchain-network"; - -var buyers, sellers, providers, shippers; -var s_string, p_string, sh_string; - -var orderStatus = { - Created: {code: 1, text: 'Order Created'}, - Bought: {code: 2, text: 'Order Purchased'}, - Cancelled: {code: 3, text: 'Order Cancelled'}, - Ordered: {code: 4, text: 'Order Submitted to Provider'}, - ShipRequest: {code: 5, text: 'Shipping Requested'}, - Delivered: {code: 6, text: 'Order Delivered'}, - Delivering: {code: 15, text: 'Order being Delivered'}, - Backordered: {code: 7, text: 'Order Backordered'}, - Dispute: {code: 8, text: 'Order Disputed'}, - Resolve: {code: 9, text: 'Order Dispute Resolved'}, - PayRequest: {code: 10, text: 'Payment Requested'}, - Authorize: {code: 11, text: 'Payment Approved'}, - Paid: {code: 14, text: 'Payment Processed'}, - Refund: {code: 12, text: 'Order Refund Requested'}, - Refunded: {code: 13, text: 'Order Refunded'} +// z2b-initiate.js + +'use strict'; + +let connectionProfileName = 'z2b-test-profile'; +let networkFile = 'zerotoblockchain-network.bna'; +let businessNetwork = 'zerotoblockchain-network'; + +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 = { + Created: {code: 1, text: 'Order Created'}, + Bought: {code: 2, text: 'Order Purchased'}, + Cancelled: {code: 3, text: 'Order Cancelled'}, + Ordered: {code: 4, text: 'Order Submitted to Provider'}, + ShipRequest: {code: 5, text: 'Shipping Requested'}, + Delivered: {code: 6, text: 'Order Delivered'}, + Delivering: {code: 15, text: 'Order being Delivered'}, + Backordered: {code: 7, text: 'Order Backordered'}, + Dispute: {code: 8, text: 'Order Disputed'}, + Resolve: {code: 9, text: 'Order Dispute Resolved'}, + PayRequest: {code: 10, text: 'Payment Requested'}, + Authorize: {code: 11, text: 'Payment Approved'}, + Paid: {code: 14, text: 'Payment Processed'}, + Refund: {code: 12, text: 'Order Refund Requested'}, + Refunded: {code: 13, text: 'Order Refunded'} }; /** * standard home page initialization routine * Refer to this by {@link initPage()}. */ - function initPage () +function initPage () { - // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English - goMultiLingual("US_English", "index"); - // singleUX loads the members already present in the network - memberLoad(); - // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring - getChainEvents(); + // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English + goMultiLingual('US_English', 'index'); + // singleUX loads the members already present in the network + memberLoad(); + // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring + getChainEvents(); + // get the asynch port + wsConnect(); } diff --git a/Chapter06/HTML/js/z2b-utilities.js b/Chapter06/HTML/js/z2b-utilities.js index ae1637f..3b137f0 100644 --- a/Chapter06/HTML/js/z2b-utilities.js +++ b/Chapter06/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 - +// z2b-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 += '
  • '+_array[_idx].menu+'
  • '} + })(each, _res); + } + _choices.append(_str); }); - var _choices = $("#lang_choices"); - _choices.empty(); var _str = ""; - for (each in _res) - {(function(_idx, _array) - {if (_array[_idx].active == "yes") - {_str += '
  • '+_array[_idx].menu+'
  • '} - })(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 += '
  • '+_array[_idx].menu+'
  • '} - })(each, _res)} - _choices.append(_str); - }); + $.when($.get('/api/getSupportedLanguages')).done(function(_res) + { + languages = _res; console.log(_res); let _choices = $('#lang_choices'); + _choices.empty(); let _str = ''; + for (let each in _res) + {(function(_idx, _array) + {if (_array[_idx].active === 'yes') + {_str += '
  • '+_array[_idx].menu+'
  • ';} + })(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 += ""; - })(each, _exports); - } - _eTarget.append(_str); - _str = ""; - for (each in _classes) { - (function(_idx, _arr){ - _curObj = Object.getOwnPropertyNames(_arr[_idx]); - for (every in _arr[_idx][_curObj[0]]){ - (function(_idx2, _arr2) - { - _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]); - _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 += ''; + })(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+= ''; + })(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/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..d8f70ae 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(); 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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    "+_curObj+""+_arr[_idx][_curObj]+"
    "+_curObj[0]+""+_curObj2+""+_arr2[_idx2][_curObj2[0]]+"
    '+_curObj+''+_arr[_idx][_curObj]+'
    '+_curObj[0]+''+_curObj2+''+_arr2[_idx2][_curObj2[0]]+'
    '; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _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 += '
  • '+_array[_idx].menu+'
  • '} + })(each, _res); + } + _choices.append(_str); }); - var _choices = $("#lang_choices"); - _choices.empty(); var _str = ""; - for (each in _res) - {(function(_idx, _array) - {if (_array[_idx].active == "yes") - {_str += '
  • '+_array[_idx].menu+'
  • '} - })(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 += '
  • '+_array[_idx].menu+'
  • '} - })(each, _res)} - _choices.append(_str); - }); + $.when($.get('/api/getSupportedLanguages')).done(function(_res) + { + languages = _res; console.log(_res); let _choices = $('#lang_choices'); + _choices.empty(); let _str = ''; + for (let each in _res) + {(function(_idx, _array) + {if (_array[_idx].active === 'yes') + {_str += '
  • '+_array[_idx].menu+'
  • ';} + })(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 += ""; - })(each, _exports); - } - _eTarget.append(_str); - _str = ""; - for (each in _classes) { - (function(_idx, _arr){ - _curObj = Object.getOwnPropertyNames(_arr[_idx]); - for (every in _arr[_idx][_curObj[0]]){ - (function(_idx2, _arr2) - { - _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]); - _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 += ''; + })(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+= ''; + })(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/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..d8f70ae 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(); 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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    "+_curObj+""+_arr[_idx][_curObj]+"
    "+_curObj[0]+""+_curObj2+""+_arr2[_idx2][_curObj2[0]]+"
    '+_curObj+''+_arr[_idx][_curObj]+'
    '+_curObj[0]+''+_curObj2+''+_arr2[_idx2][_curObj2[0]]+'
    '; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _str+= '' for (let every in _arr[_idx].items) { @@ -286,18 +304,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/Chapter08/HTML/js/z2b-events.js b/Chapter08/HTML/js/z2b-events.js index f93de96..282f3ea 100644 --- a/Chapter08/HTML/js/z2b-events.js +++ b/Chapter08/HTML/js/z2b-events.js @@ -14,31 +14,79 @@ // z2c-events.js +'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 */ function memberLoad () { - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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); - }); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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 @@ -46,39 +94,190 @@ function memberLoad () */ function deferredMemberLoad() { - var d_prompts = $.Deferred(); - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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(); - }).fail(d_prompts.reject); - return d_prompts.promise(); + let d_prompts = $.Deferred(); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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; + 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/Chapter08/HTML/js/z2b-initiate.js b/Chapter08/HTML/js/z2b-initiate.js index 290ef03..7ba4493 100644 --- a/Chapter08/HTML/js/z2b-initiate.js +++ b/Chapter08/HTML/js/z2b-initiate.js @@ -13,41 +13,52 @@ */ // z2c-initiate.js -var connectionProfileName = "z2b-test-profile"; -var networkFile = "zerotoblockchain-network.bna" -var businessNetwork = "zerotoblockchain-network"; - -var buyers, sellers, providers, shippers; -var s_string, p_string, sh_string; - -var orderStatus = { - Created: {code: 1, text: 'Order Created'}, - Bought: {code: 2, text: 'Order Purchased'}, - Cancelled: {code: 3, text: 'Order Cancelled'}, - Ordered: {code: 4, text: 'Order Submitted to Provider'}, - ShipRequest: {code: 5, text: 'Shipping Requested'}, - Delivered: {code: 6, text: 'Order Delivered'}, - Delivering: {code: 15, text: 'Order being Delivered'}, - Backordered: {code: 7, text: 'Order Backordered'}, - Dispute: {code: 8, text: 'Order Disputed'}, - Resolve: {code: 9, text: 'Order Dispute Resolved'}, - PayRequest: {code: 10, text: 'Payment Requested'}, - Authorize: {code: 11, text: 'Payment Approved'}, - Paid: {code: 14, text: 'Payment Processed'}, - Refund: {code: 12, text: 'Order Refund Requested'}, - Refunded: {code: 13, text: 'Order Refunded'} + +'use strict'; + +let connectionProfileName = 'z2b-test-profile'; +let networkFile = 'zerotoblockchain-network.bna'; +let businessNetwork = 'zerotoblockchain-network'; + +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 = { + Created: {code: 1, text: 'Order Created'}, + Bought: {code: 2, text: 'Order Purchased'}, + Cancelled: {code: 3, text: 'Order Cancelled'}, + Ordered: {code: 4, text: 'Order Submitted to Provider'}, + ShipRequest: {code: 5, text: 'Shipping Requested'}, + Delivered: {code: 6, text: 'Order Delivered'}, + Delivering: {code: 15, text: 'Order being Delivered'}, + Backordered: {code: 7, text: 'Order Backordered'}, + Dispute: {code: 8, text: 'Order Disputed'}, + Resolve: {code: 9, text: 'Order Dispute Resolved'}, + PayRequest: {code: 10, text: 'Payment Requested'}, + Authorize: {code: 11, text: 'Payment Approved'}, + Paid: {code: 14, text: 'Payment Processed'}, + Refund: {code: 12, text: 'Order Refund Requested'}, + Refunded: {code: 13, text: 'Order Refunded'} }; /** * standard home page initialization routine * Refer to this by {@link initPage()}. */ - function initPage () +function initPage () { - // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English - goMultiLingual("US_English", "index"); - // singleUX loads the members already present in the network - memberLoad(); - // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring - getChainEvents(); + // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English + goMultiLingual('US_English', 'index'); + // singleUX loads the members already present in the network + memberLoad(); + // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring + getChainEvents(); + // get the asynch port + wsConnect(); } diff --git a/Chapter08/HTML/js/z2b-seller.js b/Chapter08/HTML/js/z2b-seller.js index ecc7209..19072a8 100644 --- a/Chapter08/HTML/js/z2b-seller.js +++ b/Chapter08/HTML/js/z2b-seller.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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; @@ -143,7 +151,7 @@ function formatShipperOrders(_target, _orders) console.log('shipper _action: '+_action); if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+_statusText+''+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+'Buyer: '+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+_statusText+''+_button+''; _str+= '' 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/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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _str+= '' for (let every in _arr[_idx].items) { @@ -286,18 +304,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/Chapter09/HTML/js/z2b-events.js b/Chapter09/HTML/js/z2b-events.js index f93de96..282f3ea 100644 --- a/Chapter09/HTML/js/z2b-events.js +++ b/Chapter09/HTML/js/z2b-events.js @@ -14,31 +14,79 @@ // z2c-events.js +'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 */ function memberLoad () { - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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); - }); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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 @@ -46,39 +94,190 @@ function memberLoad () */ function deferredMemberLoad() { - var d_prompts = $.Deferred(); - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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(); - }).fail(d_prompts.reject); - return d_prompts.promise(); + let d_prompts = $.Deferred(); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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; + 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/Chapter09/HTML/js/z2b-initiate.js b/Chapter09/HTML/js/z2b-initiate.js index 290ef03..7ba4493 100644 --- a/Chapter09/HTML/js/z2b-initiate.js +++ b/Chapter09/HTML/js/z2b-initiate.js @@ -13,41 +13,52 @@ */ // z2c-initiate.js -var connectionProfileName = "z2b-test-profile"; -var networkFile = "zerotoblockchain-network.bna" -var businessNetwork = "zerotoblockchain-network"; - -var buyers, sellers, providers, shippers; -var s_string, p_string, sh_string; - -var orderStatus = { - Created: {code: 1, text: 'Order Created'}, - Bought: {code: 2, text: 'Order Purchased'}, - Cancelled: {code: 3, text: 'Order Cancelled'}, - Ordered: {code: 4, text: 'Order Submitted to Provider'}, - ShipRequest: {code: 5, text: 'Shipping Requested'}, - Delivered: {code: 6, text: 'Order Delivered'}, - Delivering: {code: 15, text: 'Order being Delivered'}, - Backordered: {code: 7, text: 'Order Backordered'}, - Dispute: {code: 8, text: 'Order Disputed'}, - Resolve: {code: 9, text: 'Order Dispute Resolved'}, - PayRequest: {code: 10, text: 'Payment Requested'}, - Authorize: {code: 11, text: 'Payment Approved'}, - Paid: {code: 14, text: 'Payment Processed'}, - Refund: {code: 12, text: 'Order Refund Requested'}, - Refunded: {code: 13, text: 'Order Refunded'} + +'use strict'; + +let connectionProfileName = 'z2b-test-profile'; +let networkFile = 'zerotoblockchain-network.bna'; +let businessNetwork = 'zerotoblockchain-network'; + +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 = { + Created: {code: 1, text: 'Order Created'}, + Bought: {code: 2, text: 'Order Purchased'}, + Cancelled: {code: 3, text: 'Order Cancelled'}, + Ordered: {code: 4, text: 'Order Submitted to Provider'}, + ShipRequest: {code: 5, text: 'Shipping Requested'}, + Delivered: {code: 6, text: 'Order Delivered'}, + Delivering: {code: 15, text: 'Order being Delivered'}, + Backordered: {code: 7, text: 'Order Backordered'}, + Dispute: {code: 8, text: 'Order Disputed'}, + Resolve: {code: 9, text: 'Order Dispute Resolved'}, + PayRequest: {code: 10, text: 'Payment Requested'}, + Authorize: {code: 11, text: 'Payment Approved'}, + Paid: {code: 14, text: 'Payment Processed'}, + Refund: {code: 12, text: 'Order Refund Requested'}, + Refunded: {code: 13, text: 'Order Refunded'} }; /** * standard home page initialization routine * Refer to this by {@link initPage()}. */ - function initPage () +function initPage () { - // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English - goMultiLingual("US_English", "index"); - // singleUX loads the members already present in the network - memberLoad(); - // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring - getChainEvents(); + // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English + goMultiLingual('US_English', 'index'); + // singleUX loads the members already present in the network + memberLoad(); + // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring + getChainEvents(); + // get the asynch port + wsConnect(); } diff --git a/Chapter09/HTML/js/z2b-provider.js b/Chapter09/HTML/js/z2b-provider.js index 99d667f..4a9e800 100644 --- a/Chapter09/HTML/js/z2b-provider.js +++ b/Chapter09/HTML/js/z2b-provider.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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; @@ -148,7 +154,7 @@ function formatFinanceOrders(_target, _orders) let _len = 'resource:org.acme.Z2BTestNetwork.Buyer#'.length; let _buyer = _arr[_idx].buyer.substring(_len, _arr[_idx].buyer.length); _str += '
    '; - _str += ''+_action+''+_button+'
    Order #StatusTotalBuyer: '+findMember(_buyer,buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+''+_button+''; _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/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 += ''; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _str+= '' for (let every in _arr[_idx].items) { @@ -286,18 +304,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/Chapter10/HTML/js/z2b-events.js b/Chapter10/HTML/js/z2b-events.js index f93de96..282f3ea 100644 --- a/Chapter10/HTML/js/z2b-events.js +++ b/Chapter10/HTML/js/z2b-events.js @@ -14,31 +14,79 @@ // z2c-events.js +'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 */ function memberLoad () { - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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); - }); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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 @@ -46,39 +94,190 @@ function memberLoad () */ function deferredMemberLoad() { - var d_prompts = $.Deferred(); - var options = {}; - options.registry = 'Seller'; - var options2 = {}; - options2.registry = 'Buyer'; - var options3 = {}; - options3.registry = 'Provider'; - var options4 = {}; - options4.registry = 'Shipper'; - $.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(); - }).fail(d_prompts.reject); - return d_prompts.promise(); + let d_prompts = $.Deferred(); + let options = {}; + options.registry = 'Seller'; + let options2 = {}; + options2.registry = 'Buyer'; + let options3 = {}; + options3.registry = 'Provider'; + let options4 = {}; + options4.registry = 'Shipper'; + $.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 = 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; + 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/Chapter10/HTML/js/z2b-financeCo.js b/Chapter10/HTML/js/z2b-financeCo.js index 64355a0..211b139 100644 --- a/Chapter10/HTML/js/z2b-financeCo.js +++ b/Chapter10/HTML/js/z2b-financeCo.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,8 +81,8 @@ 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) { @@ -144,8 +150,10 @@ function formatFinanceOrders(_target, _orders) let _button = '' _action += ''; if (_idx > 0) {_str += '
    ';} - _str += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '; - _str += ''+_action+''+_button+'
    Order #StatusTotalBuyer: '+findMember(_arr[_idx].buyer,buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + let _len = 'resource:org.acme.Z2BTestNetwork.Buyer#'.length; + let _buyer = _arr[_idx].buyer.substring(_len, _arr[_idx].buyer.length); + _str += '
    '; + _str += ''+_action+''+_button+'
    Order #StatusTotalBuyer: '+findMember(_buyer,buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; _str+= formatDetail(_idx, _arr[_idx]); })(each, _orders); } @@ -168,11 +176,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/js/z2b-initiate.js b/Chapter10/HTML/js/z2b-initiate.js index 290ef03..7ba4493 100644 --- a/Chapter10/HTML/js/z2b-initiate.js +++ b/Chapter10/HTML/js/z2b-initiate.js @@ -13,41 +13,52 @@ */ // z2c-initiate.js -var connectionProfileName = "z2b-test-profile"; -var networkFile = "zerotoblockchain-network.bna" -var businessNetwork = "zerotoblockchain-network"; - -var buyers, sellers, providers, shippers; -var s_string, p_string, sh_string; - -var orderStatus = { - Created: {code: 1, text: 'Order Created'}, - Bought: {code: 2, text: 'Order Purchased'}, - Cancelled: {code: 3, text: 'Order Cancelled'}, - Ordered: {code: 4, text: 'Order Submitted to Provider'}, - ShipRequest: {code: 5, text: 'Shipping Requested'}, - Delivered: {code: 6, text: 'Order Delivered'}, - Delivering: {code: 15, text: 'Order being Delivered'}, - Backordered: {code: 7, text: 'Order Backordered'}, - Dispute: {code: 8, text: 'Order Disputed'}, - Resolve: {code: 9, text: 'Order Dispute Resolved'}, - PayRequest: {code: 10, text: 'Payment Requested'}, - Authorize: {code: 11, text: 'Payment Approved'}, - Paid: {code: 14, text: 'Payment Processed'}, - Refund: {code: 12, text: 'Order Refund Requested'}, - Refunded: {code: 13, text: 'Order Refunded'} + +'use strict'; + +let connectionProfileName = 'z2b-test-profile'; +let networkFile = 'zerotoblockchain-network.bna'; +let businessNetwork = 'zerotoblockchain-network'; + +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 = { + Created: {code: 1, text: 'Order Created'}, + Bought: {code: 2, text: 'Order Purchased'}, + Cancelled: {code: 3, text: 'Order Cancelled'}, + Ordered: {code: 4, text: 'Order Submitted to Provider'}, + ShipRequest: {code: 5, text: 'Shipping Requested'}, + Delivered: {code: 6, text: 'Order Delivered'}, + Delivering: {code: 15, text: 'Order being Delivered'}, + Backordered: {code: 7, text: 'Order Backordered'}, + Dispute: {code: 8, text: 'Order Disputed'}, + Resolve: {code: 9, text: 'Order Dispute Resolved'}, + PayRequest: {code: 10, text: 'Payment Requested'}, + Authorize: {code: 11, text: 'Payment Approved'}, + Paid: {code: 14, text: 'Payment Processed'}, + Refund: {code: 12, text: 'Order Refund Requested'}, + Refunded: {code: 13, text: 'Order Refunded'} }; /** * standard home page initialization routine * Refer to this by {@link initPage()}. */ - function initPage () +function initPage () { - // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English - goMultiLingual("US_English", "index"); - // singleUX loads the members already present in the network - memberLoad(); - // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring - getChainEvents(); + // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English + goMultiLingual('US_English', 'index'); + // singleUX loads the members already present in the network + memberLoad(); + // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring + getChainEvents(); + // get the asynch port + wsConnect(); } diff --git a/Chapter10/HTML/js/z2b-provider.js b/Chapter10/HTML/js/z2b-provider.js index 99d667f..4a9e800 100644 --- a/Chapter10/HTML/js/z2b-provider.js +++ b/Chapter10/HTML/js/z2b-provider.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 += ''; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; @@ -143,7 +151,7 @@ function formatShipperOrders(_target, _orders) console.log('shipper _action: '+_action); if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+_statusText+''+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+'Buyer: '+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+_statusText+''+_button+''; _str+= '' 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 += '
  • '+_array[_idx].menu+'
  • '} + })(each, _res); + } + _choices.append(_str); }); - var _choices = $("#lang_choices"); - _choices.empty(); var _str = ""; - for (each in _res) - {(function(_idx, _array) - {if (_array[_idx].active == "yes") - {_str += '
  • '+_array[_idx].menu+'
  • '} - })(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 += '
  • '+_array[_idx].menu+'
  • '} - })(each, _res)} - _choices.append(_str); - }); + $.when($.get('/api/getSupportedLanguages')).done(function(_res) + { + languages = _res; console.log(_res); let _choices = $('#lang_choices'); + _choices.empty(); let _str = ''; + for (let each in _res) + {(function(_idx, _array) + {if (_array[_idx].active === 'yes') + {_str += '
  • '+_array[_idx].menu+'
  • ';} + })(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 += ""; - })(each, _exports); - } - _eTarget.append(_str); - _str = ""; - for (each in _classes) { - (function(_idx, _arr){ - _curObj = Object.getOwnPropertyNames(_arr[_idx]); - for (every in _arr[_idx][_curObj[0]]){ - (function(_idx2, _arr2) - { - _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]); - _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 += ''; + })(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+= ''; + })(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/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..ed6499f 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(); 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/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 += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    "+_curObj+""+_arr[_idx][_curObj]+"
    "+_curObj[0]+""+_curObj2+""+_arr2[_idx2][_curObj2[0]]+"
    '+_curObj+''+_arr[_idx][_curObj]+'
    '+_curObj[0]+''+_curObj2+''+_arr2[_idx2][_curObj2[0]]+'
    '; - _str += ''+_action+r_string+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.seller+findMember(_arr[_idx].seller.split('#')[1],sellers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+r_string+_button+''; _str+= '' for (let every in _arr[_idx].items) { @@ -286,18 +304,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/Chapter11/HTML/js/z2b-events.js b/Chapter11/HTML/js/z2b-events.js index 74cf7d3..282f3ea 100644 --- a/Chapter11/HTML/js/z2b-events.js +++ b/Chapter11/HTML/js/z2b-events.js @@ -16,6 +16,8 @@ 'use strict'; +let wsSocket; + /** * load the four initial user roles into a single page. */ @@ -23,24 +25,30 @@ 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) - { - // get the msgPort - - // empty and reload the body div - - // call the load<> functions - + { $.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) { - // empty and reload the body div - - // call the load<> functions - - }); + $('#body').empty(); + $('#body').append(_page); + loadBuyerUX(); + loadSellerUX(); + loadProviderUX(); + loadShipperUX(); + // Initialize Registration for all Z2B Business Events + goEventInitialize(); + }); } } /** @@ -60,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). @@ -87,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/js/z2b-financeCo.js b/Chapter11/HTML/js/z2b-financeCo.js index a74a755..40ff08e 100644 --- a/Chapter11/HTML/js/z2b-financeCo.js +++ b/Chapter11/HTML/js/z2b-financeCo.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 = '
    '+_action+''+_button+'
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    '; - _str += ''+_action+''+_button+'
    Order #StatusTotalBuyer: '+findMember(_buyer,buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += '
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; _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/Chapter11/HTML/js/z2b-initiate.js b/Chapter11/HTML/js/z2b-initiate.js index bc5076a..7ba4493 100644 --- a/Chapter11/HTML/js/z2b-initiate.js +++ b/Chapter11/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/Chapter11/HTML/js/z2b-provider.js b/Chapter11/HTML/js/z2b-provider.js index 68a670c..cc8d752 100644 --- a/Chapter11/HTML/js/z2b-provider.js +++ b/Chapter11/HTML/js/z2b-provider.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) { $('#providerbody').empty(); $('#providerbody').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 += ''; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+''+textPrompts.orderProcess.buyer+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+'
    '; @@ -143,7 +151,7 @@ function formatShipperOrders(_target, _orders) console.log('shipper _action: '+_action); if (_idx > 0) {_str += '
    ';} _str += ''; - _str += ''+_action+_statusText+''+_button+'
    '+textPrompts.orderProcess.orderno+''+textPrompts.orderProcess.status+''+textPrompts.orderProcess.total+'Buyer: '+findMember(_arr[_idx].buyer.split('#')[1],buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; + _str += ''+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00'+_action+_statusText+''+_button+''; _str+= '' 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 += '
  • '+_array[_idx].menu+'
  • '} + })(each, _res); + } + _choices.append(_str); }); - var _choices = $("#lang_choices"); - _choices.empty(); var _str = ""; - for (each in _res) - {(function(_idx, _array) - {if (_array[_idx].active == "yes") - {_str += '
  • '+_array[_idx].menu+'
  • '} - })(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 += '
  • '+_array[_idx].menu+'
  • '} - })(each, _res)} - _choices.append(_str); - }); + $.when($.get('/api/getSupportedLanguages')).done(function(_res) + { + languages = _res; console.log(_res); let _choices = $('#lang_choices'); + _choices.empty(); let _str = ''; + for (let each in _res) + {(function(_idx, _array) + {if (_array[_idx].active === 'yes') + {_str += '
  • '+_array[_idx].menu+'
  • ';} + })(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 += ""; - })(each, _exports); - } - _eTarget.append(_str); - _str = ""; - for (each in _classes) { - (function(_idx, _arr){ - _curObj = Object.getOwnPropertyNames(_arr[_idx]); - for (every in _arr[_idx][_curObj[0]]){ - (function(_idx2, _arr2) - { - _curObj2 = Object.getOwnPropertyNames(_arr2[_idx2]); - _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 += ''; + })(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+= ''; + })(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/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..ed6499f 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(); 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/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 = '' _action += ''; if (_idx > 0) {_str += '
    ';} - _str += '
    '+textPrompts.orderProcess.itemno+''+textPrompts.orderProcess.description+''+textPrompts.orderProcess.qty+''+textPrompts.orderProcess.price+'
    "+_curObj+""+_arr[_idx][_curObj]+"
    "+_curObj[0]+""+_curObj2+""+_arr2[_idx2][_curObj2[0]]+"
    '+_curObj+''+_arr[_idx][_curObj]+'
    '+_curObj[0]+''+_curObj2+''+_arr2[_idx2][_curObj2[0]]+'
    '; + let _len = 'resource:org.acme.Z2BTestNetwork.Buyer#'.length; + let _buyer = _arr[_idx].buyer.substring(_len, _arr[_idx].buyer.length); + _str += '
    Order #StatusTotalBuyer: '+findMember(_arr[_idx].buyer,buyers).companyName+'
    '; _str += ''+_action+''+_button+'
    Order #StatusTotalBuyer: '+findMember(_buyer,buyers).companyName+'
    '+_arr[_idx].id+''+JSON.parse(_arr[_idx].status).text+': '+_date+'$'+_arr[_idx].amount+'.00
    '; _str+= formatDetail(_idx, _arr[_idx]); })(each, _orders); diff --git a/Chapter12/HTML/js/z2b-initiate.js b/Chapter12/HTML/js/z2b-initiate.js index 3fcfe09..7ba4493 100644 --- a/Chapter12/HTML/js/z2b-initiate.js +++ b/Chapter12/HTML/js/z2b-initiate.js @@ -20,6 +20,8 @@ let connectionProfileName = 'z2b-test-profile'; let networkFile = 'zerotoblockchain-network.bna'; let businessNetwork = 'zerotoblockchain-network'; +let host_address = window.location.host; + let buyers = new Array(); let sellers= new Array(); let providers= new Array(); @@ -58,7 +60,5 @@ function initPage () // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring getChainEvents(); // get the asynch port - getPort(); - // get the Alert Port - getAlertPort(); + wsConnect(); } diff --git a/Chapter12/HTML/js/z2b-provider.js b/Chapter12/HTML/js/z2b-provider.js index b849d89..cc8d752 100644 --- a/Chapter12/HTML/js/z2b-provider.js +++ b/Chapter12/HTML/js/z2b-provider.js @@ -28,23 +28,21 @@ let p_id; 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) { $('#providerbody').empty(); $('#providerbody').append(page); @@ -52,9 +50,6 @@ function setupProvider(page, port) {$(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); diff --git a/Chapter12/HTML/js/z2b-seller.js b/Chapter12/HTML/js/z2b-seller.js index 3a8fd29..333b45b 100644 --- a/Chapter12/HTML/js/z2b-seller.js +++ b/Chapter12/HTML/js/z2b-seller.js @@ -26,23 +26,21 @@ let s_id; 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) { $('#sellerbody').empty(); $('#sellerbody').append(page); @@ -50,8 +48,6 @@ function setupSeller(page, port) {$(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); diff --git a/Chapter12/HTML/js/z2b-shipper.js b/Chapter12/HTML/js/z2b-shipper.js index 9746eb1..049d021 100644 --- a/Chapter12/HTML/js/z2b-shipper.js +++ b/Chapter12/HTML/js/z2b-shipper.js @@ -28,22 +28,20 @@ let sh_id; 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); @@ -51,9 +49,6 @@ function setupShipper(page, port) {$(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); diff --git a/Chapter12/HTML/js/z2b-utilities.js b/Chapter12/HTML/js/z2b-utilities.js index 6072ca2..7f2c962 100644 --- a/Chapter12/HTML/js/z2b-utilities.js +++ b/Chapter12/HTML/js/z2b-utilities.js @@ -292,14 +292,6 @@ function displayAPI(_api) */ function formatMessage(_msg) {return '

    '+_msg+'

    ';} -/** - * get the web socket port - */ -function getPort () -{ - if (msgPort === null) - { $.when($.get('/setup/getPort')).done(function (port){console.log('port is: '+port.port); msgPort = port.port;});} -} /** * closes all accordians in this div @@ -340,4 +332,3 @@ function accToggle(_parent, _body, _header) $(parent).removeClass('off'); $(parent).addClass('on'); } } - diff --git a/Chapter12/controller/env.json b/Chapter12/controller/env.json index dd1c45d..72dc4a1 100644 --- a/Chapter12/controller/env.json +++ b/Chapter12/controller/env.json @@ -1,16 +1,4 @@ { - "composer": - { - "connectionProfile": "hlfv1", - "network": "zerotoblockchain-network", - "adminID": "admin", - "adminPW": "adminpw", - "PeerAdmin": "PeerAdmin", - "PeerPW": "randomString", - "NS": "org.acme.Z2BTestNetwork", - "adminCard": "admin@zerotoblockchain-network", - "PeerCard": "PeerAdmin@hlfv1" - }, "fabric": { "user": "queryUser", @@ -24,6 +12,18 @@ "ordererURL" : "grpc://localhost:7050", "caURL": "http://localhost:7054" }, + "composer": + { + "connectionProfile": "hlfv1", + "network": "zerotoblockchain-network", + "adminID": "admin", + "adminPW": "adminpw", + "PeerAdmin": "PeerAdmin", + "PeerPW": "randomString", + "NS": "org.acme.Z2BTestNetwork", + "adminCard": "admin@zerotoblockchain-network", + "PeerCard": "PeerAdmin@hlfv1" + }, "metaData": { "version": 1, "userName": "temp", @@ -31,6 +31,8 @@ "businessNetwork":"", "enrollmentSecret": "temp" }, + "keyValStore": "/.composer-credentials", + "kube_address":"169.46.111.109", "connectionProfile": { "name": "hlfv1", diff --git a/Chapter12/controller/restapi/features/composer/Z2B_Services.js b/Chapter12/controller/restapi/features/composer/Z2B_Services.js index 4367cf3..c4f8aa4 100644 --- a/Chapter12/controller/restapi/features/composer/Z2B_Services.js +++ b/Chapter12/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,48 +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 {BusinessNetworkConnection} businessNetworkConnection - already created business network connection + * @returns {promise} promise */ loadTransaction: function (_con, _item, _id, businessNetworkConnection) { - var method = 'loadTransaction'; + let method = 'loadTransaction'; return businessNetworkConnection.submitTransaction(_item) .then(() => { - console.log(method+': order '+_id+' successfully added '); - _con.sendUTF(method+': 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 */ + * @param {businessNetworkConnection} _bnc - business network connection to use + * @returns {promise} promise + */ addOrder: function (_con, _order, _registry, _createNew, _bnc) - { - var 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); +{ + 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)} - }); + } + 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 @@ -168,23 +200,26 @@ addOrder: function (_con, _order, _registry, _createNew, _bnc) 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) @@ -207,7 +242,7 @@ addOrder: function (_con, _order, _registry, _createNew, _bnc) _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}); }, /** @@ -215,22 +250,23 @@ addOrder: function (_con, _order, _registry, _createNew, _bnc) * 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 @@ -254,128 +290,15 @@ addOrder: function (_con, _order, _registry, _createNew, _bnc) 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. + * 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 */ - 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. - */ - - 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}; - }, -/** - * the al_connection is used to display alerts to the unified view - * a sepaarate port from the user experience socket. - * @returns {websocket} - web socket connection to be used on the server side. - */ - -al_connection: null, -al_socketAddr: null, -al_socket: null, -createAlertSocket: function () +send: function (_locals, type, event) { - var port = app.get('port')+3; - if (this.al_socket == null) - { - this.al_socketAddr = port; - this.al_socket= new ws.server({httpServer: http.createServer().listen(this.al_socketAddr)}); - var _this = this; - this.al_socket.on('request', function(request) - { - _this.al_connection = request.accept(null, request.origin); - _this.al_connection.on('message', function(message) - { - console.log(message.utf8Data); - _this.al_connection.sendUTF(JSON.stringify({'type': 'connected', 'ID': 'admin', 'orderID': 'nought'})); - _this.al_connection.on('close', function(al_connection) {console.log('al_connection closed'); }); - }); - }); - } - return {conn: this.al_connection, socket: this.al_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. - */ - -f_connection: null, -f_socketAddr: null, -f_socket: null, -createFinanceAlertSocket: function () -{ - var port = app.get('port')+4; - if (this.f_socket == null) - { - this.f_socketAddr = port; - this.f_socket= new ws.server({httpServer: http.createServer().listen(this.f_socketAddr)}); - var _this = this; - this.f_socket.on('request', function(request) - { - _this.f_connection = request.accept(null, request.origin); - _this.f_connection.on('message', function(message) - { - console.log(message.utf8Data); - _this.f_connection.sendUTF(JSON.stringify({'type': 'connected', 'ID': 'admin', 'orderID': 'nought'})); - _this.f_connection.on('close', function(f_connection) {console.log('f_connection closed'); }); - }); - }); - } - return {conn: this.f_connection, socket: this.f_socketAddr}; -} - + _locals.processMessages({'type': type, 'data': event} ); } +}; module.exports = Z2Blockchain; \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/autoLoad.js b/Chapter12/controller/restapi/features/composer/autoLoad.js index 69dab55..da256aa 100644 --- a/Chapter12/controller/restapi/features/composer/autoLoad.js +++ b/Chapter12/controller/restapi/features/composer/autoLoad.js @@ -34,62 +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}); -}; - -/** - * getAlertPort 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.getAlertPort = function(req, res, next) { - let _conn = svc.createAlertSocket(); - res.send({'port': _conn.socket}); -}; - -/** - * getFinanceAlertPort 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.getFinanceAlertPort = function(req, res, next) { - let _conn = svc.createFinanceAlertSocket(); - res.send({'port': _conn.socket}); -}; /** * autoLoad reads the memberList.json file from the Startup folder and adds members, @@ -108,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 @@ -140,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); @@ -148,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. @@ -174,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); }); @@ -202,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 @@ -216,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); @@ -229,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 @@ -237,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);} }); }); @@ -250,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/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/connection.json b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/connection.json new file mode 100644 index 0000000..84312ef --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/connection.json @@ -0,0 +1 @@ +{"name":"hlfv1","type":"hlfv1","orderers":[{"url":"grpc://localhost:7050"}],"ca":{"url":"http://localhost:7054","name":"ca.org1.example.com"},"peers":[{"requestURL":"grpc://localhost:7051","eventURL":"grpc://localhost:7053"}],"channel":"composerchannel","mspID":"Org1MSP","timeout":300} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/certificate b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/certificate new file mode 100644 index 0000000..3adfdfc --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/certificate @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGjCCAcCgAwIBAgIRANuOnVN+yd/BGyoX7ioEklQwCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzEuZXhhbXBsZS5jb20wHhcNMTcwNjI2MTI0OTI2WhcNMjcwNjI0MTI0OTI2 +WjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWQWRtaW5Ab3JnMS5leGFtcGxlLmNvbTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABGu8KxBQ1GkxSTMVoLv7NXiYKWj5t6Dh +WRTJBHnLkWV7lRUfYaKAKFadSii5M7Z7ZpwD8NS7IsMdPR6Z4EyGgwKjTTBLMA4G +A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIBmrZau7BIB9 +rRLkwKmqpmSecIaOOr0CF6Mi2J5H4aauMAoGCCqGSM49BAMCA0gAMEUCIQC4sKQ6 +CEgqbTYe48az95W9/hnZ+7DI5eSnWUwV9vCd/gIgS5K6omNJydoFoEpaEIwM97uS +XVMHPa0iyC497vdNURA= +-----END CERTIFICATE----- diff --git a/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/privateKey b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/privateKey new file mode 100644 index 0000000..11b9fd4 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/credentials/privateKey @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg00IwLLBKoi/9ikb6 +ZOAV0S1XeNGWllvlFDeczRKQn2uhRANCAARrvCsQUNRpMUkzFaC7+zV4mClo+beg +4VkUyQR5y5Fle5UVH2GigChWnUoouTO2e2acA/DUuyLDHT0emeBMhoMC +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/metadata.json b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/metadata.json new file mode 100644 index 0000000..ab54dce --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/PeerAdmin@hlfv1/metadata.json @@ -0,0 +1 @@ +{"version":1,"userName":"PeerAdmin","roles":["PeerAdmin","ChannelAdmin"]} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/connection.json b/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/connection.json new file mode 100644 index 0000000..84312ef --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/connection.json @@ -0,0 +1 @@ +{"name":"hlfv1","type":"hlfv1","orderers":[{"url":"grpc://localhost:7050"}],"ca":{"url":"http://localhost:7054","name":"ca.org1.example.com"},"peers":[{"requestURL":"grpc://localhost:7051","eventURL":"grpc://localhost:7053"}],"channel":"composerchannel","mspID":"Org1MSP","timeout":300} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/metadata.json b/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/metadata.json new file mode 100644 index 0000000..7f0fa95 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/cards/admin@zerotoblockchain-network/metadata.json @@ -0,0 +1 @@ +{"version":1,"userName":"admin","businessNetwork":"zerotoblockchain-network","enrollmentSecret":"adminpw"} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-priv new file mode 100644 index 0000000..11b9fd4 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg00IwLLBKoi/9ikb6 +ZOAV0S1XeNGWllvlFDeczRKQn2uhRANCAARrvCsQUNRpMUkzFaC7+zV4mClo+beg +4VkUyQR5y5Fle5UVH2GigChWnUoouTO2e2acA/DUuyLDHT0emeBMhoMC +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-pub new file mode 100644 index 0000000..8c2855f --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa7wrEFDUaTFJMxWgu/s1eJgpaPm3 +oOFZFMkEecuRZXuVFR9hooAoVp1KKLkztntmnAPw1Lsiwx09HpngTIaDAg== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/PeerAdmin b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/PeerAdmin new file mode 100644 index 0000000..7db3ed0 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/PeerAdmin@hlfv1/PeerAdmin @@ -0,0 +1 @@ +{"name":"PeerAdmin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICGjCCAcCgAwIBAgIRANuOnVN+yd/BGyoX7ioEklQwCgYIKoZIzj0EAwIwczEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcwNjI2MTI0OTI2WhcNMjcwNjI0MTI0OTI2\nWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN\nU2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWQWRtaW5Ab3JnMS5leGFtcGxlLmNvbTBZ\nMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGu8KxBQ1GkxSTMVoLv7NXiYKWj5t6Dh\nWRTJBHnLkWV7lRUfYaKAKFadSii5M7Z7ZpwD8NS7IsMdPR6Z4EyGgwKjTTBLMA4G\nA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIBmrZau7BIB9\nrRLkwKmqpmSecIaOOr0CF6Mi2J5H4aauMAoGCCqGSM49BAMCA0gAMEUCIQC4sKQ6\nCEgqbTYe48az95W9/hnZ+7DI5eSnWUwV9vCd/gIgS5K6omNJydoFoEpaEIwM97uS\nXVMHPa0iyC497vdNURA=\n-----END CERTIFICATE-----\n"}}} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-priv new file mode 100644 index 0000000..75b209b --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgnGx1N7LcnQd9zZRo +NW7jlt7fB92neFClgWXfQwjGEJOhRANCAARH66vzjgmgc9yCygbYUWyG1CBVyYBL +EksdAjal58R21Bs1vERnNUS4qZgl1FtZfQTic+uBrz2lrpWiiTcIHglF +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-pub new file mode 100644 index 0000000..55dd524 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/157ef0c5933c7d5261153162b6b19b96554687e00c2c9daf45db7811a0c74b84-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER+ur844JoHPcgsoG2FFshtQgVcmA +SxJLHQI2pefEdtQbNbxEZzVEuKmYJdRbWX0E4nPrga89pa6Vook3CB4JRQ== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-priv new file mode 100644 index 0000000..e464a16 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3WweHXn9YueI21bv +1NJDoOr6sfkoF+N+i7c1HrG7d2ShRANCAARWXvrkECSC0Pyq5TUhXR0O+DwzI/E6 +G8dSpw/BsBQGY64IvGtjQT3Q83IaltXi6FuDO8X5a5v/A61xQxxi4wb7 +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-pub new file mode 100644 index 0000000..c493b18 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVl765BAkgtD8quU1IV0dDvg8MyPx +OhvHUqcPwbAUBmOuCLxrY0E90PNyGpbV4uhbgzvF+Wub/wOtcUMcYuMG+w== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-priv new file mode 100644 index 0000000..a07d791 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWSEa4+KRO7Egdbfv +fedhir19rU5jlZPmdl/sIxiBNVihRANCAATFZWF2fAgOsghZoGfcdqV46RNTQB5D +iZAWh9Xk130ajzpKvSFK8HtQVqk+Idq4TLNpYdLM/VC+ookamG1z4A5w +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-pub new file mode 100644 index 0000000..fe55035 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/3c64462a8b1f951653463df88192c74f8388e18971793d32f21df75b6eb33e19-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExWVhdnwIDrIIWaBn3HaleOkTU0Ae +Q4mQFofV5Nd9Go86Sr0hSvB7UFapPiHauEyzaWHSzP1QvqKJGphtc+AOcA== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-priv new file mode 100644 index 0000000..dbc6dbc --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjnwJZpAOpr7hevOi +8OpJ9xrPBT34vHexhMxrofbDrfGhRANCAASxoTdi/H9lYTymkRisb8B5Qch7iLVL +IRZ37Vx2wIm+8xQ8SjTIGMda1K39XGgGU39ZJ4z2I4lxD9GYbMtClA8Y +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-pub new file mode 100644 index 0000000..4714733 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/6926053f3f7fa76f2b51f5d602afe850d9cabb18ffd763c73d5205df5a3f9bfb-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsaE3Yvx/ZWE8ppEYrG/AeUHIe4i1 +SyEWd+1cdsCJvvMUPEo0yBjHWtSt/VxoBlN/WSeM9iOJcQ/RmGzLQpQPGA== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-priv new file mode 100644 index 0000000..db32c87 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAJhQXYndLtBRv7Sc +NGG7T19NszqmiPbGVdPlfkNnbZehRANCAASjJYD3s/yyq6aWHZKCrXrTJ1307DlZ +VZNpLMSqpAnjMaloMH5Ed0Ch1xBAxTDrMFzz0mYB0YYKckl0Sb3NCc7V +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-pub new file mode 100644 index 0000000..c2b4a1d --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/978c7f2e9314c72cb0c564883dced69bfdffe8457cbd790a3d6f289d56bc0220-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoyWA97P8squmlh2Sgq160ydd9Ow5 +WVWTaSzEqqQJ4zGpaDB+RHdAodcQQMUw6zBc89JmAdGGCnJJdEm9zQnO1Q== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-priv new file mode 100644 index 0000000..aa29eb9 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVknENe69G9Bpzw9V +1LRObfxQRUg3T2+NS70AcgZT3Z2hRANCAASwvvcU9VBWcV37xluwmHfvvB5w97TA +S2YD0mD+8WNTJ11V+t6oCD3cvkgePDwlouZyVAvcy1p+4wHPLyfio7Nq +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-pub new file mode 100644 index 0000000..819c22f --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/9b1be46b5ce3ce11a301282751df96a801f7e0c3601f040f12a0915519cbda16-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsL73FPVQVnFd+8ZbsJh377wecPe0 +wEtmA9Jg/vFjUyddVfreqAg93L5IHjw8JaLmclQL3MtafuMBzy8n4qOzag== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/admin b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/admin new file mode 100644 index 0000000..9be6ecf --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/admin @@ -0,0 +1 @@ +{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"210a472603fb250e838c0e68affc6f04260b40ac8871f96fcf1789e8ae033e89","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIIB8DCCAZegAwIBAgIUPy38KPB78U7H+M4iWBqefDs0DKEwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTgwOTA0MTQxNTAwWhcNMTkwOTA0MTQx\nNTAwWjAQMQ4wDAYDVQQDEwVhZG1pbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA\nBFZe+uQQJILQ/KrlNSFdHQ74PDMj8Tobx1KnD8GwFAZjrgi8a2NBPdDzchqW1eLo\nW4M7xflrm/8DrXFDHGLjBvujbDBqMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8E\nAjAAMB0GA1UdDgQWBBScgfVr0UcRWjGWUIrqGNBVOr/2pDArBgNVHSMEJDAigCAZ\nq2WruwSAfa0S5MCpqqZknnCGjjq9AhejItieR+GmrjAKBggqhkjOPQQDAgNHADBE\nAiB9Zl01J8cWlx2veHMt2vELYeBc2X9oX4t7vkaVK8zdCwIgF86+wM16VUvcgkBk\nUeRsni/BXFdPW1AwXDdcHqmbANg=\n-----END CERTIFICATE-----\n"}}} \ No newline at end of file diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-priv b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-priv new file mode 100644 index 0000000..ab87b87 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVBfyT/v+XQ+4B2Ql ++6hDZFXUrtpviB+yLY3OBj6WS5qhRANCAAQ9PBILCLDwf9sJPIO6wLvplW1PgwMU +mfrovrdcc7ReuZ8LCDM9BuCoWfh48stLh1Bh0PVfm3ZppVuLl090Ifmj +-----END PRIVATE KEY----- diff --git a/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-pub b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-pub new file mode 100644 index 0000000..8a489f6 --- /dev/null +++ b/Chapter12/controller/restapi/features/composer/creds/client-data/admin@zerotoblockchain-network/e0bf1c87fb12b9a7c67fc3f571368cea034e3166974615868c02140aeaa7fc54-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPTwSCwiw8H/bCTyDusC76ZVtT4MD +FJn66L63XHO0XrmfCwgzPQbgqFn4ePLLS4dQYdD1X5t2aaVbi5dPdCH5ow== +-----END PUBLIC KEY----- diff --git a/Chapter12/controller/restapi/features/composer/hlcAdmin.js b/Chapter12/controller/restapi/features/composer/hlcAdmin.js index 8e9e8b9..46b188c 100644 --- a/Chapter12/controller/restapi/features/composer/hlcAdmin.js +++ b/Chapter12/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/Chapter12/controller/restapi/features/composer/hlcClient.js b/Chapter12/controller/restapi/features/composer/hlcClient.js index 7fb32ee..fc7955c 100644 --- a/Chapter12/controller/restapi/features/composer/hlcClient.js +++ b/Chapter12/controller/restapi/features/composer/hlcClient.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 - // ========> Your Code Goes Here <========= - res.send('event registration complete'); - }).catch((error) => { - // if an error is encountered, log the error and send it back to the requestor - // ========> Your Code Goes Here <========= - 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); @@ -446,4 +411,40 @@ function _monitor(_conn, _f_conn, _event) 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/controller/restapi/features/composer/queryBlockChain.js b/Chapter12/controller/restapi/features/composer/queryBlockChain.js index 9b6a396..75b67d3 100644 --- a/Chapter12/controller/restapi/features/composer/queryBlockChain.js +++ b/Chapter12/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/Chapter12/controller/restapi/router.js b/Chapter12/controller/restapi/router.js index ba7b3cc..14883e7 100644 --- a/Chapter12/controller/restapi/router.js +++ b/Chapter12/controller/restapi/router.js @@ -32,12 +32,8 @@ router.get('/fabric/getChainEvents', hlcFabric.getChainEvents); router.get('/fabric/getHistory', hlcAdmin.getHistory); router.post('/setup/autoLoad*', setup.autoLoad); -router.get('/setup/getPort*', setup.getPort); router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents); -router.get('/setup/getAlertPort*', setup.getAlertPort); -router.get('/setup/getFinanceAlertPort*', setup.getFinanceAlertPort); - module.exports = router; let count = 0; /** @@ -77,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); @@ -96,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/Chapter12/index.js b/Chapter12/index.js index 70319d6..cf79cb7 100644 --- a/Chapter12/index.js +++ b/Chapter12/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/Chapter12/network/lib/sample.js b/Chapter12/network/lib/sample.js index 4ce83b2..7a0c946 100644 --- a/Chapter12/network/lib/sample.js +++ b/Chapter12/network/lib/sample.js @@ -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/Chapter13/network/lib/sample.js b/Chapter13/network/lib/sample.js index 4ce83b2..7a0c946 100644 --- a/Chapter13/network/lib/sample.js +++ b/Chapter13/network/lib/sample.js @@ -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(); From b26b3be4fd81125045e3c419b71576ecdf9cdcfb Mon Sep 17 00:00:00 2001 From: Bob Dill Date: Mon, 10 Sep 2018 07:40:22 -0400 Subject: [PATCH 2/4] add wheres my code to chapter 13 --- Chapter13/README.md | 4 ++++ Chapter13/Z2B Wheres my code.pdf | Bin 0 -> 241234 bytes 2 files changed, 4 insertions(+) create mode 100644 Chapter13/Z2B Wheres my code.pdf diff --git a/Chapter13/README.md b/Chapter13/README.md index ffa2eaa..cc3fe36 100644 --- a/Chapter13/README.md +++ b/Chapter13/README.md @@ -2,6 +2,10 @@ [Return to Table of Contents](../README.md) +## c13-database branch + - This branch is to replace the file folder based persistence in for member cards with a database approach + - Potentially required to address Cloud-Foundry based restarts of application, which causes the current folder to be deleted and recreated. + ## (1) network information - Docker and the Kubernetes deploy use different names for the CA and for the channel - Docker diff --git a/Chapter13/Z2B Wheres my code.pdf b/Chapter13/Z2B Wheres my code.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5a62a369c3b85d798931b6605d3eb30624de9573 GIT binary patch literal 241234 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 Date: Thu, 13 Sep 2018 09:26:42 -0400 Subject: [PATCH 3/4] updated C13 to support nodejs restarts. Restart management now uses a Cloudant database to preserve user cards across nodejs restarts --- Chapter13/HTML/admin.html | 1 + Chapter13/HTML/index.html | 172 +++--- Chapter13/HTML/js/z2b-admin.js | 2 + Chapter13/HTML/js/z2b-initiate.js | 2 + Chapter13/README.md | 14 +- Chapter13/controller/env.json | 13 + .../restapi/features/cloudant_utils_web.js | 501 ++++++++++++++++++ .../restapi/features/composer/Z2B_Services.js | 158 +++++- .../restapi/features/composer/autoLoad.js | 349 +++++++----- .../restapi/features/composer/hlcAdmin.js | 35 +- .../features/composer/queryBlockChain.js | 28 +- Chapter13/controller/restapi/router.js | 46 +- Chapter13/index.js | 27 +- 13 files changed, 1094 insertions(+), 254 deletions(-) create mode 100644 Chapter13/controller/restapi/features/cloudant_utils_web.js diff --git a/Chapter13/HTML/admin.html b/Chapter13/HTML/admin.html index 53b3a2d..1c0e9fe 100644 --- a/Chapter13/HTML/admin.html +++ b/Chapter13/HTML/admin.html @@ -1,6 +1,7 @@

    Why All The Changes?

    +

    NodeJS last restarted:

    diff --git a/Chapter13/HTML/index.html b/Chapter13/HTML/index.html index 49183d2..0ac3bef 100644 --- a/Chapter13/HTML/index.html +++ b/Chapter13/HTML/index.html @@ -1,88 +1,90 @@ - - - - - - - - -
    - +
    + +
    +
    +
    +
    +

    +

    +

    NodeJS last restarted:

    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Chapter13/HTML/js/z2b-admin.js b/Chapter13/HTML/js/z2b-admin.js index f3c3ffc..21244f1 100644 --- a/Chapter13/HTML/js/z2b-admin.js +++ b/Chapter13/HTML/js/z2b-admin.js @@ -25,11 +25,13 @@ let _blctr = 0; */ function loadAdminUX () { + let methodName = 'loadAdminUX'; let toLoad = 'admin.html'; $.when($.get(toLoad)).done(function (page) {$('#body').empty(); $('#body').append(page); updatePage('admin'); + $.when($.get('/setup/getLastRestart')).done(function(_res) { $('#lastUpdate').append(_res.timeStamp); }); listMemRegistries(); }); } diff --git a/Chapter13/HTML/js/z2b-initiate.js b/Chapter13/HTML/js/z2b-initiate.js index e5b82d6..526e5e6 100644 --- a/Chapter13/HTML/js/z2b-initiate.js +++ b/Chapter13/HTML/js/z2b-initiate.js @@ -59,6 +59,8 @@ function initPage () console.log('looking for kubernetes at: '+_addr); // goMultiLingual() establishes what languages are available for this web app, populates the header with available languages and sets the default language to US_English goMultiLingual('US_English', 'index'); + // get the timestamp for when the network was last loaded + $.when($.get('/setup/getLastRestart')).done(function(_res) { $('#lastUpdate').append(_res.timeStamp); }) // singleUX loads the members already present in the network memberLoad(); // goChainEvents creates a web socket connection with the server and initiates blockchain event monitoring diff --git a/Chapter13/README.md b/Chapter13/README.md index cc3fe36..78864c4 100644 --- a/Chapter13/README.md +++ b/Chapter13/README.md @@ -5,7 +5,19 @@ ## c13-database branch - This branch is to replace the file folder based persistence in for member cards with a database approach - Potentially required to address Cloud-Foundry based restarts of application, which causes the current folder to be deleted and recreated. - +### Concept flow + - Database is created during admin - load process + - erase existing cards + - if database does not exist || database exists but is empty + - create database + - add record to database when create card complete + - else + - read database + - create card for each entry in database + - write card to .hfc-key-store + - fi +- This requires a manual step to drop the database when cluster is loaded + ## (1) network information - Docker and the Kubernetes deploy use different names for the CA and for the channel - Docker diff --git a/Chapter13/controller/env.json b/Chapter13/controller/env.json index d634cd3..2bcbbd9 100644 --- a/Chapter13/controller/env.json +++ b/Chapter13/controller/env.json @@ -31,6 +31,19 @@ "businessNetwork":"", "enrollmentSecret": "temp" }, + "cloudant": { + "username": "{your cloudant user name}", + "password": "{your cloudant password}", + "host": "{your cloudant host}", + "port": 443, + "url": "{your cloudant url}" + }, + "couchdb": { + "url": "http://localhost:5984/", + "urlBase": "localhost:5984/", + "username": "{your couchdb admin id", + "password": "{your couchdb password}" + }, "keyValStore": "/.composer-credentials", "kube_address":"0.0.0.0" } diff --git a/Chapter13/controller/restapi/features/cloudant_utils_web.js b/Chapter13/controller/restapi/features/cloudant_utils_web.js new file mode 100644 index 0000000..991a77f --- /dev/null +++ b/Chapter13/controller/restapi/features/cloudant_utils_web.js @@ -0,0 +1,501 @@ +/** + * Copyright 2015 IBM Corp. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// cloudant support +var cloudantAuth; +var cloudant_credentials; +var request = require('request'); +var cfenv = require('cfenv'); +var fs = require('fs'); +var jsonObjects = "jsonObjects.json"; +var metaData = {}; +var protocolToUse = "http"; + +function displayObjectProperties(_obj){ for(var property in _obj){ console.log("object property: "+property ); }} +function displayObjectPropertyValues(_obj){ for(var property in _obj){ console.log("object property: "+property+" with value: "+_obj[property] ); }} + +exports.w_authenticate=w_authenticate; +exports.w_create=w_create; +exports.w_drop=w_drop; +exports.w_insert=w_insert; +exports.w_update=w_update; +exports.w_delete=w_deleteItem; +exports.w_listAllDatabases=w_listAllDatabases; +exports.w_listAllDocuments=w_listAllDocuments; +exports.w_capabilities=w_capabilities; +exports.w_getMetadata=w_getMetadata; +exports.w_buildEntryPage=w_buildEntryPage; +exports.w_getDocs=w_getDocs; +exports.w_getOne=w_getOne; +exports.w_select=w_select; +exports.w_select2=w_select2; +exports.w_selectMulti=w_selectMulti; +exports.w_createBackup=w_createBackup; +exports.w_restoreTable=w_restoreTable; +exports.w_getBackups=w_getBackups; + +function getDBPath() +{ + if (cfenv.getAppEnv().isLocal) + { console.log("using local database"); + cloudant_credentials = require('../../env.json').couchdb; + protocolToUse = "http"; + var url = "http://"+cloudant_credentials.username+":"+cloudant_credentials.password+"@"+cloudant_credentials.urlBase; } + else + {console.log("using host database"); + cloudant_credentials = require('../../env.json').cloudant; + protocolToUse = "https"; + // var url = cloudant_credentials.url+"/_session"; + var url = cloudant_credentials.url+"/"; } + // console.log("getDBPath url is: "+url); + return url; +} + +function w_authenticate(req, res, next) +{ + var params = "name="+cloudant_credentials.username+"&password="+cloudant_credentials.password; + var method = "POST"; + var url = getDBPath()+"/_session"; + request({ url: url, method: method, headers: { "content-type": "application/x-www-form-urlencoded" }, form: params }, + function(error, response, body) { + if (error) { console.log("authenticate error: "+error); res.send({"error": error});} + else { cloudantAuth=response["headers"]["set-cookie"]; console.log("authentication succeeded: "+response); res.send(response);} + }); + +} +function w_create(req, res, next) +{ + // create a new database + var _name = req.body.name; + var method = "PUT"; + var url = getDBPath()+_name; + console.log("w_create: db=",_name); + request({ + url: url, + headers: {"set-cookie": cloudantAuth, 'Accept': '/'}, + method: method, + }, function(error, response, body) { + var _body = JSON.parse(body); + if ((error) || ((typeof(_body.error) != 'undefined') && (_body.error != null))) + { if ((typeof(_body.error) != 'undefined') && (_body.error != null)) { res.send({"error": _body.error});} + else {res.send({"error": error}); } + } else { res.send({"success": _body})} + }); +} +function w_drop(req, res, next) +{ + // drop a database + var _name = req.body.name; + var method = "DELETE"; + var url = getDBPath()+_name; + var _res = res; + request( { url: url, method: method }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} + +function w_insert(req, res, next) +{ + // iCloudant automatically creates a unique index for each entry. + var _name = req.body.name; + var _object = req.body.content; + delete _object._rev; + var _res = res; + var method = "POST"; + var url = getDBPath()+_name; + delete _object._rev; + // console.log("w_insert url is "+url); + console.log ("w_insert for: "+ _name+ " with content: "+JSON.stringify(_object)); + request( { url: url, json: _object, method: method }, + function(error, response, body) { + if (error){console.log("error: "+ error); + _res.send({"error": error});} + else { console.log("success: "+ JSON.stringify(body)); + _res.send({"success": body}); + } + }); +} +function w_update(req, res, next) +{ + // insert JSON _object into database _name + var updateContent = req.body.content; + var _name = req.body.name; + var method = "POST"; + var object = {}; object.name = req.body.name; object.oid = req.body.oid; + var _res = res; + var url = protocolToUse+'://'+req.headers.host+'/db/getOne'; + request( { url: url, json: object, method: method }, + function(error, response, body) + { if (error){console.log("error: "+ error); _res.send({"error": error});} + else + {var orig = JSON.parse(body.success); + for(prop in updateContent){(function(_idx, _array){orig[_idx]=_array[_idx]; })(prop, updateContent)} + var method = "PUT"; + var url = getDBPath()+_name+"/"+object.oid; + console.log("update path is: "+_name+"/"+object.oid); + request( { url: url, json: orig, method: method }, + function(error, response, body) { if (error){console.log("error: "+ error); _res.send({"error": error});} + else { console.log("success: "+ JSON.stringify(body)); _res.send({"success": body});} + }); + } + }); +} + +function w_getOne(req, res, next) +{ + // select objects from database _name specified by selection criteria _selector + var _name = req.body.name; + var _oid = req.body.oid; + var method = "GET"; + var _res = res; + var url = getDBPath()+_name+"/"+_oid; + console.log("w_getOne: "+_name+"/"+_oid); + request( { url: url, method: method }, + function(error, response, body) { + if (error){ + _res.send({"error": body});} + else { + _res.send({"success": body});} + }); +} + +function w_select(req, res, next) +{ + // select objects from database _name specified by selection criteria _selector + console.log("w_select entered"); + var _name = req.body.name; + var key = req.body.key; + var view = req.body.view; + var method = "GET"; + var object = {"selector" : {"idRubric" : key}}; + var _res = res; + console.log("name: "+_name+" key: "+key+" view: "+view); + var select = ((typeof(key) == "undefined") || (key == "")) ? '/_design/views/_view/'+view : '/_design/views/_view/'+view+'?key="'+key+'"'; + console.log("select is ", select); + var url = getDBPath()+_name+ select; + //console.log(url); + request( { url: url, method: method, json: object, headers: {"Content-Type": "application/json"} }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} + +function w_select2(req, res, next) +{ + // select objects from database _name specified by selection criteria _selector + console.log("w_select2 entered"); + var _name = req.body.name; + var key = req.body.key; + var view = req.body.view; + var method = "POST"; + var object = {"selector" : {"_id" : { "$gt" : 0}, "type":{"$in":key}}}; + var _res = res; + console.log("name: "+_name+" key: "+key+" view: "+view); + console.log("object: "+JSON.stringify(object)); + // var select = ((typeof(key) == "undefined") || (key == "")) ? '/_design/views/_view/'+view : '/_design/views/_view/'+view+'?key="'+key+'"'; + var select = ""; + select = "/_find"; + console.log("select is ", select); + var url = getDBPath()+_name+ select; + console.log(url); + request( { url: url, method: method, json: object, headers: {"Content-Type": "application/json"} }, + function(error, response, body) { + if (error){ _res.send({"error": body});} + else { _res.send({"success": body } );} + }); +} + +function w_selectMulti(req, res, next) +{ + // select objects from database _name specified by selection criteria _selector + console.log("w_selectMulti entered"); + var _name = req.body.name; + var keyArray = req.body.key; + var keys = ""; + for (each in keyArray){(function(_idx, _array){keys += (_idx==0)?'["'+_array[_idx]+'"': ', "'+_array[_idx]+'"'})(each, keyArray);} + keys +="]"; + var view = req.body.view; + var method = "GET"; + var object = {"keys": keyArray}; + var _res = res; + var select = '/_design/views/_view/'+view+"?key="+keys ; + console.log("object is ", object); + console.log("select is ", select); + var url = getDBPath()+_name+ select; + // console.log(url); + request( { url: url, method: method, json: object, headers: {"Content-Type": "application/json"} }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} +function w_deleteItem(req, res, next) +{ + // delete object specified by _oid in database _name /$DATABASE/$DOCUMENT_ID?rev=$REV + //_name, _oid, _rev, cbfn + var _name = req.body.name; + var _oid = req.body.oid; + var _rev = req.body.rev; + var _res = res; + var method = "DELETE"; + var url = getDBPath()+_name+"/"+oid+"?rev="+_rev; + request( { url: url, method: method }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} +function w_getDocs(req, res, next) +{ + var method = "GET"; + var url = getDBPath()+req.body.name+"/_all_docs?include_docs=true"; + console.log("w_getDocs path: ", url); + var _res = res; + request( { url: url, method: method }, + function(error, response, body) { if (error){console.log("w_getDocs error: ", body); _res.send({"error": body});} else { _res.send({"success": body});} + }); +} +function w_listAllDocuments(req, res, next) +{ + // list all documents in database _name + var method = "GET"; + var url = getDBPath()+"/_all_docs"; + var _res = res; + request( { url: url, method: method }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} + +function w_listAllDatabases(req, res, next) +{ + // list all databases + var method = "GET"; + var url = getDBPath()+"/_all_dbs"; + var _res = res; + request( { url: url, method: method }, + function(error, response, body) { if (error){ _res.send({"error": body});} else { _res.send({"success": body});} + }); +} + +function w_capabilities(req, res, next) +{ + var _c = {}; + _c.authenticate = "function (): uses credentials in env.json file to authenticate to cloudant server"; + _c.create = "function (_name): create a new database"; + _c.drop = "function (_name): drop a database"; + _c.insert = "function (_name, _object): insert JSON _object into database _name"; + _c.update = "function (_name, _oid, _object): update JSON object specified by object _oid in database _name with new object _object"; + _c.select = "function (_name, _selector): select objects from database _name specified by selection criteria _selector"; + _c.delete = "function (_name, _oid): delete object specified by _oid in database _name"; + _c.listAllDatabases = "function (): list all databases I can access"; + _c.listAllDocuments = "function (_name): list all documents in database _name"; + _c.capabilities = "return this object with descriptors."; + _c.getMetadata = "return the jsonObjectsPretty.json file."; + res.send(_c); +} +function w_getMetadata(req, res, next) +{ + var file = jsonObjects; + var toSave = process.cwd()+"/"+file; + var contents = fs.readFileSync(toSave, 'utf8'); + metaData = JSON.parse(contents); + res.send(contents); +} +function retrieveMetaData(_name) +{ + for (each in metaData.dataModel) + {if (metaData.dataModel[each].name == _name){return(metaData.dataModel[each]);}} + return({"failed": _name+" not found"}); +} +function w_buildEntryPage(req, res, next) +{ + var pageMeta = retrieveMetaData(req.body.name); + if (typeof(pageMeta.failed) != "undefined"){return(pageMeta)} + else + { console.log(pageMeta); + var _str = "

    "+pageMeta.name+"

    "+pageMeta.description+"
    "; + var _type = req.body.type; + var _fields = ""; + for (each in pageMeta.elements) + {(function(_idx, _array){_str+=""; + })(each, pageMeta.elements)} + _str += "
    "+_array[_idx].name+""; + if (_idx > 0){_fields +=",";} + switch (_array[_idx].type) + { + case 'int': + _str += ""; + _fields += _array[_idx].name; + break; + case 'string': + _str += ""; + _fields += _array[_idx].name; + break; + case 'boolean': + _str += "True"; + _str += "False"; + _fields += _array[_idx].name+"_true,"; _fields += _array[_idx].name+"_false"; + break; + case 'email': + _str += ""; + _fields += _array[_idx].name; + break; + case 'timestamp': + // needs routine to get timestamp + var _tStamp = new Date(Date.now()); + _str += "
    "+_tStamp.toISOString()+"
    "; + _fields += _array[_idx].name; + break; + case 'JSON': + _str += ""; + _fields += _array[_idx].name; + break; + case 'index': + _str += "
    Defined when inserted
    "; + _fields += _array[_idx].name; + break; + default: + _str += "
    "+_array[_idx].type+" is not a recognized type
    "; + _fields += _array[_idx].name; + break; + } + _str +="
    _rev"+"
    Defined when inserted
    "; + _fields += ",_rev"; + console.log(_fields); + _str += "Insert New Document
    \n"; + _str += " \n"; + console.log(_str); + res.send(_str); + } +} +function w_createBackup(req, res, next) +{ + var _object = {}; + _object.name = (typeof(req.body.name)=="undefined") ? "" : req.body.name; + var _res = res; + var method = "POST"; + var url = protocolToUse+'://'+req.headers.host+'/db/getDocs'; + console.log("w_createBackup object: ", _object); + // console.log("w_createBackup path: ", url); + request( { url: url, json: _object, method: method }, + function(error, response, body) + { console.log("request completed for table: "+_object.name); + if (error) + {console.log("error: "+ error); _res.send({"error": error});} + else + { +// console.log("success: response ", response); + console.log("success: body ", body.success); + var fileName = process.cwd()+"/HTML/backups/"+'Backup_'; + fileName += (_object.name == "") ? "allFiles" : _object.name; + fileName +="_"+getTimeStamp()+".json"; + console.log("creating backup at: ", fileName); + var rows = JSON.parse(body.success).rows; + var _views = '"views": ['; var viewNum = 0; + var _str = "["; + for (each in rows) + {(function(_idx, _array) + {if(_array[_idx].doc._id != '_design/views') + { if(_idx>0){_str+=", ";} + _str+=JSON.stringify(_array[_idx].doc); console.log(JSON.stringify(_array[_idx].doc)); + } + else { + if(viewNum>0){_views+=", ";} _views += '{ "_id": "_design/views", "views":' +JSON.stringify(_array[_idx].doc.views)+'}'; + } + })(each, rows)} + _str+="]"; _views += "]"; + fs.writeFileSync(fileName, '{"table" : "'+_object.name+'", "date": "'+getTimeStamp()+'", "rows": '+_str+', '+_views+'}', 'utf8'); + _res.send(JSON.parse(body.success).rows);} + }); +} +function w_restoreTable(req, res, next) +{ + console.log("w_restoreTable entered for "+req.body.name); + var fileName = process.cwd()+"/HTML/backups/"+req.body.name; + var restoreObject =JSON.parse(fs.readFileSync(fileName)); + console.log("starting restore for table: "+restoreObject.table+" created on: "+restoreObject.date); + console.log("restore data: "+JSON.stringify(restoreObject.rows)); + // drop existing table + var object = {}; object.name = restoreObject.table ; + var _res = res; + var method = "POST"; + var url = protocolToUse+'://'+req.headers.host+'/db/dropTable'; + request( { url: url, json: object, method: method }, + function(error, response, body) + { console.log("request to drop table: "+object.name+" completed"); + if (error){console.log("error: "+ error); _res.send({"error": error});} + else + {console.log("drop table: result: "+response); + // create new table + var url = protocolToUse+'://'+req.headers.host+'/db/createTable'; + request( { url: url, json: object, method: method }, + function(error, response, body) + { console.log("request to create table: "+object.name+" completed"); + if (error){console.log("error: "+ error); _res.send({"error": error});} + else + {console.log("create table: result: "+response); + var url = protocolToUse+'://'+req.headers.host+'/db/insert'; + for (each in restoreObject.rows) + { console.log("["+each+"] restoring row: "+JSON.stringify(restoreObject.rows[each])); + var _object = object; _object.content = restoreObject.rows[each]; + if(process.env.SNDEMAIL && process.env.SNDEMAIL!== '') { + _object.content.email = process.env.SNDEMAIL; + } + console.log("Restoring with : ", JSON.stringify(_object, null , 4)); + request( { url: url, json: _object, method: method }, + function(error, response, body) { console.log("row restored: "+response);}); + } + for (each in restoreObject.views) + { console.log("["+each+"] restoring row: "+JSON.stringify(restoreObject.views[each])); + var _object = {}; _object.name = object.name; _object.content = restoreObject.views[each]; + if(process.env.SNDEMAIL && process.env.SNDEMAIL!== '') { + _object.content.email = process.env.SNDEMAIL; + } + + console.log("Restoring with : ", JSON.stringify(_object, null , 4)); + request( { url: url, json: _object, method: method }, + function(error, response, body) { console.log("view restored: "+response);}); + } + } + }); + } + }); +} +function w_getBackups(req, res, next) +{ + var loc = process.cwd()+"/HTML/backups/"; + var files = fs.readdirSync(loc); + console.log(files); + res.send(files); +} +function getTimeStamp() {return(new Date(Date.now()).toISOString().replace(/:/g, '.'));} diff --git a/Chapter13/controller/restapi/features/composer/Z2B_Services.js b/Chapter13/controller/restapi/features/composer/Z2B_Services.js index 07004e6..eab9aa4 100644 --- a/Chapter13/controller/restapi/features/composer/Z2B_Services.js +++ b/Chapter13/controller/restapi/features/composer/Z2B_Services.js @@ -16,6 +16,7 @@ let fs = require('fs'); let path = require('path'); const sleep = require('sleep'); +var request = require('request'); // const ws = require('websocket'); // const http = require('http'); @@ -26,7 +27,18 @@ const cfenv = require('cfenv'); const appEnv = cfenv.getAppEnv(); const util = require('./Z2B_Utilities'); app.set('port', appEnv.port); - +let protocolToUse; +if (cfenv.getAppEnv().isLocal) +{ + console.log(" Z2B is running locally"); + protocolToUse = "http"; +} +else +{ + console.log("Z2B is running in cloud"); + protocolToUse = "https"; +} +const restartDate = new Date().toISOString(); /** @@ -125,10 +137,10 @@ let Z2Blockchain = { */ loadTransaction: function (_con, _item, _id, businessNetworkConnection) { - let method = 'loadTransaction'; + let methodName = 'loadTransaction'; return businessNetworkConnection.submitTransaction(_item) .then(() => { - console.log(method+': order '+_id+' successfully added '); + console.log(methodName+': order '+_id+' successfully added '); this.send(_con, 'Message', 'Order '+_id+' successfully added'); }) .catch((error) => { @@ -150,7 +162,7 @@ let Z2Blockchain = { */ addOrder: function (_con, _order, _registry, _createNew, _bnc) { - let method = 'addOrder'; + let methodName = 'addOrder'; return _registry.add(_order) .then(() => { this.loadTransaction(_con, _createNew, _order.orderNumber, _bnc); @@ -160,7 +172,7 @@ let Z2Blockchain = { {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);} + else {console.log(methodName+' error with assetRegistry.add', error);} }); }, @@ -174,18 +186,146 @@ let Z2Blockchain = { */ bindIdentity: function (_con, _id, _cert, _bnc) { - let method = 'bindIdentity'; - console.log(method+' retrying bindIdentity for: '+_id); + let methodName = 'bindIdentity'; + console.log(methodName+' retrying bindIdentity for: '+_id); return _bnc.bindIdentity(_id, _cert) .then(() => { - console.log(method+' Succeeded for: '+_id); + console.log(methodName+' 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);} + else {console.log(methodName+' error with _bnc.bindIdentity(_id, _cert) for: '+_id+' with error: ', error);} + }); + }, +/** + * saves the member table with ids and secrets + * @param {string} _dbName - string, name of database to access + * @param {String} _key - key field for this record + * @param {String} _host - host url + */ + getThisMember: function (_locals, _dbName, _key, _host) + { + let methodName = 'getThisMember'; + console.log(methodName+' entered for '+_dbName+' with key: '+_key); + return new Promise(function(resolve, reject) + { + let options = {}; + options.name = _dbName; + options.oid = _key; + var url = protocolToUse+'://'+_host+'/db/getOne'; + var method = "POST"; + console.log(methodName+' requesting from url: '+url+' with options: ', options); + request( { url: url, json: options, method: method }, + function(error, response, body) + { + if (typeof(body.success) != 'undefined') + {console.log(methodName+" "+_key+" retrieved successfully"); resolve(body.success);} + else + {console.log(methodName+' error body: ', body); reject(body);} + }); + }); +}, +/** + * saves the member table with ids and secrets + * @param {string} _dbName - string, name of database to access + * @param {JSON} _item - data to store in dbName + * @param {String} _key - key field for this record + * @param {String} _host - host url + */ +saveToDB: function (_dbName, _item, _key, _host) +{ + let methodName = 'saveToDB'; + let _this = this; + _item.lastUpdated=restartDate; + let options = {}; + options.name = _dbName; + options.content = _item; + options.content._id = _key; + var url = protocolToUse+'://'+_host+'/db/insert'; + var method = "POST"; + request( { url: url, json: options, method: method }, + function(error, response, body) + { + if ((typeof(body.success) != 'undefined') && (body.success.ok == 'true')) + {console.log(body.id+" stored in db successfully");} + else + { + // { success: { error: 'conflict', reason: 'Document update conflict.' } } + // this occurs when the network has been reloaded and the database still has the previous + // version information + if ((typeof(body.success.error) != 'undefined') && (body.success.error == 'conflict')) + { + _this.updateDB (_dbName, _item, _key, _host) + } + else {console.log(methodName+' error body: ', body);} + } + }); +}, +/** + * saves the member table with ids and secrets + * @param {string} _dbName - string, name of database to access + * @param {JSON} _item - data to store in dbName + * @param {String} _key - key field for this record + * @param {String} _host - host url + */ + updateDB: function (_dbName, _item, _key, _host) + { + let methodName = 'updateDB'; + _item.lastUpdated=restartDate; + let options = {}; + options.name = _dbName; + options.content = _item; + options.oid = _key; + var url = protocolToUse+'://'+_host+'/db/update'; + var method = "POST"; + request( { url: url, json: options, method: method }, + function(error, response, body) + { + // updateDB error body: { success: { ok: true, id: 'abdul@cognitiveadvantageinc.com', rev: '2-b42cdc8bfa78e8e62d11947e4357a0d4' } } + if ((typeof(body.success) != 'undefined') && (body.success.ok == true)) + {console.log(body.success.id+" updated in db successfully");} + else + { + console.log(methodName+' error body: ', body); + } + }); + }, +/** + * saves the member table with ids and secrets + * @param {string} _dbName - string, name of database to create if missing + * @param {String} _host - host url + */ + connectToDB: function (_dbName, _host) + { + let methodName = 'connectToDB'; + return new Promise(function(resolve, reject) + { + let options = {}; + options.name = _dbName; + var url = protocolToUse+'://'+_host+'/db/createTable'; + var method = "POST"; + request( { url: url, json: options, method: method }, + function(error, response, body) + { + if ((typeof(body.success) != 'undefined') && (body.success.ok == 'true')) + {console.log(_dbName+" created successfully"); resolve("success");} + else + { + if ((typeof(body.error) != 'undefined') && (body.error == 'file_exists')) + { + console.log(_dbName+' database already exists'); + resolve("success"); + } + else + { + console.log(methodName+' error is: ', error); + reject(error); + } + } + }); }); }, diff --git a/Chapter13/controller/restapi/features/composer/autoLoad.js b/Chapter13/controller/restapi/features/composer/autoLoad.js index e8d1c40..5c4fb38 100644 --- a/Chapter13/controller/restapi/features/composer/autoLoad.js +++ b/Chapter13/controller/restapi/features/composer/autoLoad.js @@ -26,23 +26,46 @@ const fs = require('fs'); const path = require('path'); const _home = require('os').homedir(); const hlc_idCard = require('composer-common').IdCard; +const request = require('request'); const AdminConnection = require('composer-admin').AdminConnection; const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; const financeCoID = 'easymoney@easymoneyinc.com'; - +const cfenv = require('cfenv'); const svc = require('./Z2B_Services'); const config = require('../../../env.json'); -const admin_connection = require('../../../connection.json'); +// 9/12/18 change to read connection.json from PeerAdmin@hlfv1 card +const admin_connection = require('./creds/cards/PeerAdmin@hlfv1/connection.json'); admin_connection.keyValStore = _home+config.keyValStore; - +const memberDB = 'z2b_members'; +const homedir = require('os').homedir(); +const CLIENT_DATA = 'client-data' +const basePath = homedir+'/.composer/'+CLIENT_DATA+'/'; +let protocolToUse; +if (cfenv.getAppEnv().isLocal) + { protocolToUse = "http"; } +else + { protocolToUse = "https"; } /** * 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(); +/** + * getLastRestart retrieves a timestamp for the last time the system was restarted + * @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 + */ +exports.getLastRestart = function( req, res, next) +{ + let methodName = 'getLastRestart'; + const _id = 'noop@dummyProvider'; + svc.getThisMember(req.app.locals, memberDB, _id, req.headers.host) + .then((_res) => {res.send({result: 'success', timeStamp: JSON.parse(_res).lastUpdated}); }); +} /** * autoLoad reads the memberList.json file from the Startup folder and adds members, * executes the identity process, and then loads orders @@ -54,156 +77,216 @@ let memberTable = new Array(); * @function */ exports.autoLoad = function(req, res, next) { - // get the autoload file - let newFile = path.join(path.dirname(require.main.filename),'startup','memberList.json'); - let startupFile = JSON.parse(fs.readFileSync(newFile)); - // connect to the network - let businessNetworkConnection; - let factory; let participant; -// 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 - adminConnection.connect(config.composer.adminCard) - .then(() => { - // a businessNetworkConnection is required to add members - businessNetworkConnection = new BusinessNetworkConnection(); + let methodName = 'autoLoad'; + // connect to the member table + svc.connectToDB(memberDB, req.headers.host) + .then((_conn_res) => { + // get the autoload file + let newFile = path.join(path.dirname(require.main.filename),'startup','memberList.json'); + let startupFile = JSON.parse(fs.readFileSync(newFile)); + // connect to the network + let businessNetworkConnection; + let factory; let participant; + // svc.createMessageSocket(); + // socketAddr = svc.m_socketAddr; + let adminConnection = new AdminConnection(); // connection prior to V0.15 - // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW) + // adminConnection.connect(config.composer.connectionProfile, config.composer.adminID, config.composer.adminPW) // connection in v0.15 - return businessNetworkConnection.connect(config.composer.adminCard) + adminConnection.connect(config.composer.adminCard) .then(() => { - // a factory is required to build the member object - factory = businessNetworkConnection.getBusinessNetwork().getFactory(); - //iterate through the list of members in the memberList.json file and - // first add the member to the network, then create an identity for - // them. This generates the memberList.txt file later used for - // retrieving member secrets. - for (let each in startupFile.members) - {(function(_idx, _arr) - { - // the participant registry is where member information is first stored - // there are individual registries for each type of participant, or member. - // In our case, that is Buyer, Seller, Provider, Shipper, FinanceCo - return businessNetworkConnection.getParticipantRegistry(config.composer.NS+'.'+_arr[_idx].type) - .then(function(participantRegistry){ - 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.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); - participant.companyName = _arr[_idx].companyName; - participantRegistry.add(participant) - .then(() => { - console.log('['+_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. - // V0.14 - // return businessNetworkConnection.issueIdentity(config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id, _arr[_idx].pw) - // V0.15 - console.log('issuing identity for: '+config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id); - return businessNetworkConnection.issueIdentity(config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id, _arr[_idx].id) - .then((result) => { - console.log('_arr[_idx].id: '+_arr[_idx].id); - console.log('result.userID: '+result.userID); - let _mem = _arr[_idx]; - _mem.secret = result.userSecret; - _mem.userID = result.userID; - memberTable.push(_mem); - // svc.saveMemberTable(memberTable); + // a businessNetworkConnection is required to add members + businessNetworkConnection = new BusinessNetworkConnection(); + // connection prior to V0.15 + // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW) + // connection in v0.15 + return businessNetworkConnection.connect(config.composer.adminCard) + .then(() => { + // a factory is required to build the member object + factory = businessNetworkConnection.getBusinessNetwork().getFactory(); + //iterate through the list of members in the memberList.json file and + // first add the member to the network, then create an identity for + // them. This generates the memberList.txt file later used for + // retrieving member secrets. + for (let each in startupFile.members) + {(function(_idx, _arr) + { + // the participant registry is where member information is first stored + // there are individual registries for each type of participant, or member. + // In our case, that is Buyer, Seller, Provider, Shipper, FinanceCo + return businessNetworkConnection.getParticipantRegistry(config.composer.NS+'.'+_arr[_idx].type) + .then(function(participantRegistry){ + return participantRegistry.get(_arr[_idx].id) + .then((_res) => { + svc.getThisMember(req.app.locals, memberDB, _arr[_idx].id, req.headers.host) + .then((_res) => { + console.log(methodName+' svc.getThisMember('+_arr[_idx].id+') _res: ', _res); + let member = JSON.parse(_res); + let options = {}; + options.registry = member.type; let _meta = {}; for (each in config.composer.metaData) {(function(_idx, _obj) {_meta[_idx] = _obj[_idx]; })(each, config.composer.metaData); } _meta.businessNetwork = config.composer.network; - _meta.userName = result.userID; - _meta.enrollmentSecret = result.userSecret; + _meta.userName = member.id; + _meta.enrollmentSecret = member.secret; + console.log(methodName+' svc.getThisMember('+_arr[_idx].id+') _meta: ', _meta); let tempCard = new hlc_idCard(_meta, admin_connection); - return adminConnection.importCard(result.userID, tempCard) + return adminConnection.importCard(member.id, tempCard) + .then(() => { + let currentMemberPath = path.join(basePath,member.id); + fs.mkdirSync(currentMemberPath); + for (let i=0; i<3; i++) + {(function(_idx) + {console.log(methodName+' looping on ['+_idx+'] is: '+member[CLIENT_DATA+_idx].name); + let _path = path.join(basePath,member.id,member[CLIENT_DATA+_idx].name); + fs.writeFileSync(_path, member[CLIENT_DATA+_idx].file); + })(i) + } + }) + .catch((error) => {console.log(methodName+' adminConnection.importCard for '+member.id+' failed with error: ', error);}) + }) + .catch((_error) => { + console.log(methodName+' error: ',_error); + }); + }) + .catch((error) => { + participant = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id); + participant.companyName = _arr[_idx].companyName; + participantRegistry.add(participant) + .then(() => { + console.log('['+_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. + // V0.14 + // return businessNetworkConnection.issueIdentity(config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id, _arr[_idx].pw) + // V0.15 + console.log('issuing identity for: '+config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id); + return businessNetworkConnection.issueIdentity(config.composer.NS+'.'+_arr[_idx].type+'#'+_arr[_idx].id, _arr[_idx].id) + .then((result) => { + console.log('_arr[_idx].id: '+_arr[_idx].id); + console.log('result.userID: '+result.userID); + let _mem = _arr[_idx]; + _mem.secret = result.userSecret; + _mem.userID = result.userID; + memberTable.push(_mem); + //svc.saveToDB(memberDB, _mem, result.userID, req.headers.host); + //svc.saveMemberTable(memberTable); + let _meta = {}; + for (each in config.composer.metaData) + {(function(_idx, _obj) {_meta[_idx] = _obj[_idx]; })(each, config.composer.metaData); } + _meta.businessNetwork = config.composer.network; + _meta.userName = result.userID; + _meta.enrollmentSecret = result.userSecret; + let tempCard = new hlc_idCard(_meta, admin_connection); + return adminConnection.importCard(result.userID, tempCard) .then ((_res) => { - if (_res) {console.log('card updated');} else {console.log('card imported');} + if (_res) + {console.log(methodName+' '+result.userID+' card updated');} + else {console.log(methodName+' '+result.userID+' card imported');} + let bnc = new BusinessNetworkConnection(); + return bnc.connect(_arr[_idx].id) + .then(() => { + console.log(methodName+' businessNetworkConnection.connect() succeeded'); + return bnc.ping() + .then((_msg) => { + console.log(methodName+' businessNetworkConnection.ping() succeeded'); + // retrieve CLIENT_DATA info and preserve it + let currentMemberPath = path.join(basePath,result.userID); + let fileList = fs.readdirSync(currentMemberPath); + for (each in fileList) + {(function(_each, _files) + {console.log(methodName+' looping on _files['+_each+'] is: '+_files[_each]); + let _path = path.join(basePath,result.userID,_files[_each]); + let _file=fs.readFileSync(_path, 'utf8'); + _mem[CLIENT_DATA+_each]={'name': _files[_each], 'file': _file};})(each, fileList) + } + svc.saveToDB(memberDB, _mem, result.userID, req.headers.host); + }) + .catch((error) => { console.log(methodName+' businessNetworkConnection.ping() failed. error: ',error); bnc.disconnect(); }); + }) + .catch((error) => { console.log(methodName+' businessNetworkConnection.connect() failed. error: ',error); bnc.disconnect(); }); }) .catch((error) => { console.error('adminConnection.importCard failed. ',error.message); }); + }) + .catch((error) => { + console.error('create id for '+_arr[_idx].id+'failed. ',error.message); + }); }) - .catch((error) => { - console.error('create id for '+_arr[_idx].id+'failed. ',error.message); - }); - }) - .catch((error) => {console.log(_arr[_idx].companyName+' add failed',error.message);}); - }); - }) - .catch((error) => {console.log('error with getParticipantRegistry', error.message);}); - })(each, startupFile.members); - } - // iterate through the order objects in the memberList.json file. - for (let each in startupFile.items){(function(_idx, _arr){itemTable.push(_arr[_idx]);})(each, startupFile.items);} - svc.saveItemTable(itemTable); - for (let each in startupFile.assets) - {(function(_idx, _arr) - { - // each type of asset, like each member, gets it's own registry. Our application - // has only one type of asset: 'Order' - return businessNetworkConnection.getAssetRegistry(config.composer.NS+'.'+_arr[_idx].type) - .then((assetRegistry) => { - 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.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type); + .catch((error) => {console.log(_arr[_idx].companyName+' add failed',error.message);}); + }); }) - .catch((error) => { - // first, an Order Object is created - let order = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id); - order = svc.createOrderTemplate(order); - let _tmp = svc.addItems(_arr[_idx], itemTable); - order.items = _tmp.items; - order.amount = _tmp.amount; - order.orderNumber = _arr[_idx].id; - // then the buy transaction is created - 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@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); - createNew.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer); - createNew.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller); - createNew.amount = order.amount; - // then the order is added to the asset registry. - return assetRegistry.add(order) - .then(() => { - // then a createOrder transaction is processed which uses the chaincode - // establish the order with it's initial transaction state. - svc.loadTransaction(req.app.locals, createNew, order.orderNumber, businessNetworkConnection); + .catch((error) => {console.log('error with getParticipantRegistry', error.message);}); + })(each, startupFile.members); + } + // iterate through the order objects in the memberList.json file. + for (let each in startupFile.items){(function(_idx, _arr){itemTable.push(_arr[_idx]);})(each, startupFile.items);} + svc.saveItemTable(itemTable); + for (let each in startupFile.assets) + {(function(_idx, _arr) + { + // each type of asset, like each member, gets it's own registry. Our application + // has only one type of asset: 'Order' + return businessNetworkConnection.getAssetRegistry(config.composer.NS+'.'+_arr[_idx].type) + .then((assetRegistry) => { + 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.send(req.app.locals, 'Message', '['+_idx+'] order with id: '+_arr[_idx].id+' already exists in Registry '+config.composer.NS+'.'+_arr[_idx].type); }) .catch((error) => { - // in the development environment, because of how timing is set up, it is normal to - // encounter the MVCC_READ_CONFLICT error. This is a database timing error, not a - // 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(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection); - } - else {console.log('error with assetRegistry.add', error.message);} + // first, an Order Object is created + let order = factory.newResource(config.composer.NS, _arr[_idx].type, _arr[_idx].id); + order = svc.createOrderTemplate(order); + let _tmp = svc.addItems(_arr[_idx], itemTable); + order.items = _tmp.items; + order.amount = _tmp.amount; + order.orderNumber = _arr[_idx].id; + // then the buy transaction is created + 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@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); + createNew.buyer = factory.newRelationship(config.composer.NS, 'Buyer', _arr[_idx].buyer); + createNew.seller = factory.newRelationship(config.composer.NS, 'Seller', _arr[_idx].seller); + createNew.amount = order.amount; + // then the order is added to the asset registry. + return assetRegistry.add(order) + .then(() => { + // then a createOrder transaction is processed which uses the chaincode + // establish the order with it's initial transaction state. + 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 + // encounter the MVCC_READ_CONFLICT error. This is a database timing error, not a + // 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(req.app.locals, order, assetRegistry, createNew, businessNetworkConnection); + } + else {console.log('error with assetRegistry.add', error.message);} + }); }); - }); - }) - .catch((error) => {console.log('error with getParticipantRegistry', error.message);}); - })(each, startupFile.assets); - } + }) + .catch((error) => {console.log('error with getParticipantRegistry', error.message);}); + })(each, startupFile.assets); + } + }) + .catch((error) => {console.log('error with business network Connect', error.message);}); }) - .catch((error) => {console.log('error with business network Connect', error.message);}); + .catch((error) => {console.log('error with adminConnect', error.message);}); + res.send({'result': 'Success'}); }) - .catch((error) => {console.log('error with adminConnect', error.message);}); - res.send({'result': 'Success'}); + .catch((error) => {console.log(methodName+' svc.connectToDB('+memberDB+', req.headers.host); failed with error: ', error);}); }; /** diff --git a/Chapter13/controller/restapi/features/composer/hlcAdmin.js b/Chapter13/controller/restapi/features/composer/hlcAdmin.js index 5011f11..baef8a8 100644 --- a/Chapter13/controller/restapi/features/composer/hlcAdmin.js +++ b/Chapter13/controller/restapi/features/composer/hlcAdmin.js @@ -24,7 +24,9 @@ const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefi const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; const config = require('../../../env.json'); const NS = 'org.acme.Z2BTestNetwork'; -const admin_connection = require('../../../connection.json'); +// 9/12/18 change to read connection.json from PeerAdmin@hlfv1 card +const admin_connection = require('./creds/cards/PeerAdmin@hlfv1/connection.json'); +// const admin_connection = require('../../../connection.json'); admin_connection.keyValStore = _home+config.keyValStore; /** @@ -422,6 +424,8 @@ exports.checkCard = function(req, res, next) { * @function */ exports.createCard = function(req, res, next) { + let methodName = 'createCard'; + console.log(methodName+' entered for: ', req.body.id); let adminConnection = new AdminConnection(); let _meta = {}; for (let each in config.composer.metaData) @@ -435,7 +439,14 @@ exports.createCard = function(req, res, next) { return adminConnection.importCard(req.body.id, tempCard) .then ((_res) => { let _msg = ((_res) ? 'card updated' : 'card imported'); console.log('create Card succeeded:'+_msg); - res.send({'result': 'success', 'card': _msg}); + let businessNetworkConnection = new BusinessNetworkConnection(); + return businessNetworkConnection.connect(req.body.id) + .then(() => { + return businessNetworkConnection.ping() + .then((_msg) => { businessNetworkConnection.disconnect(); res.send({'result': 'success', 'card': _msg}); }) + .catch((error) => { console.log(methodName+' businessNetworkConnection.ping() failed. error: ',error); businessNetworkConnection.disconnect(); res.send({'result': 'failed', 'card': error});}); + }) + .catch((error) =>{ console.log(methodName+' businessNetworkConnection.connect('+req.body.id+') failed. error: ',error); res.send({'result': 'failed', 'card': error});}); }) .catch((error) => { console.error('adminConnection.importCard failed. ',error.message); @@ -618,6 +629,7 @@ exports.removeMember = function(req, res, next) { // connection prior to V0.15 // return businessNetworkConnection.connect(config.composer.connectionProfile, config.composer.network, config.composer.adminID, config.composer.adminPW) // connection in v0.15 + /* return businessNetworkConnection.connect(config.composer.adminCard) .then(() => { return businessNetworkConnection.getParticipantRegistry(NS+'.'+req.body.registry) @@ -637,6 +649,7 @@ exports.removeMember = function(req, res, next) { .catch((error) => {console.log('error with getParticipantRegistry', error); res.send(error.message);}); }) .catch((error) => {console.log('error with businessNetworkConnection', error); res.send(error.message);}); + */ }; /** @@ -692,4 +705,20 @@ exports.getHistory = function(req, res, next) { */ exports.getKubeAddress = function(req, res, next) { res.send(config.kube_address); -}; \ No newline at end of file +}; +/** + * check to see if the system has been loaded with members yet. + * memberStatus.JSON: BUILD_MEMBERS === 'NOT STARTED', the autoLoad process has not yet been run + * memberStatus.JSON: BUILD_MEMBERS === 'COMPLETE', the autoLoad process has been run to completion + * Folder abby@kidfriendlyinc.com exists, this system has not yet been restarted since autoLoad was run to completion + * Folder abby@kidfriendlyinc.com does not exist, this system has been restarted since autoLoad was run to completion + * + * ===> Not Yet Active <=== + * @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.checkLoadStatus = function(req, res, next) { + res.send({status: ''}); +}; diff --git a/Chapter13/controller/restapi/features/composer/queryBlockChain.js b/Chapter13/controller/restapi/features/composer/queryBlockChain.js index 04498ef..340ac90 100644 --- a/Chapter13/controller/restapi/features/composer/queryBlockChain.js +++ b/Chapter13/controller/restapi/features/composer/queryBlockChain.js @@ -28,6 +28,8 @@ const svc = require('./Z2B_Services'); // const util = require('./Z2B_Utilities'); // const financeCoID = 'easymoney@easymoneyinc.com'; const config = require('../../../env.json'); +// 9/12/18 change to read connection.json from PeerAdmin@hlfv1 card +const admin_connection = require('./creds/cards/PeerAdmin@hlfv1/connection.json'); const hlf1_profile = require('../../../connection.json'); let chainEvents = false; @@ -73,18 +75,18 @@ exports.getChainInfo = function(req, res, next) // 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') + if (admin_connection.channel === 'composerchannel') { - console.log(method+" running locally"); + console.log(method+" running against Docker images"); 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 locally"); - channel = client.newChannel(hlf1_profile.channel); - channel.addPeer(client.newPeer(hlf1_profile.peers[0].requestURL)); - channel.addOrderer(client.newOrderer(hlf1_profile.orderers[0].url)); + console.log(method+" running against kubernetes immages"); + channel = client.newChannel(admin_connection.channel); + channel.addPeer(client.newPeer(admin_connection.peers[0].requestURL)); + channel.addOrderer(client.newOrderer(admin_connection.orderers[0].url)); } }) .then(() => { @@ -142,17 +144,21 @@ exports.getChainEvents = function(req, res, next) // 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(hlf1_profile.channel); + if (admin_connection.channel === 'composerchannel') + {console.log(method+" running against Docker images");} + else + {console.log(method+" running against kubernetes images");} + channel = client.newChannel(admin_connection.channel); //get the request URL for the Peer0 container - channel.addPeer(client.newPeer(hlf1_profile.peers[0].requestURL)); + channel.addPeer(client.newPeer(admin_connection.peers[0].requestURL)); // get the orderer URL - channel.addOrderer(client.newOrderer(hlf1_profile.orderers[0].url)); + channel.addOrderer(client.newOrderer(admin_connection.orderers[0].url)); // change Admin in following line to admin var pemPath = path.join(__dirname,'creds','ca.pem'); var adminPEM = fs.readFileSync(pemPath).toString(); var bcEvents = new hfcEH(client); - bcEvents.setPeerAddr(hlf1_profile.peers[0].eventURL, {pem: adminPEM}); - bcEvents.registerBlockEvent( + bcEvents.setPeerAddr(admin_connection.peers[0].eventURL, {pem: adminPEM}); + bcEvents.registerBlockEvent( function(event){svc.send(req.app.locals, 'BlockChain', event);}, function(error){console.log(method+': registerBlockEvent error: ', error);} ); diff --git a/Chapter13/controller/restapi/router.js b/Chapter13/controller/restapi/router.js index 6703a00..9a91733 100644 --- a/Chapter13/controller/restapi/router.js +++ b/Chapter13/controller/restapi/router.js @@ -14,17 +14,18 @@ 'use strict'; -let express = require('express'); -let router = express.Router(); -let format = require('date-format'); +var express = require('express'); +var router = express.Router(); +var 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'); +var noSQL = require('./features/cloudant_utils_web'); router.get('/fabric/getChainInfo', hlcFabric.getChainInfo); @@ -32,10 +33,11 @@ router.get('/fabric/getChainEvents', hlcFabric.getChainEvents); router.get('/fabric/getHistory', hlcAdmin.getHistory); router.post('/setup/autoLoad*', setup.autoLoad); +router.get('/setup/getLastRestart*', setup.getLastRestart); router.get('/composer/client/initEventRegistry*', hlcClient.init_z2bEvents); module.exports = router; -let count = 0; +var 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. @@ -58,6 +60,28 @@ router.use(function(req, res, next) { // The asterisk (*) means 'ignore anything following this point' // which means we have to be careful about ordering these statements. // + +router.get('/db/getMetadata*', noSQL.w_getMetadata); +router.get('/db/getCapabilities*', noSQL.w_capabilities); +router.get('/db/getBackups*', noSQL.w_getBackups); +router.get('/db/getOne*', noSQL.w_getOne); +router.post('/db/getOne*', noSQL.w_getOne); +router.post('/db/authenticate*', noSQL.w_authenticate); +router.post('/db/listCollections*', noSQL.w_listAllDatabases); +router.post('/db/listDocuments*', noSQL.w_listAllDocuments); +router.post('/db/createBackup*', noSQL.w_createBackup); +router.post('/db/createTable*', noSQL.w_create); +router.post('/db/dropTable*', noSQL.w_drop); +router.post('/db/insert*', noSQL.w_insert); +router.post('/db/update*', noSQL.w_update); +router.post('/db/delete*', noSQL.w_delete); +router.post('/db/buildEntryPage*', noSQL.w_buildEntryPage); +router.post('/db/getDocs*', noSQL.w_getDocs); +router.post('/db/selectMulti*', noSQL.w_selectMulti); +router.post('/db/select2*', noSQL.w_select2); +router.post('/db/select*', noSQL.w_select); +router.post('/db/restoreTable*', noSQL.w_restoreTable); + router.get('/api/getSupportedLanguages*',multi_lingual.languages); router.get('/api/getTextLocations*',multi_lingual.locations); router.post('/api/selectedPrompts*',multi_lingual.prompts); diff --git a/Chapter13/index.js b/Chapter13/index.js index dc2ff55..22f9914 100644 --- a/Chapter13/index.js +++ b/Chapter13/index.js @@ -25,6 +25,7 @@ const fs = require('fs'); const mime = require('mime'); const bodyParser = require('body-parser'); const cfenv = require('cfenv'); +const request = require('request'); const cookieParser = require('cookie-parser'); // const session = require('express-session'); @@ -38,6 +39,7 @@ 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 /** @@ -63,6 +65,10 @@ 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. +if (cfenv.getAppEnv().isLocal == true) +{let protocolToUse = 'http';} +else +{let protocolToUse = 'https';} app.use('/', require('./controller/restapi/router')); @@ -84,7 +90,7 @@ app.locals.wsServer.on('request', function(request) 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 }); + let json = JSON.stringify({ type:'Message', data: obj.text }); app.locals.processMessages(json); }); @@ -115,6 +121,25 @@ 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);}); + +// +// +// +let urlBase = cfenv.getAppEnv(url).url; +console.log('index.js urlBase is: '+urlBase); +// +// execute autoload process to set up initial members and orders in network. +// +let options = {}; +var url = urlBase+'/setup/autoLoad'; +var method = "POST"; +request( { url: url, json: options, method: method }, + function(error, response, body) + { + console.log('index.js autoload (error)', error); + console.log('index.js autoload (body)', body); + }); + /** * load any file requested on the server * @param {express.req} req - the inbound request object from the client From dc7587006bfbe9eebb936c894035762aa23a1ace Mon Sep 17 00:00:00 2001 From: Bob Dill Date: Thu, 20 Sep 2018 15:58:58 -0400 Subject: [PATCH 4/4] created new favico. fixed incorrect if statements in network/lib/sample.js --- Chapter04/favicon.ico | Bin 2742 -> 0 bytes Chapter05/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter05/favicon.ico | Bin 2742 -> 0 bytes Chapter05/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter06/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter06/favicon.ico | Bin 2742 -> 0 bytes Chapter06/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter07/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter07/favicon.ico | Bin 2742 -> 0 bytes Chapter07/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter08/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter08/favicon.ico | Bin 2742 -> 0 bytes Chapter08/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter09/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter09/favicon.ico | Bin 2742 -> 0 bytes Chapter09/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter10/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter10/favicon.ico | Bin 2742 -> 0 bytes Chapter10/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter11/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter11/favicon.ico | Bin 2742 -> 0 bytes Chapter11/network/lib/sample.js | 30 +++++++++++++++--------------- Chapter12/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter12/favicon.ico | Bin 2742 -> 0 bytes Chapter12/network/lib/sample.js | 6 +++--- Chapter13/HTML/favicon.ico | Bin 2742 -> 1150 bytes Chapter13/favicon.ico | Bin 2742 -> 0 bytes Chapter13/network/lib/sample.js | 6 +++--- Chapter14/favicon.ico | Bin 2742 -> 0 bytes 29 files changed, 111 insertions(+), 111 deletions(-) delete mode 100755 Chapter04/favicon.ico mode change 100755 => 100644 Chapter05/HTML/favicon.ico delete mode 100755 Chapter05/favicon.ico mode change 100755 => 100644 Chapter06/HTML/favicon.ico delete mode 100755 Chapter06/favicon.ico mode change 100755 => 100644 Chapter07/HTML/favicon.ico delete mode 100755 Chapter07/favicon.ico mode change 100755 => 100644 Chapter08/HTML/favicon.ico delete mode 100755 Chapter08/favicon.ico mode change 100755 => 100644 Chapter09/HTML/favicon.ico delete mode 100755 Chapter09/favicon.ico mode change 100755 => 100644 Chapter10/HTML/favicon.ico delete mode 100755 Chapter10/favicon.ico mode change 100755 => 100644 Chapter11/HTML/favicon.ico delete mode 100755 Chapter11/favicon.ico mode change 100755 => 100644 Chapter12/HTML/favicon.ico delete mode 100755 Chapter12/favicon.ico mode change 100755 => 100644 Chapter13/HTML/favicon.ico delete mode 100755 Chapter13/favicon.ico delete mode 100755 Chapter14/favicon.ico diff --git a/Chapter04/favicon.ico b/Chapter04/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter05/HTML/favicon.ico b/Chapter05/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter05/favicon.ico b/Chapter05/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 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/HTML/favicon.ico b/Chapter06/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter06/favicon.ico b/Chapter06/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 zcmV;n3Q6^eP)004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000U-Nkl_s(=Jp zppBp)FdzDXLWMz4gsKV)6=*98u38n9ipmjvq=>5PREk7t!Ie@HK#----9km!G>PNH zu`eI)xp*|4nfuzc*VY_q<-MMB=gyq}IddK}cMCxf;Hi74$BV!N;>iQz0rBJk@ql>p zfOtSWc|bfMo;)C_r~=gFv)0BmDLmDr9T(RCASK{~YhX-OU_=9|B7C*=1jt&Ussd7G zYxaO-qZ&}?M8`$V3@bnjm=*IIlmFL&X=#g0aZJ9?N}Fx19hE*X^rO?Ld?MbdnDLJY z|44$d=<|&DTN&H*?+9(lWo6 zK2{STElnVGtpj!9>h-kJ*ObOLA?+%^3gE1Pf7b9!6Bu>5o>F*Q036%q(cy5$Qz`kQ@eC`xrYPx{5=f|jr-A3S|3!}Yc}GCvqFKXz^_9%jD>AVFI08Hc z{0!J_;N6~JGAiwqzh9RA_0LmUWaJ86O@P!KZPPiz*t)A*7IW4B?cG4V&woD_>|rLdBn20)_kN$XQBi$$j+ODO0H6XV*( z4N2Ri627*S@Um)1Bx}OoGGw;4yl6rTS6f=*nI!xTV%kbE@)*~~RJDY{ro1OM56J_F znp@~)f(glET8i*prI41TbW*ZBBT|A&0x+VFX)Jo%Y=Svgw47-o3m}CKp6%5OkTu1u z^+KkpSQuA`)cIS=b5?uvDk{j<)85=kKspfOtSWc|bfMo;)C)JRlwr zPaY5th^KZ^vDs`kfT1?^_f6n-`Ti?9b87u4@C>jIxEEM-@ZJvGkmFg`1HVYY-v=B9 z&dd0`7m7f+jlz19^8I39QwIRqM7UdcEpR9BRr!0;!28ymWB4xc5aDT-B=mXUEjgZd zNfG#^4Ek(NK@9*0lVV%r_LZi{RM@(%gXJ7rmeZXG1zL{`4^Xy8^ zMLu`2e0s48T%BXt>=C(q9r#-e@ArXiIp*6794P{yX`@j7sQ|Kv_+z4-p!Wc8m+xbQ zrxWwizkshe+TTmQLwgaODM}c*sZAQHI+??wF)O`kzXLcQ_&4Ek)3a?D!-o`WG0`q* zTg)lHgM~(;@dh!Myo2tMXymwDpC^&m(h398CmKB`5z^+IV<^aSe8kbuHj!c8=bTwD zQP!HEBvF3}1&(*-A zj`kag|7C<59e`|`pyYg-^Z*j<>`z=Z-z8rlgw-2vt66)$GQplhDAmLl8`Pq02!AuPa;6J%9`mKa9;+&4avQopa&Cx5joQ& z8%X_*x!(_bG)6{6-0yI1P2<#`k?hp99&jvKKP;Eak0D?*PO%kNCdp$(9iCA@Qmy;-(R zPsqKU2z##sev=^5S|Y5$4LPhX3g^EDyg!AtK0$ckVo>@RaPYqD6T{?*p{h9NCLC{% zlx-H}`#CYsFeZjR%333(lZ$~XaTgcwBmH@&^9c{Qxr&PqOTJ>G%YhHzE-t#t>wSc0 zxopM7sPK1Ct~U}EBLjzX9|nHQ^5KdNz$azNvOc3+E+0(lLv~5D;G(@50C@*+M+{Fr z29VFmWL@+e_KNSu1b(Jp0b~LA7O(w{7M=DefNTctPVtP`JS0g-n#BhsxhL+~Ne&X^ zJyWcXVFCC~4F5^OA85-xPn(5IOGxY5!p*WDa#+ki&wj{{fIr~g!rkwa}V?f6hP3neYKtmGZE~T8u zQA0p8NLXx~*LW8VKCef3ds4`?04x@P$AEXY0gwW4k2qZ<(z$|_KPU(AW|p^VZ%a9b z0`Mi^$A$>*IW5`i1-?gkU_qsGB&bNc)o{E>_#Q{QetU6;^8F%K=g0p4qGZ8aGmhZ` z!~c#FZm#JirH3uPd2@#6ZYdHT65c_+W028O@oVM!ZdUKGbwv9KU)otnIgbODwp(N= z3&c8JBrI&2qON--A4$4g_5dfnQ|~gTzFzi2`qm3*g+`-s2jO;O-j`lBNcR2%xxSUo zzS7nw&a+>t3J)20t|2@Jb~>SBkM=4i3AbTwe)2)W^88H}p3Smk4Ww`d#mK2&FA+pL zXh8wwj2gtbZQfOztNctAXPKs+Fx wJRlwrPaY5th$jz-Cl81R#FGbPJ<$IK0Q%jKJcl7zFaQ7m07*qoM6N<$f-_VMasU7T diff --git a/Chapter06/network/lib/sample.js b/Chapter06/network/lib/sample.js index d8f70ae..cf20b90 100644 --- a/Chapter06/network/lib/sample.js +++ b/Chapter06/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter07/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter07/favicon.ico b/Chapter07/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter07/network/lib/sample.js b/Chapter07/network/lib/sample.js index d8f70ae..cf20b90 100644 --- a/Chapter07/network/lib/sample.js +++ b/Chapter07/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter08/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter08/favicon.ico b/Chapter08/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter08/network/lib/sample.js b/Chapter08/network/lib/sample.js index ed6499f..0d948b6 100644 --- a/Chapter08/network/lib/sample.js +++ b/Chapter08/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter09/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter09/favicon.ico b/Chapter09/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter09/network/lib/sample.js b/Chapter09/network/lib/sample.js index ed6499f..0d948b6 100644 --- a/Chapter09/network/lib/sample.js +++ b/Chapter09/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter10/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter10/favicon.ico b/Chapter10/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter10/network/lib/sample.js b/Chapter10/network/lib/sample.js index ed6499f..0d948b6 100644 --- a/Chapter10/network/lib/sample.js +++ b/Chapter10/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter11/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter11/favicon.ico b/Chapter11/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter11/network/lib/sample.js b/Chapter11/network/lib/sample.js index ed6499f..0d948b6 100644 --- a/Chapter11/network/lib/sample.js +++ b/Chapter11/network/lib/sample.js @@ -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/HTML/favicon.ico b/Chapter12/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter12/favicon.ico b/Chapter12/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter12/network/lib/sample.js b/Chapter12/network/lib/sample.js index 7a0c946..174b4db 100644 --- a/Chapter12/network/lib/sample.js +++ b/Chapter12/network/lib/sample.js @@ -196,7 +196,6 @@ 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) @@ -206,6 +205,7 @@ function RequestPayment(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a payment to the seller @@ -216,7 +216,6 @@ 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) @@ -226,6 +225,7 @@ function AuthorizePayment(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a payment to the seller @@ -236,7 +236,6 @@ 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) @@ -246,6 +245,7 @@ function Pay(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a dispute by the buyer diff --git a/Chapter13/HTML/favicon.ico b/Chapter13/HTML/favicon.ico old mode 100755 new mode 100644 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..3dbe425bfbe4df79693f618e61bcf2f3a11426a3 GIT binary patch literal 1150 zcmd6mO-mJF6vv;)G%e*yRA|1`Lc9vK>1rDIvh+Jd3sIv@1WKaNBEf<_LT&_w+^B^h z8m?v;1QDX9cdqXXze33H>wo6C7Z-B{!%b)S&kSdt-<Y z9PL5)8*i;XBvv2gD@CWjLC-477 z_iwXcvtj!`IfjFr_pdjt?^i3;6p_5^Y4-lv9GQ$?TY}|z&lj7OVmX`Dl?7<84!E12 z-;n|M_!i!e!H4mrEN{<;`+X0$#bvGsbT>h@{-5<9#BuruqSK@-Z}ICpTov0T&i=nA hmGDUX>*p{z3KPT9NK)^Vx96{(*?P@srM7>$_zSY+sRaN4 literal 2742 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 diff --git a/Chapter13/favicon.ico b/Chapter13/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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 diff --git a/Chapter13/network/lib/sample.js b/Chapter13/network/lib/sample.js index 7a0c946..174b4db 100644 --- a/Chapter13/network/lib/sample.js +++ b/Chapter13/network/lib/sample.js @@ -196,7 +196,6 @@ 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) @@ -206,6 +205,7 @@ function RequestPayment(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a payment to the seller @@ -216,7 +216,6 @@ 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) @@ -226,6 +225,7 @@ function AuthorizePayment(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a payment to the seller @@ -236,7 +236,6 @@ 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) @@ -246,6 +245,7 @@ function Pay(purchase) { return (_res); }).catch(function(error){return(error);}); }); + } } /** * Record a dispute by the buyer diff --git a/Chapter14/favicon.ico b/Chapter14/favicon.ico deleted file mode 100755 index fc9e25d83e1d0538587e37b383c1f74638a35ff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2742 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