/* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ /** @file filter.js * @authors: * Jeffrey Wilcke * Marek Kotewicz * Marian Oancea * Fabian Vogelsteller * Gav Wood * @date 2014 */ var formatters = require('./formatters'); var utils = require('../utils/utils'); /** * Converts a given topic to a hex string, but also allows null values. * * @param {Mixed} value * @return {String} */ var toTopic = function(value){ if(value === null || typeof value === 'undefined') return null; value = String(value); if(value.indexOf('0x') === 0) return value; else return utils.fromUtf8(value); }; /// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones /// @param should be string or object /// @returns options string or object var getOptions = function (options) { if (utils.isString(options)) { return options; } options = options || {}; // make sure topics, get converted to hex options.topics = options.topics || []; options.topics = options.topics.map(function(topic){ return (utils.isArray(topic)) ? topic.map(toTopic) : toTopic(topic); }); // lazy load return { topics: options.topics, to: options.to, address: options.address, fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock), toBlock: formatters.inputBlockNumberFormatter(options.toBlock) }; }; /** Adds the callback and sets up the methods, to iterate over the results. @method getLogsAtStart @param {Object} self @param {funciton} */ var getLogsAtStart = function(self, callback){ // call getFilterLogs for the first watch callback start if (!utils.isString(self.options)) { self.get(function (err, messages) { // don't send all the responses to all the watches again... just to self one if (err) { callback(err); } if(utils.isArray(messages)) { messages.forEach(function (message) { callback(null, message); }); } }); } }; /** Adds the callback and sets up the methods, to iterate over the results. @method pollFilter @param {Object} self */ var pollFilter = function(self) { var onMessage = function (error, messages) { if (error) { return self.callbacks.forEach(function (callback) { callback(error); }); } if(utils.isArray(messages)) { messages.forEach(function (message) { message = self.formatter ? self.formatter(message) : message; self.callbacks.forEach(function (callback) { callback(null, message); }); }); } }; self.requestManager.startPolling({ method: self.implementation.poll.call, params: [self.filterId], }, self.filterId, onMessage, self.stopWatching.bind(self)); }; var Filter = function (web3, options, methods, formatter, callback) { var self = this; var implementation = {}; methods.forEach(function (method) { method.setRequestManager(web3._requestManager); method.attachToObject(implementation); }); this.requestManager = web3._requestManager; this.options = getOptions(options); this.implementation = implementation; this.filterId = null; this.callbacks = []; this.getLogsCallbacks = []; this.pollFilters = []; this.formatter = formatter; this.implementation.newFilter(this.options, function(error, id){ if(error) { self.callbacks.forEach(function(cb){ cb(error); }); } else { self.filterId = id; // check if there are get pending callbacks as a consequence // of calling get() with filterId unassigned. self.getLogsCallbacks.forEach(function (cb){ self.get(cb); }); self.getLogsCallbacks = []; // get filter logs for the already existing watch calls self.callbacks.forEach(function(cb){ getLogsAtStart(self, cb); }); if(self.callbacks.length > 0) pollFilter(self); // start to watch immediately if(callback) { return self.watch(callback); } } }); return this; }; Filter.prototype.watch = function (callback) { this.callbacks.push(callback); if(this.filterId) { getLogsAtStart(this, callback); pollFilter(this); } return this; }; Filter.prototype.stopWatching = function () { this.requestManager.stopPolling(this.filterId); // remove filter async this.implementation.uninstallFilter(this.filterId, function(){}); this.callbacks = []; }; Filter.prototype.get = function (callback) { var self = this; if (utils.isFunction(callback)) { if (this.filterId === null) { // If filterId is not set yet, call it back // when newFilter() assigns it. this.getLogsCallbacks.push(callback); } else { this.implementation.getLogs(this.filterId, function(err, res){ if (err) { callback(err); } else { callback(null, res.map(function (log) { return self.formatter ? self.formatter(log) : log; })); } }); } } else { if (this.filterId === null) { throw new Error('Filter ID Error: filter().get() can\'t be chained synchronous, please provide a callback for the get() method.'); } var logs = this.implementation.getLogs(this.filterId); return logs.map(function (log) { return self.formatter ? self.formatter(log) : log; }); } return this; }; module.exports = Filter;