From 6db40ecb22c28a777f4ab1cd4de5a12e41ac669d Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 30 Sep 2014 23:26:16 +0200 Subject: [PATCH] WebSocket interface Web sockets handlers fully implemented. Filter handlers have yet to be implemented. --- ethereum/flags.go | 62 ++++----- ethereum/main.go | 4 + mist/assets/ext/html_messaging.js | 28 +---- mist/assets/ext/pre.js | 3 - mist/assets/ext/qt_messaging_adapter.js | 21 ++++ mist/assets/qml/webapp.qml | 2 +- utils/websockets.go | 161 ++++++++++++++++++++++++ 7 files changed, 226 insertions(+), 55 deletions(-) delete mode 100644 mist/assets/ext/pre.js create mode 100644 mist/assets/ext/qt_messaging_adapter.js create mode 100644 utils/websockets.go diff --git a/ethereum/flags.go b/ethereum/flags.go index c488e63143..58220f4e6b 100644 --- a/ethereum/flags.go +++ b/ethereum/flags.go @@ -10,36 +10,41 @@ import ( "github.com/ethereum/eth-go/ethlog" ) -var Identifier string -var KeyRing string -var DiffTool bool -var DiffType string -var KeyStore string -var StartRpc bool -var RpcPort int -var UseUPnP bool -var OutboundPort string -var ShowGenesis bool -var AddPeer string -var MaxPeer int -var GenAddr bool -var UseSeed bool -var SecretFile string -var ExportDir string -var NonInteractive bool -var Datadir string -var LogFile string -var ConfigFile string -var DebugFile string -var LogLevel int -var Dump bool -var DumpHash string -var DumpNumber int +var ( + Identifier string + KeyRing string + DiffTool bool + DiffType string + KeyStore string + StartRpc bool + StartWebSockets bool + RpcPort int + UseUPnP bool + OutboundPort string + ShowGenesis bool + AddPeer string + MaxPeer int + GenAddr bool + UseSeed bool + SecretFile string + ExportDir string + NonInteractive bool + Datadir string + LogFile string + ConfigFile string + DebugFile string + LogLevel int + Dump bool + DumpHash string + DumpNumber int +) // flags specific to cli client -var StartMining bool -var StartJsConsole bool -var InputFile string +var ( + StartMining bool + StartJsConsole bool + InputFile string +) func defaultDataDir() string { usr, _ := user.Current() @@ -62,6 +67,7 @@ func Init() { flag.IntVar(&MaxPeer, "maxpeer", 10, "maximum desired peers") flag.IntVar(&RpcPort, "rpcport", 8080, "port to start json-rpc server on") flag.BoolVar(&StartRpc, "rpc", false, "start rpc server") + flag.BoolVar(&StartWebSockets, "ws", false, "start websocket server") flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ethereum/main.go b/ethereum/main.go index 6521188ff6..0f0df20bbd 100644 --- a/ethereum/main.go +++ b/ethereum/main.go @@ -103,6 +103,10 @@ func main() { utils.StartRpc(ethereum, RpcPort) } + if StartWebSockets { + utils.StartWebSockets(ethereum) + } + utils.StartEthereum(ethereum, UseSeed) // this blocks the thread diff --git a/mist/assets/ext/html_messaging.js b/mist/assets/ext/html_messaging.js index 1f94181483..91310e998e 100644 --- a/mist/assets/ext/html_messaging.js +++ b/mist/assets/ext/html_messaging.js @@ -1,9 +1,8 @@ // The magic return variable. The magic return variable will be set during the execution of the QML call. (function(window) { - function message(type, data) { - document.title = JSON.stringify({type: type, data: data}); - - return window.____returnData; + var Promise = window.Promise; + if(typeof(Promise) === "undefined") { + var Promise = Q.Promise; } function isPromise(o) { @@ -446,6 +445,7 @@ } }); + var g_seed = 1; function postData(data, cb) { data._seed = g_seed; @@ -459,24 +459,6 @@ g_seed++; - navigator.qt.postMessage(JSON.stringify(data)); - } - - navigator.qt.onmessage = function(ev) { - var data = JSON.parse(ev.data) - - if(data._event !== undefined) { - eth.trigger(data._event, data.data); - } else { - if(data._seed) { - var cb = eth._callbacks[data._seed]; - if(cb) { - cb.call(this, data.data) - - // Remove the "trigger" callback - delete eth._callbacks[ev._seed]; - } - } - } + window._messagingAdapter.call(this, JSON.stringify(data)) } })(this); diff --git a/mist/assets/ext/pre.js b/mist/assets/ext/pre.js deleted file mode 100644 index f298fe9a13..0000000000 --- a/mist/assets/ext/pre.js +++ /dev/null @@ -1,3 +0,0 @@ -if(typeof(Promise) === "undefined") { - window.Promise = Q.Promise; -} diff --git a/mist/assets/ext/qt_messaging_adapter.js b/mist/assets/ext/qt_messaging_adapter.js new file mode 100644 index 0000000000..ff69761777 --- /dev/null +++ b/mist/assets/ext/qt_messaging_adapter.js @@ -0,0 +1,21 @@ +window._messagingAdapter = function(data) { + navigator.qt.postMessage(data); +}; + +navigator.qt.onmessage = function(ev) { + var data = JSON.parse(ev.data) + + if(data._event !== undefined) { + eth.trigger(data._event, data.data); + } else { + if(data._seed) { + var cb = eth._callbacks[data._seed]; + if(cb) { + cb.call(this, data.data) + + // Remove the "trigger" callback + delete eth._callbacks[ev._seed]; + } + } + } +} diff --git a/mist/assets/qml/webapp.qml b/mist/assets/qml/webapp.qml index dde2984848..09e6a83ad0 100644 --- a/mist/assets/qml/webapp.qml +++ b/mist/assets/qml/webapp.qml @@ -164,7 +164,7 @@ import "../ext/qml_messaging.js" as Messaging experimental.preferences.javascriptEnabled: true experimental.preferences.navigatorQtObjectEnabled: true experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: ["../ext/q.js", "../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] + experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] experimental.onMessageReceived: { console.log("[onMessageReceived]: ", message.data) // TODO move to messaging.js diff --git a/utils/websockets.go b/utils/websockets.go new file mode 100644 index 0000000000..e89331a98d --- /dev/null +++ b/utils/websockets.go @@ -0,0 +1,161 @@ +package utils + +import ( + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethpipe" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/websocket" +) + +func args(v ...interface{}) []interface{} { + return v +} + +type WebSocketServer struct { + ethereum *eth.Ethereum + filterCallbacks map[int][]int +} + +func NewWebSocketServer(eth *eth.Ethereum) *WebSocketServer { + return &WebSocketServer{eth, make(map[int][]int)} +} + +func (self *WebSocketServer) Serv() { + pipe := ethpipe.NewJSPipe(self.ethereum) + + wsServ := websocket.NewServer("/eth", ":40404") + wsServ.MessageFunc(func(c *websocket.Client, msg *websocket.Message) { + switch msg.Call { + case "compile": + data := ethutil.NewValue(msg.Args) + bcode, err := ethutil.Compile(data.Get(0).Str(), false) + if err != nil { + c.Write(args(nil, err.Error()), msg.Seed) + } + + code := ethutil.Bytes2Hex(bcode) + c.Write(args(code, nil), msg.Seed) + case "getBlockByNumber": + args := msg.Arguments() + + block := pipe.BlockByNumber(int32(args.Get(0).Uint())) + c.Write(block, msg.Seed) + + case "getKey": + c.Write(pipe.Key().PrivateKey, msg.Seed) + case "transact": + if mp, ok := msg.Args[0].(map[string]interface{}); ok { + object := mapToTxParams(mp) + c.Write( + args(pipe.Transact(object["from"], object["to"], object["value"], object["gas"], object["gasPrice"], object["data"])), + msg.Seed, + ) + + } + case "getCoinBase": + c.Write(pipe.CoinBase(), msg.Seed) + + case "getIsListening": + c.Write(pipe.IsListening(), msg.Seed) + + case "getIsMining": + c.Write(pipe.IsMining(), msg.Seed) + + case "getPeerCoint": + c.Write(pipe.PeerCount(), msg.Seed) + + case "getCountAt": + args := msg.Arguments() + + c.Write(pipe.TxCountAt(args.Get(0).Str()), msg.Seed) + + case "getCodeAt": + args := msg.Arguments() + + c.Write(len(pipe.CodeAt(args.Get(0).Str())), msg.Seed) + + case "getBlockByHash": + args := msg.Arguments() + + c.Write(pipe.BlockByHash(args.Get(0).Str()), msg.Seed) + + case "getStorageAt": + args := msg.Arguments() + + c.Write(pipe.StorageAt(args.Get(0).Str(), args.Get(1).Str()), msg.Seed) + + case "getBalanceAt": + args := msg.Arguments() + + c.Write(pipe.BalanceAt(args.Get(0).Str()), msg.Seed) + + case "getSecretToAddress": + args := msg.Arguments() + + c.Write(pipe.SecretToAddress(args.Get(0).Str()), msg.Seed) + + case "newFilter": + case "newFilterString": + case "messages": + // TODO + } + + }) + + wsServ.Listen() +} + +func StartWebSockets(eth *eth.Ethereum) { + sock := NewWebSocketServer(eth) + go sock.Serv() +} + +// TODO This is starting to become a generic method. Move to utils +func mapToTxParams(object map[string]interface{}) map[string]string { + // Default values + if object["from"] == nil { + object["from"] = "" + } + if object["to"] == nil { + object["to"] = "" + } + if object["value"] == nil { + object["value"] = "" + } + if object["gas"] == nil { + object["gas"] = "" + } + if object["gasPrice"] == nil { + object["gasPrice"] = "" + } + + var dataStr string + var data []string + if str, ok := object["data"].(string); ok { + data = []string{str} + } + + for _, str := range data { + if ethutil.IsHex(str) { + str = str[2:] + + if len(str) != 64 { + str = ethutil.LeftPadString(str, 64) + } + } else { + str = ethutil.Bytes2Hex(ethutil.LeftPadBytes(ethutil.Big(str).Bytes(), 32)) + } + + dataStr += str + } + object["data"] = dataStr + + conv := make(map[string]string) + for key, value := range object { + if v, ok := value.(string); ok { + conv[key] = v + } + } + + return conv +}