js.go 5.64 KB
Newer Older
obscuren's avatar
obscuren committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301  USA
Felix Lange's avatar
Felix Lange committed
17

18 19 20
package main

import (
21
	"bufio"
22
	"fmt"
23
	"os"
24 25
	"path"
	"strings"
26

zelig's avatar
zelig committed
27
	"github.com/ethereum/go-ethereum/cmd/utils"
28
	"github.com/ethereum/go-ethereum/core/types"
zelig's avatar
zelig committed
29
	"github.com/ethereum/go-ethereum/eth"
zelig's avatar
zelig committed
30 31
	re "github.com/ethereum/go-ethereum/jsre"
	"github.com/ethereum/go-ethereum/rpc"
obscuren's avatar
obscuren committed
32
	"github.com/ethereum/go-ethereum/xeth"
33
	"github.com/peterh/liner"
34 35
)

36 37 38 39 40 41
type prompter interface {
	AppendHistory(string)
	Prompt(p string) (string, error)
	PasswordPrompt(p string) (string, error)
}

42
type dumbterm struct{ r *bufio.Reader }
43

44
func (r dumbterm) Prompt(p string) (string, error) {
45 46
	fmt.Print(p)
	return r.r.ReadString('\n')
47
}
48

49
func (r dumbterm) PasswordPrompt(p string) (string, error) {
50 51 52 53 54 55 56
	fmt.Println("!! Unsupported terminal, password will echo.")
	fmt.Print(p)
	input, err := bufio.NewReader(os.Stdin).ReadString('\n')
	fmt.Println()
	return input, err
}

57
func (r dumbterm) AppendHistory(string) {}
58 59

type jsre struct {
zelig's avatar
zelig committed
60
	re       *re.JSRE
61 62
	ethereum *eth.Ethereum
	xeth     *xeth.XEth
63
	ps1      string
64
	atexit   func()
65

66
	prompter
67 68
}

zelig's avatar
zelig committed
69
func newJSRE(ethereum *eth.Ethereum, libPath string) *jsre {
70 71
	js := &jsre{ethereum: ethereum, ps1: "> "}
	js.xeth = xeth.New(ethereum, js)
zelig's avatar
zelig committed
72 73
	js.re = re.New(libPath)
	js.apiBindings()
74
	js.adminBindings()
75

76
	if !liner.TerminalSupported() {
77
		js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
78 79
	} else {
		lr := liner.NewLiner()
80
		js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
81
		lr.SetCtrlCAborts(true)
82
		js.prompter = lr
83 84 85 86
		js.atexit = func() {
			js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
			lr.Close()
		}
87
	}
88
	return js
89 90
}

zelig's avatar
zelig committed
91 92 93
func (js *jsre) apiBindings() {

	ethApi := rpc.NewEthereumApi(js.xeth, js.ethereum.DataDir)
94
	ethApi.Close()
95 96 97 98 99 100 101 102
	//js.re.Bind("jeth", rpc.NewJeth(ethApi, js.re.ToVal))

	jeth := rpc.NewJeth(ethApi, js.re.ToVal, js.re)
	//js.re.Bind("jeth", jeth)
	js.re.Set("jeth", struct{}{})
	t, _ := js.re.Get("jeth")
	jethObj := t.Object()
	jethObj.Set("send", jeth.Send)
zelig's avatar
zelig committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

	_, err := js.re.Eval(re.BigNumber_JS)

	if err != nil {
		utils.Fatalf("Error loading bignumber.js: %v", err)
	}

	// we need to declare a dummy setTimeout. Otto does not support it
	_, err = js.re.Eval("setTimeout = function(cb, delay) {};")
	if err != nil {
		utils.Fatalf("Error defining setTimeout: %v", err)
	}

	_, err = js.re.Eval(re.Ethereum_JS)
	if err != nil {
		utils.Fatalf("Error loading ethereum.js: %v", err)
	}

	_, err = js.re.Eval("var web3 = require('web3');")
	if err != nil {
		utils.Fatalf("Error requiring web3: %v", err)
	}

	_, err = js.re.Eval("web3.setProvider(jeth)")
	if err != nil {
		utils.Fatalf("Error setting web3 provider: %v", err)
	}
	_, err = js.re.Eval(`
	var eth = web3.eth;
  var shh = web3.shh;
  var db  = web3.db;
  var net = web3.net;
  `)
	if err != nil {
		utils.Fatalf("Error setting namespaces: %v", err)
	}

}

142 143
func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool {
	p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx)
144
	answer, _ := self.Prompt(p)
145
	return strings.HasPrefix(strings.Trim(answer, " "), "y")
146 147
}

148 149
func (self *jsre) UnlockAccount(addr []byte) bool {
	fmt.Printf("Please unlock account %x.\n", addr)
150
	pass, err := self.PasswordPrompt("Passphrase: ")
151
	if err != nil {
152 153 154 155 156 157 158 159
		return false
	}
	// TODO: allow retry
	if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil {
		return false
	} else {
		fmt.Println("Account is now unlocked for this session.")
		return true
160 161 162
	}
}

163
func (self *jsre) exec(filename string) error {
zelig's avatar
zelig committed
164
	if err := self.re.Exec(filename); err != nil {
165 166 167
		return fmt.Errorf("Javascript Error: %v", err)
	}
	return nil
168 169
}

170
func (self *jsre) interactive() {
171
	for {
172
		input, err := self.Prompt(self.ps1)
173
		if err != nil {
174
			break
175 176 177 178 179 180 181 182
		}
		if input == "" {
			continue
		}
		str += input + "\n"
		self.setIndent()
		if indentCount <= 0 {
			if input == "exit" {
183
				break
184 185
			}
			hist := str[:len(str)-1]
186
			self.AppendHistory(hist)
187 188 189 190
			self.parseInput(str)
			str = ""
		}
	}
191 192 193
	if self.atexit != nil {
		self.atexit()
	}
194 195
}

196 197 198 199 200 201 202 203 204 205 206 207 208 209
func (self *jsre) withHistory(op func(*os.File)) {
	hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
	if err != nil {
		fmt.Printf("unable to open history file: %v\n", err)
		return
	}
	op(hist)
	hist.Close()
}

func (self *jsre) parseInput(code string) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("[native] error", r)
210 211
		}
	}()
212 213 214 215 216 217 218
	value, err := self.re.Run(code)
	if err != nil {
		fmt.Println(err)
		return
	}
	self.printValue(value)
}
219

220 221
var indentCount = 0
var str = ""
222

223 224 225 226 227 228 229 230 231 232 233
func (self *jsre) setIndent() {
	open := strings.Count(str, "{")
	open += strings.Count(str, "(")
	closed := strings.Count(str, "}")
	closed += strings.Count(str, ")")
	indentCount = open - closed
	if indentCount <= 0 {
		self.ps1 = "> "
	} else {
		self.ps1 = strings.Join(make([]string, indentCount*2), "..")
		self.ps1 += " "
234 235 236
	}
}

237
func (self *jsre) printValue(v interface{}) {
zelig's avatar
zelig committed
238
	val, err := self.re.PrettyPrint(v)
239
	if err == nil {
zelig's avatar
zelig committed
240
		fmt.Printf("%v", val)
241 242
	}
}