github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/kat/src/clients/event-streams.ts (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import WebSocket from 'ws'; 16 import { config } from '../lib/config'; 17 import * as utils from '../lib/utils'; 18 import { IDBBlockchainData, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated, IEventAssetInstanceCreated, IEventAssetInstancePropertySet, IEventPaymentDefinitionCreated, IEventPaymentInstanceCreated, IEventStreamMessage, IEventStreamRawMessageCorda } from '../lib/interfaces'; 19 import * as membersHandler from '../handlers/members'; 20 import * as assetDefinitionsHandler from '../handlers/asset-definitions'; 21 import * as paymentDefinitionsHandler from '../handlers/payment-definitions'; 22 import * as assetInstancesHandler from '../handlers/asset-instances'; 23 import * as paymentInstanceHandler from '../handlers/payment-instances'; 24 import { IEventMemberRegistered } from '../lib/interfaces'; 25 26 const log = utils.getLogger('clients/event-streams.ts'); 27 28 let ws: WebSocket; 29 let heartBeatTimeout: NodeJS.Timeout; 30 let disconnectionDetected = false; 31 let disconnectionTimeout: NodeJS.Timeout; 32 33 export const init = () => { 34 ws = new WebSocket(config.eventStreams.wsEndpoint, { 35 headers: { 36 Authorization: 'Basic ' + Buffer.from(`${config.eventStreams.auth?.user ?? config.appCredentials.user}` + 37 `:${config.eventStreams.auth?.password ?? config.appCredentials.password}`).toString('base64') 38 39 } 40 }); 41 addEventHandlers(); 42 }; 43 44 export const shutDown = () => { 45 if (disconnectionTimeout) { 46 clearTimeout(disconnectionTimeout); 47 } 48 if (ws) { 49 clearTimeout(heartBeatTimeout); 50 ws.close(); 51 } 52 }; 53 54 const addEventHandlers = () => { 55 ws.on('open', () => { 56 if (disconnectionDetected) { 57 disconnectionDetected = false; 58 log.info('Event stream websocket restored'); 59 } 60 ws.send(JSON.stringify({ 61 type: 'listen', 62 topic: config.eventStreams.topic 63 })); 64 heartBeat(); 65 }).on('close', () => { 66 disconnectionDetected = true; 67 log.error(`Event stream websocket disconnected, attempting to reconnect in ${utils.constants.EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS} second(s)`); 68 disconnectionTimeout = setTimeout(() => { 69 init(); 70 }, utils.constants.EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS * 1000); 71 }).on('message', async (message: string) => { 72 await handleMessage(message); 73 ws.send(JSON.stringify({ 74 type: 'ack', 75 topic: config.eventStreams.topic 76 })); 77 }).on('pong', () => { 78 heartBeat(); 79 }).on('error', err => { 80 log.error(`Event stream websocket error. ${err}`); 81 }); 82 }; 83 84 const heartBeat = () => { 85 ws.ping(); 86 clearTimeout(heartBeatTimeout); 87 heartBeatTimeout = setTimeout(() => { 88 log.error('Event stream ping timeout'); 89 ws.terminate(); 90 }, utils.constants.EVENT_STREAM_PING_TIMEOUT_SECONDS * 1000); 91 } 92 93 const processRawMessage = (message: string): Array<IEventStreamMessage> => { 94 switch (config.protocol) { 95 case 'ethereum': 96 return JSON.parse(message); 97 case 'corda': 98 const cordaMessages: Array<IEventStreamRawMessageCorda> = JSON.parse(message); 99 return cordaMessages.map(msg => ( 100 { 101 data: { 102 ...msg.data.data, 103 timestamp: Date.parse(msg.recordedTime) 104 }, 105 transactionHash: msg.stateRef.txhash, 106 subId: msg.subId, 107 signature: msg.signature 108 } 109 ) 110 ); 111 } 112 } 113 114 const getBlockchainData = (message: IEventStreamMessage): IDBBlockchainData => { 115 switch (config.protocol) { 116 case 'ethereum': 117 return { 118 blockNumber: Number(message.blockNumber), 119 transactionHash: message.transactionHash 120 } 121 case 'corda': { 122 return { 123 transactionHash: message.transactionHash 124 } 125 } 126 } 127 } 128 129 const eventSignatures = () => { 130 switch (config.protocol) { 131 case 'ethereum': return utils.contractEventSignatures 132 case 'corda': return utils.contractEventSignaturesCorda 133 } 134 } 135 136 const handleMessage = async (message: string) => { 137 const messageArray: Array<IEventStreamMessage> = processRawMessage(message); 138 log.info(`Event batch (${messageArray.length})`) 139 const signatures = eventSignatures(); 140 for (const message of messageArray) { 141 log.trace(`Event ${JSON.stringify(message)}`); 142 log.info(`Event signature: ${message.signature}`); 143 const blockchainData: IDBBlockchainData = getBlockchainData(message); 144 try { 145 switch (message.signature) { 146 case signatures.MEMBER_REGISTERED: 147 await membersHandler.handleMemberRegisteredEvent(message.data as IEventMemberRegistered, blockchainData); break; 148 case signatures.ASSET_DEFINITION_CREATED: 149 await assetDefinitionsHandler.handleAssetDefinitionCreatedEvent(message.data as IEventAssetDefinitionCreated, blockchainData); break; 150 case signatures.DESCRIBED_PAYMENT_DEFINITION_CREATED: 151 case signatures.PAYMENT_DEFINITION_CREATED: 152 await paymentDefinitionsHandler.handlePaymentDefinitionCreatedEvent(message.data as IEventPaymentDefinitionCreated, blockchainData); break; 153 case signatures.ASSET_INSTANCE_CREATED: 154 case signatures.DESCRIBED_ASSET_INSTANCE_CREATED: 155 await assetInstancesHandler.handleAssetInstanceCreatedEvent(message.data as IEventAssetInstanceCreated, blockchainData); break; 156 case signatures.ASSET_INSTANCE_BATCH_CREATED: 157 await assetInstancesHandler.handleAssetInstanceBatchCreatedEvent(message.data as IEventAssetInstanceBatchCreated, blockchainData); break; 158 case signatures.DESCRIBED_PAYMENT_INSTANCE_CREATED: 159 case signatures.PAYMENT_INSTANCE_CREATED: 160 await paymentInstanceHandler.handlePaymentInstanceCreatedEvent(message.data as IEventPaymentInstanceCreated, blockchainData); break; 161 case signatures.ASSET_PROPERTY_SET: 162 await assetInstancesHandler.handleSetAssetInstancePropertyEvent(message.data as IEventAssetInstancePropertySet, blockchainData); break; 163 } 164 } catch (err) { 165 log.error(`Failed to handle event: ${message.signature} for message: ${JSON.stringify(message)} with error`, err.stack); 166 } 167 } 168 };