diff options
author | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-31 04:35:40 +0800 |
---|---|---|
committer | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-31 04:35:40 +0800 |
commit | aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9 (patch) | |
tree | 066a6280876d2c6c05788989d1f0610be37b4b46 | |
parent | eef4cd1b64c38e3327dbe7f1b70a60b01ce9cbed (diff) | |
parent | cb607b4911711501a8e70db0fe2148c51272c719 (diff) | |
download | dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar.gz dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar.bz2 dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar.lz dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar.xz dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.tar.zst dexon-aca9a41fcf38ae42ab72b663b6fdc4ecd9c7a7b9.zip |
Merge pull request #1 from debris/master
Http JSON-RPC provider, http polling, little refactor, whisper
-rw-r--r-- | httprpc.js | 70 | ||||
-rw-r--r-- | index.html | 1 | ||||
-rw-r--r-- | main.js | 643 | ||||
-rw-r--r-- | qt.js | 14 | ||||
-rw-r--r-- | websocket.js | 2 |
5 files changed, 364 insertions, 366 deletions
diff --git a/httprpc.js b/httprpc.js new file mode 100644 index 000000000..085b4693d --- /dev/null +++ b/httprpc.js @@ -0,0 +1,70 @@ +(function () { + var HttpRpcProvider = function (host) { + this.handlers = []; + this.host = host; + }; + + function formatJsonRpcObject(object) { + return { + jsonrpc: '2.0', + method: object.call, + params: object.args, + id: object._id + } + }; + + function formatJsonRpcMessage(message) { + var object = JSON.parse(message); + + return { + _id: object.id, + data: object.result + }; + }; + + HttpRpcProvider.prototype.sendRequest = function (payload, cb) { + var data = formatJsonRpcObject(payload); + + var request = new XMLHttpRequest(); + request.open("POST", this.host, true); + request.send(JSON.stringify(data)); + request.onreadystatechange = function () { + if (request.readyState === 4 && cb) { + cb(request); + } + } + }; + + HttpRpcProvider.prototype.send = function (payload) { + var self = this; + this.sendRequest(payload, function (request) { + self.handlers.forEach(function (handler) { + handler.call(self, formatJsonRpcMessage(request.responseText)); + }); + }); + }; + + HttpRpcProvider.prototype.poll = function (payload, id) { + var self = this; + this.sendRequest(payload, function (request) { + var parsed = JSON.parse(request.responseText); + if (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result) { + return; + } + self.handlers.forEach(function (handler) { + handler.call(self, {_event: payload.call, _id: id, data: parsed.result}); + }); + }); + }; + + Object.defineProperty(HttpRpcProvider.prototype, "onmessage", { + set: function (handler) { + this.handlers.push(handler); + } + }); + + if (typeof(web3) !== "undefined" && web3.providers !== undefined) { + web3.providers.HttpRpcProvider = HttpRpcProvider; + } +})(); + diff --git a/index.html b/index.html index fc658c00a..2b3f50a14 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ <script type="text/javascript" src="main.js"></script> <script type="text/javascript" src="websocket.js"></script> <script type="text/javascript" src="qt.js"></script> +<script type="text/javascript" src="httprpc.js"></script> <script type="text/javascript"> function registerName() { var name = document.querySelector("#name").value; @@ -3,6 +3,179 @@ return o instanceof Promise } + function flattenPromise (obj) { + if (obj instanceof Promise) { + return Promise.resolve(obj); + } + + if (obj instanceof Array) { + return new Promise(function (resolve) { + var promises = obj.map(function (o) { + return flattenPromise(o); + }); + + return Promise.all(promises).then(function (res) { + for (var i = 0; i < obj.length; i++) { + obj[i] = res[i]; + } + resolve(obj); + }); + }); + } + + if (obj instanceof Object) { + return new Promise(function (resolve) { + var keys = Object.keys(obj); + var promises = keys.map(function (key) { + return flattenPromise(obj[key]); + }); + + return Promise.all(promises).then(function (res) { + for (var i = 0; i < keys.length; i++) { + obj[keys[i]] = res[i]; + } + resolve(obj); + }); + }); + } + + return Promise.resolve(obj); + }; + + var ethMethods = function () { + var blockCall = function (args) { + return typeof args[0] === "string" ? "blockByHash" : "blockByNumber"; + }; + + var transactionCall = function (args) { + return typeof args[0] === "string" ? 'transactionByHash' : 'transactonByNumber'; + }; + + var uncleCall = function (args) { + return typeof args[0] === "string" ? 'uncleByHash' : 'uncleByNumber'; + }; + + var methods = [ + { name: 'balanceAt', call: 'balanceAt' }, + { name: 'stateAt', call: 'stateAt' }, + { name: 'countAt', call: 'countAt'}, + { name: 'codeAt', call: 'codeAt' }, + { name: 'transact', call: 'transact' }, + { name: 'call', call: 'call' }, + { name: 'block', call: blockCall }, + { name: 'transaction', call: transactionCall }, + { name: 'uncle', call: uncleCall }, + { name: 'compile', call: 'compile' } + ]; + return methods; + }; + + var ethProperties = function () { + return [ + { name: 'coinbase', getter: 'coinbase', setter: 'setCoinbase' }, + { name: 'listening', getter: 'listening', setter: 'setListening' }, + { name: 'mining', getter: 'mining', setter: 'setMining' }, + { name: 'gasPrice', getter: 'gasPrice' }, + { name: 'accounts', getter: 'accounts' }, + { name: 'peerCount', getter: 'peerCount' }, + { name: 'defaultBlock', getter: 'defaultBlock', setter: 'setDefaultBlock' }, + { name: 'number', getter: 'number'} + ]; + }; + + var dbMethods = function () { + return [ + { name: 'put', call: 'put' }, + { name: 'get', call: 'get' }, + { name: 'putString', call: 'putString' }, + { name: 'getString', call: 'getString' } + ]; + }; + + var shhMethods = function () { + return [ + { name: 'post', call: 'post' }, + { name: 'newIdentity', call: 'newIdentity' }, + { name: 'haveIdentity', call: 'haveIdentity' }, + { name: 'newGroup', call: 'newGroup' }, + { name: 'addToGroup', call: 'addToGroup' } + ]; + }; + + var ethWatchMethods = function () { + var newFilter = function (args) { + return typeof args[0] === 'string' ? 'newFilterString' : 'newFilter'; + }; + + return [ + { name: 'newFilter', call: newFilter }, + { name: 'uninstallFilter', call: 'uninstallFilter' }, + { name: 'getMessages', call: 'getMessages' } + ]; + }; + + var shhWatchMethods = function () { + return [ + { name: 'newFilter', call: 'shhNewFilter' }, + { name: 'uninstallFilter', call: 'shhUninstallFilter' }, + { name: 'getMessage', call: 'shhGetMessages' } + ]; + }; + + var setupMethods = function (obj, methods) { + methods.forEach(function (method) { + obj[method.name] = function () { + return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) { + var call = typeof method.call === "function" ? method.call(args) : method.call; + return {call: call, args: args}; + }).then(function (request) { + return new Promise(function (resolve, reject) { + web3.provider.send(request, function (result) { + if (result || typeof result === "boolean") { + resolve(result); + return; + } + reject(result); + }); + }); + }).catch(function( err) { + console.error(err); + }); + }; + }); + }; + + var setupProperties = function (obj, properties) { + properties.forEach(function (property) { + var proto = {}; + proto.get = function () { + return new Promise(function(resolve, reject) { + web3.provider.send({call: property.getter}, function(result) { + resolve(result); + }); + }); + }; + if (property.setter) { + proto.set = function (val) { + return flattenPromise([val]).then(function (args) { + return new Promise(function (resolve) { + web3.provider.send({call: property.setter, args: args}, function (result) { + if (result) { + resolve(result); + } else { + reject(result); + } + }); + }); + }).catch(function (err) { + console.error(err); + }); + } + } + Object.defineProperty(obj, property.name, proto); + }); + }; + var web3 = { _callbacks: {}, _events: {}, @@ -33,362 +206,112 @@ return str; }, - fromAscii: function(str, pad) { - if(pad === undefined) { - pad = 32 - } + toDecimal: function (val) { + return parseInt(val, 16); + }, + fromAscii: function(str, pad) { + pad = pad === undefined ? 32 : pad; var hex = this.toHex(str); - while(hex.length < pad*2) hex += "00"; - return hex }, eth: { prototype: Object(), - - - block: function(numberOrHash) { - return new Promise(function(resolve, reject) { - /* - var func; - if(typeof numberOrHash == "string") { - func = "getBlockByHash"; - } else { - func = "getBlockByNumber"; - } - */ - - web3.provider.send({call: /*func*/"block", args: [numberOrHash]}, function(block) { - if(block) - resolve(block); - else - reject("not found"); - - }); - }); - }, - - transaction: function(numberOrHash, nth) { - return new Promise(function(resolve, reject) { - reject("`transaction` not yet implemented") - }); - }, - - uncle: function(numberOrHash, nth) { - return new Promise(function(resolve, reject) { - reject("`uncle` not yet implemented") - }); - }, - - transact: function(params) { - if(params === undefined) { - params = {}; - } - - if(params.endowment !== undefined) - params.value = params.endowment; - if(params.code !== undefined) - params.data = params.code; - - - var promises = [] - if(isPromise(params.to)) { - promises.push(params.to.then(function(_to) { params.to = _to; })); - } - if(isPromise(params.from)) { - promises.push(params.from.then(function(_from) { params.from = _from; })); - } - - if(typeof params.data !== "object" || isPromise(params.data)) { - params.data = [params.data] - } - - var data = params.data; - for(var i = 0; i < params.data.length; i++) { - if(isPromise(params.data[i])) { - var promise = params.data[i]; - var _i = i; - promises.push(promise.then(function(_arg) { params.data[_i] = _arg; })); - } - } - - // Make sure everything is string - var fields = ["value", "gas", "gasPrice"]; - for(var i = 0; i < fields.length; i++) { - if(params[fields[i]] === undefined) { - params[fields[i]] = ""; - } - params[fields[i]] = params[fields[i]].toString(); - } - - // Load promises then call the last "transact". - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - params.data = params.data.join(""); - web3.provider.send({call: "transact", args: ["0x"+params]}, function(data) { - if(data[1]) - reject(data[0]); - else - resolve(data[0]); - }); - }); - }) - }, - - compile: function(code) { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "compile", args: [code]}, function(data) { - if(data[1]) - reject(data[0]); - else - resolve(data[0]); - }); - }); - }, - - balanceAt: function(address) { - var promises = []; - - if(isPromise(address)) { - promises.push(address.then(function(_address) { address = _address; })); - } - - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "balanceAt", args: [address]}, function(balance) { - resolve(balance); - }); - }); - }); + watch: function (params) { + return new Filter(params, ethWatch); }, + }, - countAt: function(address) { - var promises = []; - - if(isPromise(address)) { - promises.push(address.then(function(_address) { address = _address; })); - } - - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "countAt", args: [address]}, function(count) { - resolve(count); - }); - }); - }); - }, - - codeAt: function(address) { - var promises = []; - - if(isPromise(address)) { - promises.push(address.then(function(_address) { address = _address; })); - } - - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "codeAt", args: [address]}, function(code) { - resolve(code); - }); - }); - }); - }, - - storageAt: function(address, storageAddress) { - var promises = []; - - if(isPromise(address)) { - promises.push(address.then(function(_address) { address = _address; })); - } - - if(isPromise(storageAddress)) { - promises.push(storageAddress.then(function(_sa) { storageAddress = _sa; })); - } - - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "stateAt", args: [address, storageAddress]}, function(entry) { - resolve(entry); - }); - }); - }); - }, - - stateAt: function(address, storageAddress) { - return this.storageAt(address, storageAddress); - }, - - call: function(params) { - if(params === undefined) { - params = {}; - } - - if(params.endowment !== undefined) - params.value = params.endowment; - if(params.code !== undefined) - params.data = params.code; - - - var promises = [] - if(isPromise(params.to)) { - promises.push(params.to.then(function(_to) { params.to = _to; })); - } - if(isPromise(params.from)) { - promises.push(params.from.then(function(_from) { params.from = _from; })); - } - - if(isPromise(params.data)) { - promises.push(params.data.then(function(_code) { params.data = _code; })); - } else { - if(typeof params.data === "object") { - data = ""; - for(var i = 0; i < params.data.length; i++) { - data += params.data[i] - } - } else { - data = params.data; - } - } - - // Make sure everything is string - var fields = ["value", "gas", "gasPrice"]; - for(var i = 0; i < fields.length; i++) { - if(params[fields[i]] === undefined) { - params[fields[i]] = ""; - } - params[fields[i]] = params[fields[i]].toString(); - } - - // Load promises then call the last "transact". - return Promise.all(promises).then(function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "call", args: params}, function(data) { - if(data[1]) - reject(data[0]); - else - resolve(data[0]); - }); - }); - }) - }, + db: { + prototype: Object() + }, - watch: function(params) { - return new Filter(params); - }, + shh: { + prototype: Object(), + watch: function (params) { + return new Filter(params, shhWatch); + } }, - on: function(event, cb) { + on: function(event, id, cb) { if(web3._events[event] === undefined) { - web3._events[event] = []; + web3._events[event] = {}; } - web3._events[event].push(cb); - + web3._events[event][id] = cb; return this }, - off: function(event, cb) { + off: function(event, id) { if(web3._events[event] !== undefined) { - var callbacks = web3._events[event]; - for(var i = 0; i < callbacks.length; i++) { - if(callbacks[i] === cb) { - delete callbacks[i]; - } - } + delete web3._events[event][id]; } return this }, - trigger: function(event, data) { + trigger: function(event, id, data) { var callbacks = web3._events[event]; - if(callbacks !== undefined) { - for(var i = 0; i < callbacks.length; i++) { - // Figure out whether the returned data was an array - // array means multiple return arguments (multiple params) - if(data instanceof Array) { - callbacks[i].apply(this, data); - } else { - callbacks[i].call(this, data); - } - } + if (!callbacks || !callbacks[id]) { + return; } + var cb = callbacks[id]; + cb(data); }, }; var eth = web3.eth; - // Eth object properties - Object.defineProperty(eth, "gasPrice", { - get: function() { - return "10000000000000" - } - }); - - Object.defineProperty(eth, "coinbase", { - get: function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "coinbase"}, function(coinbase) { - resolve(coinbase); - }); - }); - }, - }); + setupMethods(eth, ethMethods()); + setupProperties(eth, ethProperties()); + setupMethods(web3.db, dbMethods()); + setupMethods(web3.shh, shhMethods()); - Object.defineProperty(eth, "listening", { - get: function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "listening"}, function(listening) { - resolve(listening); - }); - }); - }, - }); - - - Object.defineProperty(eth, "mining", { - get: function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "mining"}, function(mining) { - resolve(mining); - }); - }); - }, - }); - - Object.defineProperty(eth, "peerCount", { - get: function() { - return new Promise(function(resolve, reject) { - web3.provider.send({call: "peerCount"}, function(peerCount) { - resolve(peerCount); - }); - }); - }, - }); + var ethWatch = { + changed: 'changed' + }; + setupMethods(ethWatch, ethWatchMethods()); + var shhWatch = { + changed: 'shhChanged' + }; + setupMethods(shhWatch, shhWatchMethods()); var ProviderManager = function() { this.queued = []; + this.polls = []; this.ready = false; this.provider = undefined; this.id = 1; + + var self = this; + var poll = function () { + if (self.provider && self.provider.poll) { + self.polls.forEach(function (data) { + data.data._id = self.id; + self.id++; + self.provider.poll(data.data, data.id); + }); + } + setTimeout(poll, 12000); + }; + poll(); }; + ProviderManager.prototype.send = function(data, cb) { data._id = this.id; - if(cb) { + if (cb) { web3._callbacks[data._id] = cb; } - if(data.args === undefined) { - data.args = []; - } - + data.args = data.args || []; this.id++; if(this.provider !== undefined) { this.provider.send(data); } else { + console.warn("provider is not set"); this.queued.push(data); } }; @@ -412,40 +335,48 @@ ProviderManager.prototype.installed = function() { return this.provider !== undefined; }; + + ProviderManager.prototype.startPolling = function (data, pollId) { + if (!this.provider || !this.provider.poll) { + return; + } + this.polls.push({data: data, id: pollId}); + }; + + ProviderManager.prototype.stopPolling = function (pollId) { + for (var i = this.polls.length; i--;) { + var poll = this.polls[i]; + if (poll.id === pollId) { + this.polls.splice(i, 1); + } + } + }; + web3.provider = new ProviderManager(); web3.setProvider = function(provider) { provider.onmessage = messageHandler; - web3.provider.set(provider); - web3.provider.sendQueued(); }; - var filters = []; - var Filter = function(options) { - filters.push(this); - + var Filter = function(options, impl) { + this.impl = impl; this.callbacks = []; - this.options = options; - var call; - if(options === "chain") { - call = "newFilterString" - } else if(typeof options === "object") { - call = "newFilter" - } - - var self = this; // Cheaper than binding - this.promise = new Promise(function(resolve, reject) { - web3.provider.send({call: call, args: [options]}, function(id) { - self.id = id; - - resolve(id); - }); + var self = this; + this.promise = impl.newFilter(options); + this.promise.then(function (id) { + self.id = id; + web3.on(impl.changed, id, self.trigger.bind(self)); + web3.provider.startPolling({call: impl.changed, args: [id]}, id); }); }; + Filter.prototype.arrived = function(callback) { + this.changed(callback); + } + Filter.prototype.changed = function(callback) { var self = this; this.promise.then(function(id) { @@ -453,54 +384,39 @@ }); }; - Filter.prototype.trigger = function(messages, id) { - if(id == this.id) { - for(var i = 0; i < this.callbacks.length; i++) { - this.callbacks[i].call(this, messages); - } + Filter.prototype.trigger = function(messages) { + for(var i = 0; i < this.callbacks.length; i++) { + this.callbacks[i].call(this, messages); } }; Filter.prototype.uninstall = function() { - this.promise.then(function(id) { - web3.provider.send({call: "uninstallFilter", args:[id]}); + var self = this; + this.promise.then(function (id) { + self.impl.uninstallFilter(id); + web3.provider.stopPolling(id); + web3.off(impl.changed, id); }); }; Filter.prototype.messages = function() { - var self=this; - return Promise.all([this.promise]).then(function() { - var id = self.id - return new Promise(function(resolve, reject) { - web3.provider.send({call: "getMessages", args: [id]}, function(messages) { - resolve(messages); - }); - }); + var self = this; + return this.promise.then(function (id) { + return self.impl.getMessages(id); }); }; - // Register to the messages callback. "messages" will be emitted when new messages - // from the client have been created. - web3.on("messages", function(messages, id) { - for(var i = 0; i < filters.length; i++) { - filters[i].trigger(messages, id); - } - }); - - function messageHandler(ev) { - var data = JSON.parse(ev) - + function messageHandler(data) { if(data._event !== undefined) { - web3.trigger(data._event, data.data); - } else { - if(data._id) { - var cb = web3._callbacks[data._id]; - if(cb) { - cb.call(this, data.data) - - // Remove the "trigger" callback - delete web3._callbacks[ev._id]; - } + web3.trigger(data._event, data._id, data.data); + return; + } + + if(data._id) { + var cb = web3._callbacks[data._id]; + if (cb) { + cb.call(this, data.data) + delete web3._callbacks[data._id]; } } } @@ -515,4 +431,5 @@ */ window.web3 = web3; + })(this); @@ -1,12 +1,22 @@ (function() { - var QtProvider = function() {}; + var QtProvider = function() { + this.handlers = []; + + var self = this; + navigator.qt.onmessage = function (message) { + self.handlers.forEach(function (handler) { + handler.call(self, JSON.parse(message)); + }); + } + }; + QtProvider.prototype.send = function(payload) { navigator.qt.postData(JSON.stringify(payload)); }; Object.defineProperty(QtProvider.prototype, "onmessage", { set: function(handler) { - navigator.qt.onmessage = handler; + this.handlers.push(handler); }, }); diff --git a/websocket.js b/websocket.js index 297690b33..732a086f2 100644 --- a/websocket.js +++ b/websocket.js @@ -11,7 +11,7 @@ var self = this; this.ws.onmessage = function(event) { for(var i = 0; i < self.handlers.length; i++) { - self.handlers[i].call(self, event.data, event) + self.handlers[i].call(self, JSON.parse(event.data), event) } }; |