exports.newNetworkModulesWebSocketsNetworkClient = function newNetworkModulesWebSocketsNetworkClient() { let thisObject = { id: undefined, socket: undefined, isConnected: undefined, host: undefined, port: undefined, callerRole: undefined, p2pNetworkNode: undefined, p2pNetworkIdentity: undefined, onConnectionClosedCallBack: undefined, sendMessage: sendMessage, initialize: initialize, finalize: finalize } let web3 let called = {} let onMessageFunctionsMap = new Map() return thisObject function finalize() { thisObject.socket.close() thisObject.socket = undefined thisObject.id = undefined thisObject.isConnected = undefined thisObject.host = undefined thisObject.port = undefined thisObject.callerRole = undefined thisObject.p2pNetworkNode = undefined thisObject.p2pNetworkIdentity = undefined thisObject.onConnectionClosedCallBack = undefined web3 = undefined called = undefined onMessageFunctionsMap = undefined } async function initialize(callerRole, p2pNetworkIdentity, p2pNetworkNode, onConnectionClosedCallBack) { thisObject.callerRole = callerRole thisObject.p2pNetworkIdentity = p2pNetworkIdentity thisObject.p2pNetworkNode = p2pNetworkNode thisObject.onConnectionClosedCallBack = onConnectionClosedCallBack web3 = new SA.nodeModules.web3() thisObject.id = SA.projects.foundations.utilities.miscellaneousFunctions.genereteUniqueId() thisObject.host = JSON.parse(thisObject.p2pNetworkNode.node.config).host thisObject.port = JSON.parse(thisObject.p2pNetworkNode.node.config).webSocketsPort thisObject.socket = new SA.nodeModules.ws('ws://' + thisObject.host + ':' + thisObject.port) await setUpWebSocketClient() console.log('Websockets Client Connected to Network Node via Web Sockets .................. Connected to ' + thisObject.p2pNetworkNode.userProfile.userProfileHandle + ' -> ' + thisObject.p2pNetworkNode.node.name + ' -> ' + thisObject.host + ':' + thisObject.port) thisObject.isConnected = true } async function setUpWebSocketClient() { return new Promise(connectToNewtwork) function connectToNewtwork(resolve, reject) { try { thisObject.socket.onopen = () => { onConnectionOpened() } thisObject.socket.onclose = () => { onConnectionClosed() } thisObject.socket.onerror = err => { onError(err) } function onConnectionOpened() { handshakeProcedure() function handshakeProcedure() { let callerTimestamp stepOneRequest() function stepOneRequest() { /* Send Handshake Message Step One: At Step One we will just say hello, identify ourselves as a Network Client, and send the Network Node our User Profile Handle. This Handle will be signed by the Network Node to prove it's own identity, and later we will sign it's own handle to prove ours. */ thisObject.socket.onmessage = socketMessage => { stepOneResponse(socketMessage) } callerTimestamp = (new Date()).valueOf() let message = { messageType: 'Handshake', callerRole: thisObject.callerRole, callerProfileHandle: SA.secrets.map.get(global.env.DESKTOP_APP_SIGNING_ACCOUNT).userProfileHandle, callerTimestamp: callerTimestamp, callerNode: JSON.stringify(thisObject.p2pNetworkIdentity.node), step: 'One' } thisObject.socket.send(JSON.stringify(message)) } function stepOneResponse(socketMessage) { let response = JSON.parse(socketMessage.data) if (response.result !== 'Ok') { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> response.message = ' + response.message) reject() return } let signature = JSON.parse(response.signature) called.blockchainAccount = web3.eth.accounts.recover(signature) /* We will check that the signature received produces a valid Blockchain Account. */ if (called.blockchainAccount === undefined) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> Signature does not produce a valid Blockchain Account.') reject() return } /* We will check that the blockchain account taken from the signature matches the one we have on record for the user profile of the Network Node we are calling. */ if (called.blockchainAccount !== thisObject.p2pNetworkNode.blockchainAccount) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> The Network Node called does not have the expected Profile Handle.') reject() return } let signedMessage = JSON.parse(signature.message) /* We will verify that the signature belongs to the signature.message. To do this we will hash the signature.message and see if we get the same hash of the signature. */ let hash = web3.eth.accounts.hashMessage(signature.message) if (hash !== signature.messageHash) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> signature.message Hashed Does Not Match signature.messageHash.') reject() return } /* We will check that the Network Node that responded has the same User Profile Handle that we have on record, otherwise something is wrong and we should not proceed. */ if (signedMessage.calledProfileHandle !== thisObject.p2pNetworkNode.userProfile.userProfileHandle) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> The Network Node called does not have the expected Profile Handle.') reject() return } /* We will check that the profile handle we sent to the Network Node, is returned at the signed message, to avoid man in the middle attacks. */ if (signedMessage.callerProfileHandle !== SA.secrets.map.get(global.env.DESKTOP_APP_SIGNING_ACCOUNT).userProfileHandle) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> The Network Node callerProfileHandle does not match my own userProfileHandle.') reject() return } /* We will also check that the callerTimestamp we sent to the Network Node, is returned at the signed message, also to avoid man in the middle attacks. */ if (signedMessage.callerTimestamp !== callerTimestamp) { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> The Network Node callerTimestamp does not match my own callerTimestamp.') reject() return } /* All validations passed, it seems we can continue with Step Two. */ stepTwoRequest(signedMessage) } function stepTwoRequest(signedMessage) { /* Send Handshake Message Step Two: Here we will sign a message with the Network Node profile handle and timestamp to prove our own identity. */ thisObject.socket.onmessage = socketMessage => { stepTwoResponse(socketMessage) } let signature = web3.eth.accounts.sign(JSON.stringify(signedMessage), SA.secrets.map.get(global.env.DESKTOP_APP_SIGNING_ACCOUNT).privateKey) let message = { messageType: 'Handshake', signature: JSON.stringify(signature), step: 'Two' } thisObject.socket.send(JSON.stringify(message)) } function stepTwoResponse(socketMessage) { let response = JSON.parse(socketMessage.data) if (response.result !== 'Ok') { console.log('[ERROR] Web Sockets Client -> stepOneResponse -> response.message = ' + response.message) reject() return } /* This was the end of the Handshake procedure. We are connected to the Network Node and from now on, all response messages will be received at this following function. */ thisObject.socket.onmessage = socketMessage => { onMenssage(socketMessage) } resolve() } } } function onConnectionClosed() { if (thisObject.isConnected === true) { console.log('Websockets Client Disconnected from Network Node via Web Sockets ......... Disconnected from ' + thisObject.p2pNetworkNode.userProfile.userProfileHandle + ' -> ' + thisObject.p2pNetworkNode.node.name + ' -> ' + thisObject.host + ':' + thisObject.port) } if (thisObject.onConnectionClosedCallBack !== undefined) { thisObject.onConnectionClosedCallBack(thisObject.id) } thisObject.isConnected = false } function onError(err) { if (err.message.indexOf('ECONNREFUSED') >= 0) { console.log('[WARN] Web Sockets Client -> onError -> Nobody home at ' + thisObject.host + ':' + thisObject.port) reject() return } console.log('[ERROR] Web Sockets Client -> onError -> err.message = ' + err.message) console.log('[ERROR] Web Sockets Client -> onError -> err.stack = ' + err.stack) reject() return } } catch (err) { console.log('[ERROR] Web Sockets Client -> setUpWebSocketClient -> err.stack = ' + err.stack) } } } function sendMessage(message) { return new Promise(sendSocketMessage) function sendSocketMessage(resolve, reject) { if (thisObject.socket.readyState !== 1) { // 1 means connected and ready. console.log('[ERROR] Web Sockets Client -> sendMessage -> Cannot send message while connection is closed.') reject('Websockets Connection Not Ready.') return } let socketMessage = { messageId: SA.projects.foundations.utilities.miscellaneousFunctions.genereteUniqueId(), messageType: 'Request', payload: message } onMessageFunctionsMap.set(socketMessage.messageId, onMenssageFunction) thisObject.socket.send( JSON.stringify(socketMessage) ) function onMenssageFunction(response) { try { if (response.result === 'Ok') { resolve(response.data) } else { console.log('[ERROR] Web Sockets Client -> onMenssageFunction -> response.message = ' + response.message) reject(response.message) } } catch (err) { callbackFunction = undefined console.log('[ERROR] Web Sockets Client -> err.stack = ' + err.stack) } } } } function onMenssage(socketMessage) { let response = JSON.parse(socketMessage.data) /* We get the function that is going to resolve or reject the promise given. */ let onMenssageFunction = onMessageFunctionsMap.get(response.messageId) if (onMenssageFunction !== undefined) { /* The message received is a response to a message sent. */ onMessageFunctionsMap.delete(response.messageId) onMenssageFunction(response) } else { /* The message received is a not response to a message sent. */ let messageHeader try { messageHeader = JSON.parse(message) } catch (err) { console.log('[ERROR] Web Sockets Client -> onMenssage -> message = ' + message) console.log('[ERROR] Web Sockets Client -> onMenssage -> err.stack = ' + err.stack) thisObject.socket.close() return } switch (thisObject.callerRole) { case 'Network Client': { /* This is the use case of a network client receiving a notification of somethinig that happened at the network that is relevant to itself. */ break } case 'Network Peer': { /* No use cases so far were a Network Peer would send a message through an incomming websockets connection, without having received a request first. */ break } } } } }