UNPKG

@silasdavis/legacy-db

Version:

Javascript library that calls a Hyperledger Burrow server over rpc.

614 lines (560 loc) 18.4 kB
/** * @file consensus.js * @fileOverview Factory module for the Events class. * @author Andreas Olofsson * @module consensus */ 'use strict' var util = require('./util') var nUtil = require('util') // The interval for polling. var defaultPollingInterval = 1000 /** * Create a new instance of the Events class. Events allow callers to listen for specific * events that are being fired by the server; one example being whenever a new block * has been added to the chain, or the code in a specific contract account is being run. * * @param {module:rpc/client~Client} client - The client object. * @returns {Events} - A new instance of the Events class. */ exports.createInstance = function (server) { return new Events(server) } /** * The Events class deals with event subscriptions. * * @param {module:rpc/client~Client} client - The client object. * @augments module:util~ComponentBase * @constructor */ function Events (server) { this._pollingInterval = defaultPollingInterval util.ComponentBase.call(this, server) // Always use "HTTP" style polling for now. // if (client instanceof TWCClient) { // this._sub = WsEventSub; // } else { // this._sub = HttpEventSub; // } this._sub = HttpEventSub } nUtil.inherits(Events, util.ComponentBase) /** * Subscribe to a given event. * * @param {string} event_id - The event id. * @param {module:rpc/rpc~methodCallback} callback - The callback function. */ Events.prototype.subscribe = function (eventId, callback) { this.server.eventSubscribe({event_id: eventId}, callback) } /** * Unsubscribe to a given event. * * @param {string} subId - The subscription id (provided as a response to subscribe). * @param {module:rpc/rpc~methodCallback} callback - The callback function. */ Events.prototype.unsubscribe = function (subId, callback) { this.server.eventUnsubscribe({sub_id: subId}, callback) } /** * Poll for new event data. * * @param {string} subId - The subscription id (provided as a response to subscribe). * @param {module:rpc/rpc~methodCallback} callback - The callback function. The callback * will receive a (potentially empty) array of events of the given type. */ Events.prototype.poll = function (subId, callback) { this.server.eventPoll({sub_id: subId}, callback) } /** * Subscribe for solidity events. This is the same as subscribing for logs. * * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subSolidityEvent = function (address, createCallback, eventCallback) { // No need to do another call here... this._startEventSub(logEventId(address), createCallback, eventCallback) } /** * Subscribe for log events. * * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subLogEvent = function (address, createCallback, eventCallback) { // this._startEventSub(logEventId(address), createCallback, eventCallback); createCallback(Error('This functionality is believed to be broken in Burrow. See https://github.com/hyperledger/burrow/issues/96.')) } /** * Subscribe for account intput events. * * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subAccountInput = function (address, createCallback, eventCallback) { this._startEventSub(accInputId(address), createCallback, eventCallback) } /** * Subscribe for account output events. * * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subAccountOutput = function (address, createCallback, eventCallback) { this._startEventSub(accOutputId(address), createCallback, eventCallback) } /** * Subscribe for account receive events. * * @deprecated Use 'subAccountCall' * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subAccountReceive = function (address, createCallback, eventCallback) { console.log("DEPRECATED: Use 'subAccountCall' at night.") this._startEventSub(accCallId(address), createCallback, eventCallback) } /** * Subscribe for account call events. * * @param {string} address - The account to be tracked. * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subAccountCall = function (address, createCallback, eventCallback) { this._startEventSub(accCallId(address), createCallback, eventCallback) } /** * Subscribe for bond events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subBonds = function (createCallback, eventCallback) { this._startEventSub(bondId(), createCallback, eventCallback) } /** * Subscribe for unbond events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subUnbonds = function (createCallback, eventCallback) { this._startEventSub(unbondId(), createCallback, eventCallback) } /** * Subscribe for rebond events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subRebonds = function (createCallback, eventCallback) { this._startEventSub(rebondId(), createCallback, eventCallback) } /** * Subscribe for dupeout events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subDupeouts = function (createCallback, eventCallback) { this._startEventSub(dupeoutId(), createCallback, eventCallback) } /** * Subscribe for new block events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subNewBlocks = function (createCallback, eventCallback) { this._startEventSub(newBlockId(), createCallback, eventCallback) } /** * Subscribe for fork events. * * @param {module:rpc/rpc~methodCallback} createCallback - Callback for when the subscription has * been created. The data returned is a new EventSub object. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. */ Events.prototype.subForks = function (createCallback, eventCallback) { this._startEventSub(forkId(), createCallback, eventCallback) } /** * Set the time-interval between poll requests. This does not apply when using websockets. * * @param {number} intervalMs - The time interval in milliseconds. */ Events.prototype.setPollingInterval = function (intervalMs) { this._pollingInterval = intervalMs } /** * Get the interval between poll requests. * * @returns {number} */ Events.prototype.getPollingInterval = function () { return this._pollingInterval } /** * * @returns {module:rpc/client~Client} The client. */ Events.prototype.getClient = function () { return this._client } /** * Create a new event subscription with the basic polling interval. * @param {string} eventId - The event id. * @param {module:rpc/rpc~methodCallback} [createCallback] - Callback for when the subscription has * been created. The data returned is a new EventSub object. If this is omitted, then it is assumed * that the sub is a 'once' sub, meaning it will automatically stop after receiving the first event. * Moreover, any potential startup errors will be passed into the 'eventCallback' function. * @param {module:rpc/rpc~methodCallback} eventCallback - The callback function. * @private */ Events.prototype._startEventSub = function (eventId, createCallback, eventCallback) { var startCallback if (!eventCallback) { eventCallback = createCallback } else { startCallback = createCallback } var es = new this._sub(this, eventId, eventCallback) es.start(startCallback) } /** * An event subscription is an object that is used to poll the server continuously to check whether * any new events has arrived on the event channel. * * @param {Events} events - The Events object. * @param {string} eventId - The event id. * @param {module:rpc/rpc~methodCallback} eventCallback - Callback for handling incoming data. * @constructor */ function EventSub (events, eventId, eventCallback) { this._run = false this._events = events this._eventId = eventId this._eventCallback = eventCallback this._once = false this._closeCallback = function () { } } /** * Start listening for new events. * * @param {module:rpc/rpc~methodCallback} callback - Provides this EventSub instance as data, or an error if it * fails to set up a subscription. */ EventSub.prototype.start = function (callback) { callback(new Error("Subclass does not override the 'start' method")) } /** * Used to poll manually. */ EventSub.prototype.poll = function () {} /** * Stop subscribing. * * @param {module:rpc/rpc~methodCallback} [closeCallback] - Called when the subscription has been * canceled. This is optional, and will overwrite any close callback that was set when starting. */ EventSub.prototype.stop = function (closeCallback) { if (typeof (closeCallback) === 'function') { this._closeCallback = closeCallback } closeCallback(new Error("Subclass does not override the 'stop' method")) } /** * Get the subscriber id. * @returns {string} */ EventSub.prototype.getSubscriberId = function () { return this._subId } /** * Get the event id. * @returns {string} */ EventSub.prototype.getEventId = function () { return this._eventId } /** * An event subscription type that listens to events being sent over a websocket connection. * * // TODO change name to more general then "websocket". * * @param {Events} events - The Events object. * @param {string} eventId - The event id. * @param {module:rpc/rpc~methodCallback} eventCallback - Callback for handling incoming data. The data * will be an array of events. * @constructor */ function WsEventSub (events, eventId, eventCallback) { EventSub.call(this, events, eventId, eventCallback) } nUtil.inherits(WsEventSub, EventSub) /** * Start listening for new events. * * @param {module:rpc/rpc~methodCallback} [callback] - Provides this EventSub instance as data, or an error if it * fails to set up a subscription. If omitted, the sub will stop automatically after receiving an event. */ WsEventSub.prototype.start = function (callback) { var once = !callback this._once = once if (once) { callback = this._eventCallback } var errorCallback = once ? this._eventCallback : callback var that = this this._events.subscribe(this._eventId, function (error, data) { if (error) { errorCallback(error) return } var subId = data.sub_id that._subId = subId that._run = true var ec if (once) { ec = function (error, data) { that._eventCallback(error, data) that.stop() } } else { ec = that._eventCallback } that._events.getClient().addCallback(subId, function (err, evt) { if (err) return ec(err) ec(null, evt) }) if (!once) { callback(null, that) } }) } /** * Used to poll manually. */ WsEventSub.prototype.poll = function () { console.log('Cannot poll subscriptions when using a websocket connection; events are forwarded automatically.') } /** * Stop subscribing. This will send an unsubscribe message to the server. * * @param {module:rpc/rpc~methodCallback} [closeCallback] - Called when the subscription has been * canceled. This is optional, and will overwrite any close callback that was set when starting. */ WsEventSub.prototype.stop = function (closeCallback) { if (typeof (closeCallback) !== 'function') { closeCallback = this._closeCallback } var that = this this._events.unsubscribe(this._subId, function (error, data) { that._events.getClient().removeCallback(that._subId) if (error) { closeCallback(error) } else { closeCallback(null, data) } }) this._run = false } /** * An event subscription type that polls the server continuously to check whether * any new events has arrived. * * @param {Events} events - The Events object. * @param {string} eventId - The event id. * @param {module:rpc/rpc~methodCallback} eventCallback - Callback for handling incoming data. The data * will be an array of events. * @constructor */ function HttpEventSub (events, eventId, eventCallback) { EventSub.call(this, events, eventId, eventCallback) } nUtil.inherits(HttpEventSub, EventSub) /** * Start listening for new events. * * @param {module:rpc/rpc~methodCallback} callback - Provides this EventSub instance as data, or an error if it * fails to set up a subscription. */ HttpEventSub.prototype.start = function (callback) { var once = !callback this._once = once var that = this var errorCallback = once ? this._eventCallback : callback this._events.subscribe(this._eventId, function (error, data) { if (error) { errorCallback(error) return } that._subId = data.sub_id that._run = true // Start polling. setTimeout(function () { that._call() }, that._events.getPollingInterval()) if (callback) { callback(null, that) } }) } // TODO some cleanup here. HttpEventSub.prototype._call = function () { if (!this._run) { this._events.unsubscribe(this._subId, this._closeCallback) return } // Poll may return a number of events, so we break it down into multiple // calls to the callback. var that = this that.poll() if (!this._run) { this._events.unsubscribe(this._subId, this._closeCallback) return } setTimeout(function () { that._call() }, this._events.getPollingInterval()) } /** * Used to poll manually. */ HttpEventSub.prototype.poll = function () { var that = this this._events.poll(this._subId, function (error, data) { if (error !== null) { that._eventCallback(error) that._events.unsubscribe(that._subId, that._closeCallback) that._run = false return } if (data === null || data.events.length === 0) { return } if (that._once) { // TODO All but the first is discarded. This is perfectly fine as the // events that are pulled in will be unique to this sub so no data-loss, // though it would be better if 'once' was supported all the way to the // server, so that a 'once' flag could be passed with the request, and the // server event cache would return only the first event (if it has any). that._eventCallback(null, data.events[0]) that.stop() } else { for (var i = 0; i < data.events.length; i++) { that._eventCallback(null, data.events[i]) } } }) } /** * Stop subscribing. This will stop the polling loop and send an unsubscribe message to the server. * * @param {module:rpc/rpc~methodCallback} [closeCallback] - Called when the subscription has been * canceled. This is optional, and will overwrite any close callback that was set when starting. */ HttpEventSub.prototype.stop = function (closeCallback) { if (typeof (closeCallback) === 'function') { this._closeCallback = closeCallback } this._run = false } /** * Get log event id. This is used for solidity events as well. * * @param {string} address - The account address. * @returns {string} */ function logEventId (address) { return 'Log/' + address } /** * Get account input event id. * * @param {string} address - The account address. * @returns {string} */ function accInputId (address) { return 'Acc/' + address + '/Input' } /** * Get account output event id. * @param {string} address - The account address. * @returns {string} */ function accOutputId (address) { return 'Acc/' + address + '/Output' } /** * Get account call event id. * * @param {string} address - The account address. * @returns {string} */ function accCallId (address) { return 'Acc/' + address + '/Call' } /** * Get bond event id. * * @returns {string} */ function bondId () { return 'Bond' } /** * Get unbond event id. * @returns {string} */ function unbondId () { return 'Unbond' } /** * Get rebond event id. * @returns {string} */ function rebondId () { return 'Rebond' } /** * Get dupeout event id. * @returns {string} */ function dupeoutId () { return 'Dupeout' } /** * Get new block event id. * @returns {string} */ function newBlockId () { return 'NewBlock' } /** * Get fork event id. * @returns {string} */ function forkId () { return 'Fork' }