Merge branch 'master' into lightrpc
This commit is contained in:
commit
7c9064c856
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -28,7 +28,7 @@ dependencies = [
|
|||||||
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -106,6 +106,11 @@ dependencies = [
|
|||||||
"syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base32"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bigint"
|
name = "bigint"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -414,6 +419,7 @@ dependencies = [
|
|||||||
name = "ethcore-dapps"
|
name = "ethcore-dapps"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ethcore-devtools 1.6.0",
|
"ethcore-devtools 1.6.0",
|
||||||
@ -422,7 +428,7 @@ dependencies = [
|
|||||||
"fetch 0.1.0",
|
"fetch 0.1.0",
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -595,11 +601,12 @@ dependencies = [
|
|||||||
"ethsync 1.6.0",
|
"ethsync 1.6.0",
|
||||||
"fetch 0.1.0",
|
"fetch 0.1.0",
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-reactor 0.1.0",
|
"parity-reactor 0.1.0",
|
||||||
"parity-updater 1.6.0",
|
"parity-updater 1.6.0",
|
||||||
"rlp 0.1.0",
|
"rlp 0.1.0",
|
||||||
@ -622,7 +629,7 @@ dependencies = [
|
|||||||
"ethcore-io 1.6.0",
|
"ethcore-io 1.6.0",
|
||||||
"ethcore-rpc 1.6.0",
|
"ethcore-rpc 1.6.0",
|
||||||
"ethcore-util 1.6.0",
|
"ethcore-util 1.6.0",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parity-ui 1.6.0",
|
"parity-ui 1.6.0",
|
||||||
@ -642,7 +649,7 @@ dependencies = [
|
|||||||
"ethcore-ipc-nano 1.6.0",
|
"ethcore-ipc-nano 1.6.0",
|
||||||
"ethcore-util 1.6.0",
|
"ethcore-util 1.6.0",
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-tcp-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1005,8 +1012,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-core"
|
name = "jsonrpc-core"
|
||||||
version = "5.0.0"
|
version = "5.1.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#5eeee0980e4d2682a831c633fa03a8af99e0d68c"
|
source = "git+https://github.com/ethcore/jsonrpc.git#d179ce34d8da8ea1cd67e93a5b4cb1e30f48c156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1019,11 +1026,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-http-server"
|
name = "jsonrpc-http-server"
|
||||||
version = "7.0.0"
|
version = "7.0.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#5eeee0980e4d2682a831c633fa03a8af99e0d68c"
|
source = "git+https://github.com/ethcore/jsonrpc.git#d179ce34d8da8ea1cd67e93a5b4cb1e30f48c156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tokio-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1032,11 +1038,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-ipc-server"
|
name = "jsonrpc-ipc-server"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#5eeee0980e4d2682a831c633fa03a8af99e0d68c"
|
source = "git+https://github.com/ethcore/jsonrpc.git#d179ce34d8da8ea1cd67e93a5b4cb1e30f48c156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1048,21 +1054,19 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-macros"
|
name = "jsonrpc-macros"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#5eeee0980e4d2682a831c633fa03a8af99e0d68c"
|
source = "git+https://github.com/ethcore/jsonrpc.git#d179ce34d8da8ea1cd67e93a5b4cb1e30f48c156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
|
||||||
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpc-tcp-server"
|
name = "jsonrpc-tcp-server"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "git+https://github.com/ethcore/jsonrpc.git#5eeee0980e4d2682a831c633fa03a8af99e0d68c"
|
source = "git+https://github.com/ethcore/jsonrpc.git#d179ce34d8da8ea1cd67e93a5b4cb1e30f48c156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1481,6 +1485,11 @@ dependencies = [
|
|||||||
"user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "order-stat"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owning_ref"
|
name = "owning_ref"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -1532,7 +1541,7 @@ dependencies = [
|
|||||||
"ethcore-signer 1.6.0",
|
"ethcore-signer 1.6.0",
|
||||||
"ethcore-util 1.6.0",
|
"ethcore-util 1.6.0",
|
||||||
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
"jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1563,7 +1572,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#416d00db677b8219f7548bb4dfa2f25c4b19f36e"
|
source = "git+https://github.com/ethcore/js-precompiled.git#4110b5bc85a15ae3f0b5c02b1c3caf8423f51b50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -2473,6 +2482,7 @@ dependencies = [
|
|||||||
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
|
"checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4"
|
||||||
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
"checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975"
|
||||||
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
||||||
|
"checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c"
|
||||||
"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae"
|
"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae"
|
||||||
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
||||||
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
|
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
|
||||||
@ -2525,7 +2535,7 @@ dependencies = [
|
|||||||
"checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a"
|
"checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a"
|
||||||
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1"
|
||||||
"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5"
|
"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5"
|
||||||
"checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-core 5.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
"checksum jsonrpc-macros 0.2.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||||
@ -2574,6 +2584,7 @@ dependencies = [
|
|||||||
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
||||||
"checksum openssl 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "12be61c7eaa23228316ff02c39807e4c1b1af84ba81420f19fd58dade304b25c"
|
"checksum openssl 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "12be61c7eaa23228316ff02c39807e4c1b1af84ba81420f19fd58dade304b25c"
|
||||||
"checksum openssl-sys 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d2845e841700e7b04282ceaa115407ea84e0db918ae689ad9ceb6f06fa6046bd"
|
"checksum openssl-sys 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d2845e841700e7b04282ceaa115407ea84e0db918ae689ad9ceb6f06fa6046bd"
|
||||||
|
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
||||||
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
||||||
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
||||||
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
||||||
|
@ -23,6 +23,7 @@ serde = "0.8"
|
|||||||
serde_json = "0.8"
|
serde_json = "0.8"
|
||||||
linked-hash-map = "0.3"
|
linked-hash-map = "0.3"
|
||||||
parity-dapps-glue = "1.4"
|
parity-dapps-glue = "1.4"
|
||||||
|
base32 = "0.3"
|
||||||
mime = "0.2"
|
mime = "0.2"
|
||||||
mime_guess = "1.6.1"
|
mime_guess = "1.6.1"
|
||||||
time = "0.1.35"
|
time = "0.1.35"
|
||||||
|
@ -123,6 +123,7 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
|
|||||||
return Next::write();
|
return Next::write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO [ToDr] Consider using `path.app_params` instead
|
||||||
let url = extract_url(&request);
|
let url = extract_url(&request);
|
||||||
if url.is_none() {
|
if url.is_none() {
|
||||||
// Just return 404 if we can't parse URL
|
// Just return 404 if we can't parse URL
|
||||||
|
@ -32,8 +32,8 @@ pub mod manifest;
|
|||||||
|
|
||||||
extern crate parity_ui;
|
extern crate parity_ui;
|
||||||
|
|
||||||
pub const HOME_PAGE: &'static str = "home";
|
pub const HOME_PAGE: &'static str = "parity";
|
||||||
pub const DAPPS_DOMAIN: &'static str = ".parity";
|
pub const DAPPS_DOMAIN: &'static str = ".web3.site";
|
||||||
pub const RPC_PATH: &'static str = "rpc";
|
pub const RPC_PATH: &'static str = "rpc";
|
||||||
pub const API_PATH: &'static str = "api";
|
pub const API_PATH: &'static str = "api";
|
||||||
pub const UTILS_PATH: &'static str = "parity-utils";
|
pub const UTILS_PATH: &'static str = "parity-utils";
|
||||||
|
@ -22,6 +22,7 @@ use std::collections::BTreeMap;
|
|||||||
#[derive(Debug, PartialEq, Default, Clone)]
|
#[derive(Debug, PartialEq, Default, Clone)]
|
||||||
pub struct EndpointPath {
|
pub struct EndpointPath {
|
||||||
pub app_id: String,
|
pub app_id: String,
|
||||||
|
pub app_params: Vec<String>,
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub using_dapps_domains: bool,
|
pub using_dapps_domains: bool,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![cfg_attr(feature="nightly", plugin(clippy))]
|
#![cfg_attr(feature="nightly", plugin(clippy))]
|
||||||
|
|
||||||
|
extern crate base32;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate url as url_lib;
|
extern crate url as url_lib;
|
||||||
@ -69,9 +70,10 @@ use std::sync::{Arc, Mutex};
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use ethcore_rpc::Metadata;
|
use ethcore_rpc::{Metadata};
|
||||||
use fetch::{Fetch, Client as FetchClient};
|
use fetch::{Fetch, Client as FetchClient};
|
||||||
use hash_fetch::urlhint::ContractClient;
|
use hash_fetch::urlhint::ContractClient;
|
||||||
|
use jsonrpc_core::Middleware;
|
||||||
use jsonrpc_core::reactor::RpcHandler;
|
use jsonrpc_core::reactor::RpcHandler;
|
||||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||||
use parity_reactor::Remote;
|
use parity_reactor::Remote;
|
||||||
@ -91,11 +93,11 @@ impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
|
|||||||
/// Validates Web Proxy tokens
|
/// Validates Web Proxy tokens
|
||||||
pub trait WebProxyTokens: Send + Sync {
|
pub trait WebProxyTokens: Send + Sync {
|
||||||
/// Should return true if token is a valid web proxy access token.
|
/// Should return true if token is a valid web proxy access token.
|
||||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool;
|
fn is_web_proxy_token_valid(&self, token: &str) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
|
||||||
fn is_web_proxy_token_valid(&self, token: &String) -> bool { self(token.to_owned()) }
|
fn is_web_proxy_token_valid(&self, token: &str) -> bool { self(token.to_owned()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Webapps HTTP+RPC server build.
|
/// Webapps HTTP+RPC server build.
|
||||||
@ -178,7 +180,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
|
|
||||||
/// Asynchronously start server with no authentication,
|
/// Asynchronously start server with no authentication,
|
||||||
/// returns result with `Server` handle on success or an error.
|
/// returns result with `Server` handle on success or an error.
|
||||||
pub fn start_unsecured_http(self, addr: &SocketAddr, handler: RpcHandler<Metadata>) -> Result<Server, ServerError> {
|
pub fn start_unsecured_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
|
||||||
let fetch = self.fetch_client()?;
|
let fetch = self.fetch_client()?;
|
||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
@ -198,7 +200,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
|
|
||||||
/// Asynchronously start server with `HTTP Basic Authentication`,
|
/// Asynchronously start server with `HTTP Basic Authentication`,
|
||||||
/// return result with `Server` handle on success or an error.
|
/// return result with `Server` handle on success or an error.
|
||||||
pub fn start_basic_auth_http(self, addr: &SocketAddr, username: &str, password: &str, handler: RpcHandler<Metadata>) -> Result<Server, ServerError> {
|
pub fn start_basic_auth_http<S: Middleware<Metadata>>(self, addr: &SocketAddr, username: &str, password: &str, handler: RpcHandler<Metadata, S>) -> Result<Server, ServerError> {
|
||||||
let fetch = self.fetch_client()?;
|
let fetch = self.fetch_client()?;
|
||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
@ -257,11 +259,11 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_http<A: Authorization + 'static, F: Fetch>(
|
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
hosts: Option<Vec<String>>,
|
hosts: Option<Vec<String>>,
|
||||||
authorization: A,
|
authorization: A,
|
||||||
handler: RpcHandler<Metadata>,
|
handler: RpcHandler<Metadata, T>,
|
||||||
dapps_path: PathBuf,
|
dapps_path: PathBuf,
|
||||||
extra_dapps: Vec<PathBuf>,
|
extra_dapps: Vec<PathBuf>,
|
||||||
signer_address: Option<(String, u16)>,
|
signer_address: Option<(String, u16)>,
|
||||||
@ -409,6 +411,6 @@ mod util_tests {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(none, Vec::<String>::new());
|
assert_eq!(none, Vec::<String>::new());
|
||||||
assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]);
|
assert_eq!(some, vec!["http://parity.web3.site".to_owned(), "http://127.0.0.1:18180".into()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,7 @@ fn should_extract_path_with_appid() {
|
|||||||
prefix: None,
|
prefix: None,
|
||||||
path: EndpointPath {
|
path: EndpointPath {
|
||||||
app_id: "app".to_owned(),
|
app_id: "app".to_owned(),
|
||||||
|
app_params: vec![],
|
||||||
host: "".to_owned(),
|
host: "".to_owned(),
|
||||||
port: 8080,
|
port: 8080,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
|
@ -97,9 +97,7 @@ impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
|||||||
=>
|
=>
|
||||||
{
|
{
|
||||||
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
|
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
|
||||||
// TODO [ToDr] Some nice util for this!
|
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
|
||||||
let using_domain = if referer.using_dapps_domains { 0 } else { 1 };
|
|
||||||
let len = cmp::min(referer_url.path.len(), using_domain + 3); // token + protocol + hostname
|
|
||||||
let base = referer_url.path[..len].join("/");
|
let base = referer_url.path[..len].join("/");
|
||||||
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
|
||||||
Redirection::boxed(&format!("/{}/{}", base, requested))
|
Redirection::boxed(&format!("/{}/{}", base, requested))
|
||||||
@ -262,20 +260,27 @@ fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint
|
|||||||
match *url {
|
match *url {
|
||||||
Some(ref url) => match url.host {
|
Some(ref url) => match url.host {
|
||||||
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
|
||||||
let len = domain.len() - DAPPS_DOMAIN.len();
|
let id = &domain[0..(domain.len() - DAPPS_DOMAIN.len())];
|
||||||
let id = domain[0..len].to_owned();
|
let (id, params) = if let Some(split) = id.rfind('.') {
|
||||||
|
let (params, id) = id.split_at(split);
|
||||||
|
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
|
||||||
|
} else {
|
||||||
|
(id.to_owned(), url.path.clone())
|
||||||
|
};
|
||||||
|
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: id,
|
app_id: id,
|
||||||
|
app_params: params,
|
||||||
host: domain.clone(),
|
host: domain.clone(),
|
||||||
port: url.port,
|
port: url.port,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
}), special_endpoint(url))
|
}), special_endpoint(url))
|
||||||
},
|
},
|
||||||
_ if url.path.len() > 1 => {
|
_ if url.path.len() > 1 => {
|
||||||
let id = url.path[0].clone();
|
let id = url.path[0].to_owned();
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: id.clone(),
|
app_id: id,
|
||||||
|
app_params: url.path[1..].to_vec(),
|
||||||
host: format!("{}", url.host),
|
host: format!("{}", url.host),
|
||||||
port: url.port,
|
port: url.port,
|
||||||
using_dapps_domains: false,
|
using_dapps_domains: false,
|
||||||
@ -296,6 +301,7 @@ fn should_extract_endpoint() {
|
|||||||
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
|
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
|
app_params: vec!["index.html".to_owned()],
|
||||||
host: "localhost".to_owned(),
|
host: "localhost".to_owned(),
|
||||||
port: 8080,
|
port: 8080,
|
||||||
using_dapps_domains: false,
|
using_dapps_domains: false,
|
||||||
@ -307,6 +313,7 @@ fn should_extract_endpoint() {
|
|||||||
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "rpc".to_owned(),
|
app_id: "rpc".to_owned(),
|
||||||
|
app_params: vec!["".to_owned()],
|
||||||
host: "localhost".to_owned(),
|
host: "localhost".to_owned(),
|
||||||
port: 8080,
|
port: 8080,
|
||||||
using_dapps_domains: false,
|
using_dapps_domains: false,
|
||||||
@ -314,10 +321,11 @@ fn should_extract_endpoint() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
|
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "my.status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
host: "my.status.parity".to_owned(),
|
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
|
||||||
|
host: "my.status.web3.site".to_owned(),
|
||||||
port: 80,
|
port: 80,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
}), SpecialEndpoint::Utils)
|
}), SpecialEndpoint::Utils)
|
||||||
@ -325,10 +333,11 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// By Subdomain
|
// By Subdomain
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
|
extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "my.status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
host: "my.status.parity".to_owned(),
|
app_params: vec!["test.html".to_owned()],
|
||||||
|
host: "status.web3.site".to_owned(),
|
||||||
port: 80,
|
port: 80,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
}), SpecialEndpoint::None)
|
}), SpecialEndpoint::None)
|
||||||
@ -336,10 +345,11 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// RPC by subdomain
|
// RPC by subdomain
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
|
extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "my.status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
host: "my.status.parity".to_owned(),
|
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
|
||||||
|
host: "my.status.web3.site".to_owned(),
|
||||||
port: 80,
|
port: 80,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
}), SpecialEndpoint::Rpc)
|
}), SpecialEndpoint::Rpc)
|
||||||
@ -347,10 +357,11 @@ fn should_extract_endpoint() {
|
|||||||
|
|
||||||
// API by subdomain
|
// API by subdomain
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
|
extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok()),
|
||||||
(Some(EndpointPath {
|
(Some(EndpointPath {
|
||||||
app_id: "my.status".to_owned(),
|
app_id: "status".to_owned(),
|
||||||
host: "my.status.parity".to_owned(),
|
app_params: vec!["my".to_owned(), "api".into(), "".into()],
|
||||||
|
host: "my.status.web3.site".to_owned(),
|
||||||
port: 80,
|
port: 80,
|
||||||
using_dapps_domains: true,
|
using_dapps_domains: true,
|
||||||
}), SpecialEndpoint::Api)
|
}), SpecialEndpoint::Api)
|
||||||
|
@ -18,11 +18,15 @@ use std::sync::{Arc, Mutex};
|
|||||||
use hyper;
|
use hyper;
|
||||||
|
|
||||||
use ethcore_rpc::{Metadata, Origin};
|
use ethcore_rpc::{Metadata, Origin};
|
||||||
|
use jsonrpc_core::Middleware;
|
||||||
use jsonrpc_core::reactor::RpcHandler;
|
use jsonrpc_core::reactor::RpcHandler;
|
||||||
use jsonrpc_http_server::{Rpc, ServerHandler, PanicHandler, AccessControlAllowOrigin, HttpMetaExtractor};
|
use jsonrpc_http_server::{Rpc, ServerHandler, PanicHandler, AccessControlAllowOrigin, HttpMetaExtractor};
|
||||||
use endpoint::{Endpoint, EndpointPath, Handler};
|
use endpoint::{Endpoint, EndpointPath, Handler};
|
||||||
|
|
||||||
pub fn rpc(handler: RpcHandler<Metadata>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
|
pub fn rpc<T: Middleware<Metadata>>(
|
||||||
|
handler: RpcHandler<Metadata, T>,
|
||||||
|
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||||
|
) -> Box<Endpoint> {
|
||||||
Box::new(RpcEndpoint {
|
Box::new(RpcEndpoint {
|
||||||
handler: handler,
|
handler: handler,
|
||||||
meta_extractor: Arc::new(MetadataExtractor),
|
meta_extractor: Arc::new(MetadataExtractor),
|
||||||
@ -33,15 +37,15 @@ pub fn rpc(handler: RpcHandler<Metadata>, panic_handler: Arc<Mutex<Option<Box<Fn
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RpcEndpoint {
|
struct RpcEndpoint<T: Middleware<Metadata>> {
|
||||||
handler: RpcHandler<Metadata>,
|
handler: RpcHandler<Metadata, T>,
|
||||||
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
|
meta_extractor: Arc<HttpMetaExtractor<Metadata>>,
|
||||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||||
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
|
||||||
allowed_hosts: Option<Vec<String>>,
|
allowed_hosts: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint for RpcEndpoint {
|
impl<T: Middleware<Metadata>> Endpoint for RpcEndpoint<T> {
|
||||||
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
|
||||||
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
||||||
Box::new(ServerHandler::new(
|
Box::new(ServerHandler::new(
|
||||||
|
@ -143,7 +143,7 @@ fn should_return_signer_port_cors_headers_for_home_parity() {
|
|||||||
"\
|
"\
|
||||||
POST /api/ping HTTP/1.1\r\n\
|
POST /api/ping HTTP/1.1\r\n\
|
||||||
Host: localhost:8080\r\n\
|
Host: localhost:8080\r\n\
|
||||||
Origin: http://home.parity\r\n\
|
Origin: http://parity.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
{}
|
{}
|
||||||
@ -153,8 +153,8 @@ fn should_return_signer_port_cors_headers_for_home_parity() {
|
|||||||
// then
|
// then
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||||
assert!(
|
assert!(
|
||||||
response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"),
|
response.headers_raw.contains("Access-Control-Allow-Origin: http://parity.web3.site"),
|
||||||
"CORS header for home.parity missing: {:?}",
|
"CORS header for parity.web3.site missing: {:?}",
|
||||||
response.headers
|
response.headers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ fn should_resolve_dapp() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -52,7 +52,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
|
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -81,7 +81,7 @@ fn should_return_502_on_hash_mismatch() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.parity\r\n\
|
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -112,7 +112,7 @@ fn should_return_error_for_invalid_dapp_zip() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -144,7 +144,7 @@ fn should_return_fetched_dapp_content() {
|
|||||||
let response1 = http_client::request(server.addr(),
|
let response1 = http_client::request(server.addr(),
|
||||||
"\
|
"\
|
||||||
GET /index.html HTTP/1.1\r\n\
|
GET /index.html HTTP/1.1\r\n\
|
||||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\
|
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -152,7 +152,7 @@ fn should_return_fetched_dapp_content() {
|
|||||||
let response2 = http_client::request(server.addr(),
|
let response2 = http_client::request(server.addr(),
|
||||||
"\
|
"\
|
||||||
GET /manifest.json HTTP/1.1\r\n\
|
GET /manifest.json HTTP/1.1\r\n\
|
||||||
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.parity\r\n\
|
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -207,7 +207,7 @@ fn should_return_fetched_content() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -234,7 +234,7 @@ fn should_cache_content() {
|
|||||||
);
|
);
|
||||||
let request_str = "\
|
let request_str = "\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
";
|
";
|
||||||
@ -265,7 +265,7 @@ fn should_not_request_content_twice() {
|
|||||||
);
|
);
|
||||||
let request_str = "\
|
let request_str = "\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.parity\r\n\
|
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
";
|
";
|
||||||
@ -298,6 +298,17 @@ fn should_not_request_content_twice() {
|
|||||||
response2.assert_status("HTTP/1.1 200 OK");
|
response2.assert_status("HTTP/1.1 200 OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_encode_and_decode_base32() {
|
||||||
|
use base32;
|
||||||
|
|
||||||
|
let encoded = base32::encode(base32::Alphabet::Crockford, "token+https://parity.io".as_bytes());
|
||||||
|
assert_eq!("EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY", &encoded);
|
||||||
|
|
||||||
|
let data = base32::decode(base32::Alphabet::Crockford, "EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY").unwrap();
|
||||||
|
assert_eq!("token+https://parity.io", &String::from_utf8(data).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_stream_web_content() {
|
fn should_stream_web_content() {
|
||||||
// given
|
// given
|
||||||
@ -306,8 +317,8 @@ fn should_stream_web_content() {
|
|||||||
// when
|
// when
|
||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET /web/token/https/parity.io/ HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: localhost:8080\r\n\
|
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -322,20 +333,90 @@ fn should_stream_web_content() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_return_error_on_invalid_token() {
|
fn should_support_base32_encoded_web_urls() {
|
||||||
// given
|
// given
|
||||||
let (server, fetch) = serve_with_fetch("token");
|
let (server, fetch) = serve_with_fetch("token");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET /web/invalidtoken/https/parity.io/ HTTP/1.1\r\n\
|
GET /styles.css?test=123 HTTP/1.1\r\n\
|
||||||
|
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
response.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_security_headers_for_embed(&response.headers);
|
||||||
|
|
||||||
|
fetch.assert_requested("https://parity.io/styles.css?test=123");
|
||||||
|
fetch.assert_no_more_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_correctly_handle_long_label_when_splitted() {
|
||||||
|
// given
|
||||||
|
let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET /styles.css?test=123 HTTP/1.1\r\n\
|
||||||
|
Host: f1qprwk775k6am35a5wmpk3e9gnpgx3me1sk.mbsfcdqpwx3jd5h7ax39dxq2wvb5dhqpww3fe9t2wrvfdm.web.web3.site\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
response.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_security_headers_for_embed(&response.headers);
|
||||||
|
|
||||||
|
fetch.assert_requested("https://contribution.melonport.com/styles.css?test=123");
|
||||||
|
fetch.assert_no_more_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_support_base32_encoded_web_urls_as_path() {
|
||||||
|
// given
|
||||||
|
let (server, fetch) = serve_with_fetch("token");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET /web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css?test=123 HTTP/1.1\r\n\
|
||||||
Host: localhost:8080\r\n\
|
Host: localhost:8080\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
response.assert_status("HTTP/1.1 200 OK");
|
||||||
|
assert_security_headers_for_embed(&response.headers);
|
||||||
|
|
||||||
|
fetch.assert_requested("https://parity.io/styles.css?test=123");
|
||||||
|
fetch.assert_no_more_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_error_on_invalid_token() {
|
||||||
|
// given
|
||||||
|
let (server, fetch) = serve_with_fetch("test");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET / HTTP/1.1\r\n\
|
||||||
|
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
response.assert_status("HTTP/1.1 400 Bad Request");
|
response.assert_status("HTTP/1.1 400 Bad Request");
|
||||||
assert_security_headers_for_embed(&response.headers);
|
assert_security_headers_for_embed(&response.headers);
|
||||||
@ -365,28 +446,6 @@ fn should_return_error_on_invalid_protocol() {
|
|||||||
fetch.assert_no_more_requests();
|
fetch.assert_no_more_requests();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_redirect_if_trailing_slash_is_missing() {
|
|
||||||
// given
|
|
||||||
let (server, fetch) = serve_with_fetch("token");
|
|
||||||
|
|
||||||
// when
|
|
||||||
let response = request(server,
|
|
||||||
"\
|
|
||||||
GET /web/token/https/parity.io HTTP/1.1\r\n\
|
|
||||||
Host: localhost:8080\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
"
|
|
||||||
);
|
|
||||||
|
|
||||||
// then
|
|
||||||
response.assert_status("HTTP/1.1 302 Found");
|
|
||||||
response.assert_header("Location", "/web/token/https/parity.io/");
|
|
||||||
|
|
||||||
fetch.assert_no_more_requests();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_disallow_non_get_requests() {
|
fn should_disallow_non_get_requests() {
|
||||||
// given
|
// given
|
||||||
@ -395,8 +454,8 @@ fn should_disallow_non_get_requests() {
|
|||||||
// when
|
// when
|
||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
POST /token/https/parity.io/ HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: web.parity\r\n\
|
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
|
||||||
Content-Type: application/json\r\n\
|
Content-Type: application/json\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
@ -423,14 +482,37 @@ fn should_fix_absolute_requests_based_on_referer() {
|
|||||||
GET /styles.css HTTP/1.1\r\n\
|
GET /styles.css HTTP/1.1\r\n\
|
||||||
Host: localhost:8080\r\n\
|
Host: localhost:8080\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
Referer: http://localhost:8080/web/token/https/parity.io/\r\n\
|
Referer: http://localhost:8080/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
response.assert_status("HTTP/1.1 302 Found");
|
response.assert_status("HTTP/1.1 302 Found");
|
||||||
response.assert_header("Location", "/web/token/https/parity.io/styles.css");
|
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
|
||||||
|
|
||||||
|
fetch.assert_no_more_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_fix_absolute_requests_based_on_referer_in_url() {
|
||||||
|
// given
|
||||||
|
let (server, fetch) = serve_with_fetch("token");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
GET /styles.css HTTP/1.1\r\n\
|
||||||
|
Host: localhost:8080\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
Referer: http://localhost:8080/?__referer=web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
response.assert_status("HTTP/1.1 302 Found");
|
||||||
|
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
|
||||||
|
|
||||||
fetch.assert_no_more_requests();
|
fetch.assert_no_more_requests();
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ fn should_display_404_on_invalid_dapp_with_domain() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: invaliddapp.parity\r\n\
|
Host: invaliddapp.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
"
|
"
|
||||||
@ -179,7 +179,7 @@ fn should_serve_proxy_pac() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
|
||||||
assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
assert_eq!(response.body, "DD\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"parity.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
|
||||||
assert_security_headers(&response.headers);
|
assert_security_headers(&response.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ use tests::helpers::{serve_with_rpc, request};
|
|||||||
#[test]
|
#[test]
|
||||||
fn should_serve_rpc() {
|
fn should_serve_rpc() {
|
||||||
// given
|
// given
|
||||||
let mut io = MetaIoHandler::new();
|
let mut io = MetaIoHandler::default();
|
||||||
io.add_method("rpc_test", |_| {
|
io.add_method("rpc_test", |_| {
|
||||||
Ok(Value::String("Hello World!".into()))
|
Ok(Value::String("Hello World!".into()))
|
||||||
});
|
});
|
||||||
@ -53,7 +53,7 @@ fn should_serve_rpc() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn should_extract_metadata() {
|
fn should_extract_metadata() {
|
||||||
// given
|
// given
|
||||||
let mut io = MetaIoHandler::new();
|
let mut io = MetaIoHandler::default();
|
||||||
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
|
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
|
||||||
assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned()));
|
assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned()));
|
||||||
assert_eq!(meta.origin, Origin::Dapps);
|
assert_eq!(meta.origin, Origin::Dapps);
|
||||||
@ -87,7 +87,7 @@ fn should_extract_metadata() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn should_extract_metadata_from_custom_header() {
|
fn should_extract_metadata_from_custom_header() {
|
||||||
// given
|
// given
|
||||||
let mut io = MetaIoHandler::new();
|
let mut io = MetaIoHandler::default();
|
||||||
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
|
io.add_method_with_meta("rpc_test", |_params, meta: Metadata| {
|
||||||
assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned()));
|
assert_eq!(meta.dapp_id, Some("https://parity.io/".to_owned()));
|
||||||
assert_eq!(meta.origin, Origin::Dapps);
|
assert_eq!(meta.origin, Origin::Dapps);
|
||||||
|
@ -66,7 +66,7 @@ fn should_serve_dapps_domains() {
|
|||||||
let response = request(server,
|
let response = request(server,
|
||||||
"\
|
"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: ui.parity\r\n\
|
Host: ui.web3.site\r\n\
|
||||||
Connection: close\r\n\
|
Connection: close\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
{}
|
{}
|
||||||
|
@ -20,6 +20,7 @@ use std::sync::Arc;
|
|||||||
use fetch::{self, Fetch};
|
use fetch::{self, Fetch};
|
||||||
use parity_reactor::Remote;
|
use parity_reactor::Remote;
|
||||||
|
|
||||||
|
use base32;
|
||||||
use hyper::{self, server, net, Next, Encoder, Decoder};
|
use hyper::{self, server, net, Next, Encoder, Decoder};
|
||||||
use hyper::status::StatusCode;
|
use hyper::status::StatusCode;
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ use apps;
|
|||||||
use endpoint::{Endpoint, Handler, EndpointPath};
|
use endpoint::{Endpoint, Handler, EndpointPath};
|
||||||
use handlers::{
|
use handlers::{
|
||||||
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
|
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
|
||||||
StreamingHandler, Redirection, extract_url,
|
StreamingHandler, extract_url,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use WebProxyTokens;
|
use WebProxyTokens;
|
||||||
@ -86,9 +87,10 @@ impl ContentValidator for WebInstaller {
|
|||||||
);
|
);
|
||||||
if is_html {
|
if is_html {
|
||||||
handler.set_initial_content(&format!(
|
handler.set_initial_content(&format!(
|
||||||
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}")</script>"#,
|
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
|
||||||
apps::UTILS_PATH,
|
apps::UTILS_PATH,
|
||||||
apps::URL_REFERER,
|
apps::URL_REFERER,
|
||||||
|
apps::WEB_PATH,
|
||||||
&self.referer,
|
&self.referer,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -99,7 +101,6 @@ impl ContentValidator for WebInstaller {
|
|||||||
enum State<F: Fetch> {
|
enum State<F: Fetch> {
|
||||||
Initial,
|
Initial,
|
||||||
Error(ContentHandler),
|
Error(ContentHandler),
|
||||||
Redirecting(Redirection),
|
|
||||||
Fetching(ContentFetcherHandler<WebInstaller, F>),
|
Fetching(ContentFetcherHandler<WebInstaller, F>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,25 +115,26 @@ struct WebHandler<F: Fetch> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<F: Fetch> WebHandler<F> {
|
impl<F: Fetch> WebHandler<F> {
|
||||||
fn extract_target_url(&self, url: Option<Url>) -> Result<(String, String), State<F>> {
|
fn extract_target_url(&self, url: Option<Url>) -> Result<String, State<F>> {
|
||||||
let (path, query) = match url {
|
let token_and_url = self.path.app_params.get(0)
|
||||||
Some(url) => (url.path, url.query),
|
.map(|encoded| encoded.replace('.', ""))
|
||||||
None => {
|
.and_then(|encoded| base32::decode(base32::Alphabet::Crockford, &encoded.to_uppercase()))
|
||||||
return Err(State::Error(ContentHandler::error(
|
.and_then(|data| String::from_utf8(data).ok())
|
||||||
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
|
.ok_or_else(|| State::Error(ContentHandler::error(
|
||||||
)));
|
StatusCode::BadRequest,
|
||||||
}
|
"Invalid parameter",
|
||||||
};
|
"Couldn't parse given parameter:",
|
||||||
|
self.path.app_params.get(0).map(String::as_str),
|
||||||
|
self.embeddable_on.clone()
|
||||||
|
)))?;
|
||||||
|
|
||||||
// Support domain based routing.
|
let mut token_it = token_and_url.split('+');
|
||||||
let idx = match path.get(0).map(|m| m.as_ref()) {
|
let token = token_it.next();
|
||||||
Some(apps::WEB_PATH) => 1,
|
let target_url = token_it.next();
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if token supplied in URL is correct.
|
// Check if token supplied in URL is correct.
|
||||||
match path.get(idx) {
|
match token {
|
||||||
Some(ref token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
|
Some(token) if self.web_proxy_tokens.is_web_proxy_token_valid(token) => {},
|
||||||
_ => {
|
_ => {
|
||||||
return Err(State::Error(ContentHandler::error(
|
return Err(State::Error(ContentHandler::error(
|
||||||
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
|
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
|
||||||
@ -141,9 +143,8 @@ impl<F: Fetch> WebHandler<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate protocol
|
// Validate protocol
|
||||||
let protocol = match path.get(idx + 1).map(|a| a.as_str()) {
|
let mut target_url = match target_url {
|
||||||
Some("http") => "http",
|
Some(url) if url.starts_with("http://") || url.starts_with("https://") => url.to_owned(),
|
||||||
Some("https") => "https",
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(State::Error(ContentHandler::error(
|
return Err(State::Error(ContentHandler::error(
|
||||||
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
|
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
|
||||||
@ -151,28 +152,35 @@ impl<F: Fetch> WebHandler<F> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Redirect if address to main page does not end with /
|
if !target_url.ends_with("/") {
|
||||||
if let None = path.get(idx + 3) {
|
target_url = format!("{}/", target_url);
|
||||||
return Err(State::Redirecting(
|
|
||||||
Redirection::new(&format!("/{}/", path.join("/")))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = match query {
|
// TODO [ToDr] Should just use `path.app_params`
|
||||||
Some(query) => format!("?{}", query),
|
let (path, query) = match (&url, self.path.using_dapps_domains) {
|
||||||
|
(&Some(ref url), true) => (&url.path[..], &url.query),
|
||||||
|
(&Some(ref url), false) => (&url.path[2..], &url.query),
|
||||||
|
_ => {
|
||||||
|
return Err(State::Error(ContentHandler::error(
|
||||||
|
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let query = match *query {
|
||||||
|
Some(ref query) => format!("?{}", query),
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((format!("{}://{}{}", protocol, path[idx + 2..].join("/"), query), path[0..].join("/")))
|
Ok(format!("{}{}{}", target_url, path.join("/"), query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
||||||
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
|
||||||
let url = extract_url(&request);
|
let url = extract_url(&request);
|
||||||
|
|
||||||
// First extract the URL (reject invalid URLs)
|
// First extract the URL (reject invalid URLs)
|
||||||
let (target_url, referer) = match self.extract_target_url(url) {
|
let target_url = match self.extract_target_url(url) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.state = error;
|
self.state = error;
|
||||||
@ -186,7 +194,9 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
|||||||
self.control.clone(),
|
self.control.clone(),
|
||||||
WebInstaller {
|
WebInstaller {
|
||||||
embeddable_on: self.embeddable_on.clone(),
|
embeddable_on: self.embeddable_on.clone(),
|
||||||
referer: referer,
|
referer: self.path.app_params.get(0)
|
||||||
|
.expect("`target_url` is valid; app_params is not empty;qed")
|
||||||
|
.to_owned(),
|
||||||
},
|
},
|
||||||
self.embeddable_on.clone(),
|
self.embeddable_on.clone(),
|
||||||
self.remote.clone(),
|
self.remote.clone(),
|
||||||
@ -202,7 +212,6 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
|||||||
match self.state {
|
match self.state {
|
||||||
State::Initial => Next::end(),
|
State::Initial => Next::end(),
|
||||||
State::Error(ref mut handler) => handler.on_request_readable(decoder),
|
State::Error(ref mut handler) => handler.on_request_readable(decoder),
|
||||||
State::Redirecting(ref mut handler) => handler.on_request_readable(decoder),
|
|
||||||
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
|
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,7 +220,6 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
|||||||
match self.state {
|
match self.state {
|
||||||
State::Initial => Next::end(),
|
State::Initial => Next::end(),
|
||||||
State::Error(ref mut handler) => handler.on_response(res),
|
State::Error(ref mut handler) => handler.on_response(res),
|
||||||
State::Redirecting(ref mut handler) => handler.on_response(res),
|
|
||||||
State::Fetching(ref mut handler) => handler.on_response(res),
|
State::Fetching(ref mut handler) => handler.on_response(res),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +228,6 @@ impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
|
|||||||
match self.state {
|
match self.state {
|
||||||
State::Initial => Next::end(),
|
State::Initial => Next::end(),
|
||||||
State::Error(ref mut handler) => handler.on_response_writable(encoder),
|
State::Error(ref mut handler) => handler.on_response_writable(encoder),
|
||||||
State::Redirecting(ref mut handler) => handler.on_response_writable(encoder),
|
|
||||||
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
|
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
|
|||||||
let _ = req.read_to_string(&mut response);
|
let _ = req.read_to_string(&mut response);
|
||||||
|
|
||||||
let mut lines = response.lines();
|
let mut lines = response.lines();
|
||||||
let status = lines.next().unwrap().to_owned();
|
let status = lines.next().expect("Expected a response").to_owned();
|
||||||
let headers_raw = read_block(&mut lines, false);
|
let headers_raw = read_block(&mut lines, false);
|
||||||
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
|
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
|
||||||
let body = read_block(&mut lines, true);
|
let body = read_block(&mut lines, true);
|
||||||
|
@ -262,6 +262,18 @@ impl Client {
|
|||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wakes up client if it's a sleep.
|
||||||
|
pub fn keep_alive(&self) {
|
||||||
|
let should_wake = match *self.mode.lock() {
|
||||||
|
Mode::Dark(..) | Mode::Passive(..) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if should_wake {
|
||||||
|
self.wake_up();
|
||||||
|
(*self.sleep_state.lock()).last_activity = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds an actor to be notified on certain events
|
/// Adds an actor to be notified on certain events
|
||||||
pub fn add_notify(&self, target: Arc<ChainNotify>) {
|
pub fn add_notify(&self, target: Arc<ChainNotify>) {
|
||||||
self.notify.write().push(Arc::downgrade(&target));
|
self.notify.write().push(Arc::downgrade(&target));
|
||||||
@ -1011,17 +1023,6 @@ impl BlockChainClient for Client {
|
|||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keep_alive(&self) {
|
|
||||||
let should_wake = match *self.mode.lock() {
|
|
||||||
Mode::Dark(..) | Mode::Passive(..) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if should_wake {
|
|
||||||
self.wake_up();
|
|
||||||
(*self.sleep_state.lock()).last_activity = Some(Instant::now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mode(&self) -> IpcMode {
|
fn mode(&self) -> IpcMode {
|
||||||
let r = self.mode.lock().clone().into();
|
let r = self.mode.lock().clone().into();
|
||||||
trace!(target: "mode", "Asked for mode = {:?}. returning {:?}", &*self.mode.lock(), r);
|
trace!(target: "mode", "Asked for mode = {:?}. returning {:?}", &*self.mode.lock(), r);
|
||||||
|
@ -46,10 +46,6 @@ use encoded;
|
|||||||
/// Blockchain database client. Owns and manages a blockchain and a block queue.
|
/// Blockchain database client. Owns and manages a blockchain and a block queue.
|
||||||
pub trait BlockChainClient : Sync + Send {
|
pub trait BlockChainClient : Sync + Send {
|
||||||
|
|
||||||
/// Should be called by any external-facing interface when actively using the client.
|
|
||||||
/// To minimise chatter, there's no need to call more than once every 30s.
|
|
||||||
fn keep_alive(&self) {}
|
|
||||||
|
|
||||||
/// Get raw block header data by block id.
|
/// Get raw block header data by block id.
|
||||||
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
|
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.3.62",
|
"version": "0.3.66",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -140,6 +140,7 @@
|
|||||||
"yargs": "6.6.0"
|
"yargs": "6.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"base32.js": "0.1.0",
|
||||||
"bignumber.js": "3.0.1",
|
"bignumber.js": "3.0.1",
|
||||||
"blockies": "0.0.2",
|
"blockies": "0.0.2",
|
||||||
"brace": "0.9.0",
|
"brace": "0.9.0",
|
||||||
@ -193,6 +194,7 @@
|
|||||||
"scryptsy": "2.0.0",
|
"scryptsy": "2.0.0",
|
||||||
"solc": "ngotchac/solc-js",
|
"solc": "ngotchac/solc-js",
|
||||||
"store": "1.3.20",
|
"store": "1.3.20",
|
||||||
|
"useragent.js": "0.5.6",
|
||||||
"utf8": "2.1.2",
|
"utf8": "2.1.2",
|
||||||
"valid-url": "1.0.9",
|
"valid-url": "1.0.9",
|
||||||
"validator": "6.2.0",
|
"validator": "6.2.0",
|
||||||
|
@ -127,6 +127,18 @@ export function inNumber16 (number) {
|
|||||||
return inHex(bn.toString(16));
|
return inHex(bn.toString(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function inOptionsCondition (condition) {
|
||||||
|
if (condition) {
|
||||||
|
if (condition.block) {
|
||||||
|
condition.block = condition.block ? inNumber10(condition.block) : null;
|
||||||
|
} else if (condition.time) {
|
||||||
|
condition.time = inNumber10(Math.floor(condition.time.getTime() / 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
export function inOptions (options) {
|
export function inOptions (options) {
|
||||||
if (options) {
|
if (options) {
|
||||||
Object.keys(options).forEach((key) => {
|
Object.keys(options).forEach((key) => {
|
||||||
@ -136,6 +148,10 @@ export function inOptions (options) {
|
|||||||
options[key] = inAddress(options[key]);
|
options[key] = inAddress(options[key]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'condition':
|
||||||
|
options[key] = inOptionsCondition(options[key]);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'gas':
|
case 'gas':
|
||||||
case 'gasPrice':
|
case 'gasPrice':
|
||||||
options[key] = inNumber16((new BigNumber(options[key])).round());
|
options[key] = inNumber16((new BigNumber(options[key])).round());
|
||||||
|
@ -221,6 +221,18 @@ export function outSyncing (syncing) {
|
|||||||
return syncing;
|
return syncing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function outTransactionCondition (condition) {
|
||||||
|
if (condition) {
|
||||||
|
if (condition.block) {
|
||||||
|
condition.block = outNumber(condition.block);
|
||||||
|
} else if (condition.time) {
|
||||||
|
condition.time = outDate(condition.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
export function outTransaction (tx) {
|
export function outTransaction (tx) {
|
||||||
if (tx) {
|
if (tx) {
|
||||||
Object.keys(tx).forEach((key) => {
|
Object.keys(tx).forEach((key) => {
|
||||||
@ -234,8 +246,14 @@ export function outTransaction (tx) {
|
|||||||
tx[key] = outNumber(tx[key]);
|
tx[key] = outNumber(tx[key]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'condition':
|
||||||
|
tx[key] = outTransactionCondition(tx[key]);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'minBlock':
|
case 'minBlock':
|
||||||
tx[key] = tx[key] ? outNumber(tx[key]) : null;
|
tx[key] = tx[key]
|
||||||
|
? outNumber(tx[key])
|
||||||
|
: null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'creates':
|
case 'creates':
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin-top: 1.5em;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { ContainerTitle, DappCard, Portal, SectionList } from '~/ui';
|
import { DappCard, Portal, SectionList } from '~/ui';
|
||||||
import { CheckIcon } from '~/ui/Icons';
|
import { CheckIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './addDapps.css';
|
import styles from './addDapps.css';
|
||||||
@ -41,15 +41,13 @@ export default class AddDapps extends Component {
|
|||||||
className={ styles.modal }
|
className={ styles.modal }
|
||||||
onClose={ store.closeModal }
|
onClose={ store.closeModal }
|
||||||
open
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='dapps.add.label'
|
||||||
|
defaultMessage='visible applications'
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ContainerTitle
|
|
||||||
title={
|
|
||||||
<FormattedMessage
|
|
||||||
id='dapps.add.label'
|
|
||||||
defaultMessage='visible applications'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.warning } />
|
<div className={ styles.warning } />
|
||||||
{
|
{
|
||||||
|
@ -15,12 +15,7 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.modal {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin-top: 1.5em;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +60,6 @@
|
|||||||
|
|
||||||
.legend {
|
.legend {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -18,7 +18,7 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { AccountCard, ContainerTitle, Portal, SectionList } from '~/ui';
|
import { AccountCard, Portal, SectionList } from '~/ui';
|
||||||
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './dappPermissions.css';
|
import styles from './dappPermissions.css';
|
||||||
@ -38,18 +38,27 @@ export default class DappPermissions extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
className={ styles.modal }
|
buttons={
|
||||||
|
<div className={ styles.legend }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='dapps.permissions.description'
|
||||||
|
defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account'
|
||||||
|
values={ {
|
||||||
|
activeIcon: <CheckIcon />,
|
||||||
|
defaultIcon: <StarIcon />
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
onClose={ store.closeModal }
|
onClose={ store.closeModal }
|
||||||
open
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='dapps.permissions.label'
|
||||||
|
defaultMessage='visible dapp accounts'
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ContainerTitle
|
|
||||||
title={
|
|
||||||
<FormattedMessage
|
|
||||||
id='dapps.permissions.label'
|
|
||||||
defaultMessage='visible dapp accounts'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<SectionList
|
<SectionList
|
||||||
items={ store.accounts }
|
items={ store.accounts }
|
||||||
@ -57,16 +66,6 @@ export default class DappPermissions extends Component {
|
|||||||
renderItem={ this.renderAccount }
|
renderItem={ this.renderAccount }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.legend }>
|
|
||||||
<FormattedMessage
|
|
||||||
id='dapps.permissions.description'
|
|
||||||
defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account'
|
|
||||||
values={ {
|
|
||||||
activeIcon: <CheckIcon />,
|
|
||||||
defaultIcon: <StarIcon />
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,45 +15,22 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Input, GasPriceEditor } from '~/ui';
|
import { GasPriceEditor } from '~/ui';
|
||||||
|
|
||||||
import styles from '../executeContract.css';
|
import styles from '../executeContract.css';
|
||||||
|
|
||||||
export default class AdvancedStep extends Component {
|
export default class AdvancedStep extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
gasStore: PropTypes.object.isRequired,
|
gasStore: PropTypes.object.isRequired
|
||||||
minBlock: PropTypes.string,
|
|
||||||
minBlockError: PropTypes.string,
|
|
||||||
onMinBlockChange: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
|
const { gasStore } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={ styles.gaseditor }>
|
||||||
<Input
|
<GasPriceEditor store={ gasStore } />
|
||||||
error={ minBlockError }
|
|
||||||
hint={
|
|
||||||
<FormattedMessage
|
|
||||||
id='executeContract.advanced.minBlock.hint'
|
|
||||||
defaultMessage='Only post the transaction after this block'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='executeContract.advanced.minBlock.label'
|
|
||||||
defaultMessage='BlockNumber to send from'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value={ minBlock }
|
|
||||||
onSubmit={ onMinBlockChange }
|
|
||||||
/>
|
|
||||||
<div className={ styles.gaseditor }>
|
|
||||||
<GasPriceEditor store={ gasStore } />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
@ -100,8 +99,6 @@ class ExecuteContract extends Component {
|
|||||||
fromAddressError: null,
|
fromAddressError: null,
|
||||||
func: null,
|
func: null,
|
||||||
funcError: null,
|
funcError: null,
|
||||||
minBlock: '0',
|
|
||||||
minBlockError: null,
|
|
||||||
rejected: false,
|
rejected: false,
|
||||||
sending: false,
|
sending: false,
|
||||||
step: STEP_DETAILS,
|
step: STEP_DETAILS,
|
||||||
@ -167,8 +164,8 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
const { onClose, fromAddress } = this.props;
|
const { onClose, fromAddress } = this.props;
|
||||||
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
|
const { advancedOptions, sending, step, fromAddressError, valuesError } = this.state;
|
||||||
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
|
const hasError = fromAddressError || valuesError.find((error) => error);
|
||||||
|
|
||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
@ -258,7 +255,7 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { onFromAddressChange } = this.props;
|
const { onFromAddressChange } = this.props;
|
||||||
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
|
const { advancedOptions, step, busyState, txhash, rejected } = this.state;
|
||||||
|
|
||||||
if (rejected) {
|
if (rejected) {
|
||||||
return (
|
return (
|
||||||
@ -305,12 +302,7 @@ class ExecuteContract extends Component {
|
|||||||
);
|
);
|
||||||
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
||||||
return (
|
return (
|
||||||
<AdvancedStep
|
<AdvancedStep gasStore={ this.gasStore } />
|
||||||
gasStore={ this.gasStore }
|
|
||||||
minBlock={ minBlock }
|
|
||||||
minBlockError={ minBlockError }
|
|
||||||
onMinBlockChange={ this.onMinBlockChange }
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,15 +331,6 @@ class ExecuteContract extends Component {
|
|||||||
}, this.estimateGas);
|
}, this.estimateGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMinBlockChange = (minBlock) => {
|
|
||||||
const minBlockError = validateUint(minBlock).valueError;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
minBlock,
|
|
||||||
minBlockError
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onValueChange = (event, index, _value) => {
|
onValueChange = (event, index, _value) => {
|
||||||
const { func, values, valuesError } = this.state;
|
const { func, values, valuesError } = this.state;
|
||||||
const input = func.inputs.find((input, _index) => index === _index);
|
const input = func.inputs.find((input, _index) => index === _index);
|
||||||
@ -409,17 +392,14 @@ class ExecuteContract extends Component {
|
|||||||
postTransaction = () => {
|
postTransaction = () => {
|
||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { fromAddress } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
const { advancedOptions, amount, func, values } = this.state;
|
||||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||||
const finalstep = steps.length - 1;
|
const finalstep = steps.length - 1;
|
||||||
|
|
||||||
const options = {
|
const options = this.gasStore.overrideTransaction({
|
||||||
gas: this.gasStore.gas,
|
|
||||||
gasPrice: this.gasStore.price,
|
|
||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
|
|
||||||
value: api.util.toWei(amount || 0)
|
value: api.util.toWei(amount || 0)
|
||||||
};
|
});
|
||||||
|
|
||||||
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
||||||
|
|
||||||
|
@ -27,36 +27,17 @@ export default class Extras extends Component {
|
|||||||
dataError: PropTypes.string,
|
dataError: PropTypes.string,
|
||||||
gasStore: PropTypes.object.isRequired,
|
gasStore: PropTypes.object.isRequired,
|
||||||
isEth: PropTypes.bool,
|
isEth: PropTypes.bool,
|
||||||
minBlock: PropTypes.string,
|
|
||||||
minBlockError: PropTypes.string,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
total: PropTypes.string,
|
total: PropTypes.string,
|
||||||
totalError: PropTypes.string
|
totalError: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { gasStore, minBlock, minBlockError, onChange } = this.props;
|
const { gasStore, onChange } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{ this.renderData() }
|
{ this.renderData() }
|
||||||
<Input
|
|
||||||
error={ minBlockError }
|
|
||||||
hint={
|
|
||||||
<FormattedMessage
|
|
||||||
id='transferModal.minBlock.hint'
|
|
||||||
defaultMessage='Only post the transaction after this block'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='transferModal.minBlock.label'
|
|
||||||
defaultMessage='BlockNumber to send from'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value={ minBlock }
|
|
||||||
onChange={ this.onEditMinBlock }
|
|
||||||
/>
|
|
||||||
<div className={ styles.gaseditor }>
|
<div className={ styles.gaseditor }>
|
||||||
<GasPriceEditor
|
<GasPriceEditor
|
||||||
store={ gasStore }
|
store={ gasStore }
|
||||||
@ -98,8 +79,4 @@ export default class Extras extends Component {
|
|||||||
onEditData = (event) => {
|
onEditData = (event) => {
|
||||||
this.props.onChange('data', event.target.value);
|
this.props.onChange('data', event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditMinBlock = (event) => {
|
|
||||||
this.props.onChange('minBlock', event.target.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -52,9 +52,6 @@ export default class TransferStore {
|
|||||||
@observable data = '';
|
@observable data = '';
|
||||||
@observable dataError = null;
|
@observable dataError = null;
|
||||||
|
|
||||||
@observable minBlock = '0';
|
|
||||||
@observable minBlockError = null;
|
|
||||||
|
|
||||||
@observable recipient = '';
|
@observable recipient = '';
|
||||||
@observable recipientError = ERRORS.requireRecipient;
|
@observable recipientError = ERRORS.requireRecipient;
|
||||||
|
|
||||||
@ -78,39 +75,6 @@ export default class TransferStore {
|
|||||||
|
|
||||||
gasStore = null;
|
gasStore = null;
|
||||||
|
|
||||||
@computed get steps () {
|
|
||||||
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
|
||||||
|
|
||||||
if (this.rejected) {
|
|
||||||
steps[steps.length - 1] = TITLES.rejected;
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get isValid () {
|
|
||||||
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
|
||||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
|
|
||||||
const verifyValid = !this.passwordError;
|
|
||||||
|
|
||||||
switch (this.stage) {
|
|
||||||
case 0:
|
|
||||||
return detailsValid;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return this.extras
|
|
||||||
? extrasValid
|
|
||||||
: verifyValid;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return verifyValid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get token () {
|
|
||||||
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (api, props) {
|
constructor (api, props) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
@ -135,6 +99,39 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get steps () {
|
||||||
|
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
||||||
|
|
||||||
|
if (this.rejected) {
|
||||||
|
steps[steps.length - 1] = TITLES.rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isValid () {
|
||||||
|
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
||||||
|
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.gasStore.conditionBlockError && !this.totalError;
|
||||||
|
const verifyValid = !this.passwordError;
|
||||||
|
|
||||||
|
switch (this.stage) {
|
||||||
|
case 0:
|
||||||
|
return detailsValid;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return this.extras
|
||||||
|
? extrasValid
|
||||||
|
: verifyValid;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return verifyValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get token () {
|
||||||
|
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
|
||||||
|
}
|
||||||
|
|
||||||
@action onNext = () => {
|
@action onNext = () => {
|
||||||
this.stage += 1;
|
this.stage += 1;
|
||||||
}
|
}
|
||||||
@ -164,9 +161,6 @@ export default class TransferStore {
|
|||||||
case 'gasPrice':
|
case 'gasPrice':
|
||||||
return this._onUpdateGasPrice(value);
|
return this._onUpdateGasPrice(value);
|
||||||
|
|
||||||
case 'minBlock':
|
|
||||||
return this._onUpdateMinBlock(value);
|
|
||||||
|
|
||||||
case 'recipient':
|
case 'recipient':
|
||||||
return this._onUpdateRecipient(value);
|
return this._onUpdateRecipient(value);
|
||||||
|
|
||||||
@ -284,14 +278,6 @@ export default class TransferStore {
|
|||||||
this.recalculate();
|
this.recalculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action _onUpdateMinBlock = (minBlock) => {
|
|
||||||
console.log('minBlock', minBlock);
|
|
||||||
transaction(() => {
|
|
||||||
this.minBlock = minBlock;
|
|
||||||
this.minBlockError = this._validatePositiveNumber(minBlock);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action _onUpdateGasPrice = (gasPrice) => {
|
@action _onUpdateGasPrice = (gasPrice) => {
|
||||||
this.recalculate();
|
this.recalculate();
|
||||||
}
|
}
|
||||||
@ -590,7 +576,6 @@ export default class TransferStore {
|
|||||||
send () {
|
send () {
|
||||||
const { options, values } = this._getTransferParams();
|
const { options, values } = this._getTransferParams();
|
||||||
|
|
||||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
|
||||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||||
|
|
||||||
return this._getTransferMethod().postTransaction(options, values);
|
return this._getTransferMethod().postTransaction(options, values);
|
||||||
@ -639,15 +624,12 @@ export default class TransferStore {
|
|||||||
const to = (isEth && !isWallet) ? this.recipient
|
const to = (isEth && !isWallet) ? this.recipient
|
||||||
: (this.isWallet ? this.wallet.address : this.token.address);
|
: (this.isWallet ? this.wallet.address : this.token.address);
|
||||||
|
|
||||||
const options = {
|
const options = this.gasStore.overrideTransaction({
|
||||||
from: this.sender || this.account.address,
|
from: this.sender || this.account.address,
|
||||||
to
|
to
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!gas) {
|
if (gas) {
|
||||||
options.gas = this.gasStore.gas;
|
|
||||||
options.gasPrice = this.gasStore.price;
|
|
||||||
} else {
|
|
||||||
options.gas = MAX_GAS_ESTIMATION;
|
options.gas = MAX_GAS_ESTIMATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ class Transfer extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
|
const { isEth, data, dataError, total, totalError } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Extras
|
<Extras
|
||||||
@ -215,8 +215,6 @@ class Transfer extends Component {
|
|||||||
dataError={ dataError }
|
dataError={ dataError }
|
||||||
gasStore={ this.store.gasStore }
|
gasStore={ this.store.gasStore }
|
||||||
isEth={ isEth }
|
isEth={ isEth }
|
||||||
minBlock={ minBlock }
|
|
||||||
minBlockError={ minBlockError }
|
|
||||||
onChange={ this.store.onUpdateDetails }
|
onChange={ this.store.onUpdateDetails }
|
||||||
total={ total }
|
total={ total }
|
||||||
totalError={ totalError }
|
totalError={ totalError }
|
||||||
|
@ -52,13 +52,11 @@ export default class SignerMiddleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onConfirmStart = (store, action) => {
|
onConfirmStart = (store, action) => {
|
||||||
const { gas = 0, gasPrice = 0, id, password, payload, wallet } = action.payload;
|
const { condition, gas = 0, gasPrice = 0, id, password, payload, wallet } = action.payload;
|
||||||
|
|
||||||
const handlePromise = (promise) => {
|
const handlePromise = (promise) => {
|
||||||
promise
|
promise
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
console.log('confirmRequest', id, txHash);
|
|
||||||
|
|
||||||
if (!txHash) {
|
if (!txHash) {
|
||||||
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
|
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
|
||||||
return;
|
return;
|
||||||
@ -120,7 +118,7 @@ export default class SignerMiddleware {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password));
|
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice, condition }, password));
|
||||||
}
|
}
|
||||||
|
|
||||||
onRejectStart = (store, action) => {
|
onRejectStart = (store, action) => {
|
||||||
|
@ -14,30 +14,36 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.byline, .description {
|
|
||||||
|
$bylineColor: #aaa;
|
||||||
|
$bylineLineHeight: 1.2rem;
|
||||||
|
$bylineMaxHeight: 2.4rem;
|
||||||
|
$titleLineHeight: 2rem;
|
||||||
|
$smallFontSize: 0.75rem;
|
||||||
|
|
||||||
|
.byline,
|
||||||
|
.description {
|
||||||
|
color: $bylineColor;
|
||||||
|
display: -webkit-box;
|
||||||
|
line-height: $bylineLineHeight;
|
||||||
|
max-height: $bylineMaxHeight;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: 1.2em;
|
|
||||||
max-height: 2.4em;
|
|
||||||
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
|
||||||
color: #aaa;
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
color: #aaa !important;
|
color: $bylineColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
font-size: 0.75em;
|
font-size: $smallFontSize;
|
||||||
margin: 0.5em 0 0;
|
margin: 0.5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-transform: uppercase;
|
line-height: $titleLineHeight;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 34px;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
@ -29,29 +29,41 @@ export default class Title extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { byline, className, title } = this.props;
|
const { className, title } = this.props;
|
||||||
|
|
||||||
const byLine = typeof byline === 'string'
|
|
||||||
? (
|
|
||||||
<span title={ byline }>
|
|
||||||
{ byline }
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: byline;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className }>
|
||||||
<h3 className={ styles.title }>
|
<h3 className={ styles.title }>
|
||||||
{ title }
|
{ title }
|
||||||
</h3>
|
</h3>
|
||||||
<div className={ styles.byline }>
|
{ this.renderByline() }
|
||||||
{ byLine }
|
|
||||||
</div>
|
|
||||||
{ this.renderDescription() }
|
{ this.renderDescription() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderByline () {
|
||||||
|
const { byline } = this.props;
|
||||||
|
|
||||||
|
if (!byline) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.byline }>
|
||||||
|
{
|
||||||
|
typeof byline === 'string'
|
||||||
|
? (
|
||||||
|
<span title={ byline }>
|
||||||
|
{ byline }
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
: byline
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderDescription () {
|
renderDescription () {
|
||||||
const { description } = this.props;
|
const { description } = this.props;
|
||||||
|
|
||||||
@ -59,17 +71,17 @@ export default class Title extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const desc = typeof description === 'string'
|
|
||||||
? (
|
|
||||||
<span title={ description }>
|
|
||||||
{ description }
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: description;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.description }>
|
<div className={ styles.description }>
|
||||||
{ desc }
|
{
|
||||||
|
typeof description === 'string'
|
||||||
|
? (
|
||||||
|
<span title={ description }>
|
||||||
|
{ description }
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
: description
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
margin: 1rem 0.5rem 0.25em;
|
margin: 1rem 0.5rem 0.25em;
|
||||||
color: rgba(255, 255, 255, 0.498039);
|
color: rgba(255, 255, 255, 0.498039);
|
||||||
@ -102,14 +108,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.categories {
|
.categories {
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
margin: 2rem 0 0;
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
@ -180,34 +180,38 @@ class AddressSelect extends Component {
|
|||||||
onClose={ this.handleClose }
|
onClose={ this.handleClose }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
open={ expanded }
|
open={ expanded }
|
||||||
|
title={
|
||||||
|
<div className={ styles.title }>
|
||||||
|
<label className={ styles.label } htmlFor={ id }>
|
||||||
|
{ label }
|
||||||
|
</label>
|
||||||
|
<div className={ styles.outerInput }>
|
||||||
|
<input
|
||||||
|
id={ id }
|
||||||
|
className={ styles.input }
|
||||||
|
placeholder={ ilHint }
|
||||||
|
onBlur={ this.handleInputBlur }
|
||||||
|
onFocus={ this.handleInputFocus }
|
||||||
|
onChange={ this.handleChange }
|
||||||
|
ref={ this.setInputRef }
|
||||||
|
/>
|
||||||
|
{ this.renderLoader() }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={ styles.underline }>
|
||||||
|
<TextFieldUnderline
|
||||||
|
focus={ inputFocused }
|
||||||
|
focusStyle={ BOTTOM_BORDER_STYLE }
|
||||||
|
muiTheme={ muiTheme }
|
||||||
|
style={ BOTTOM_BORDER_STYLE }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderCurrentInput() }
|
||||||
|
{ this.renderRegistryValues() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<label className={ styles.label } htmlFor={ id }>
|
|
||||||
{ label }
|
|
||||||
</label>
|
|
||||||
<div className={ styles.outerInput }>
|
|
||||||
<input
|
|
||||||
id={ id }
|
|
||||||
className={ styles.input }
|
|
||||||
placeholder={ ilHint }
|
|
||||||
onBlur={ this.handleInputBlur }
|
|
||||||
onFocus={ this.handleInputFocus }
|
|
||||||
onChange={ this.handleChange }
|
|
||||||
ref={ this.setInputRef }
|
|
||||||
/>
|
|
||||||
{ this.renderLoader() }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={ styles.underline }>
|
|
||||||
<TextFieldUnderline
|
|
||||||
focus={ inputFocused }
|
|
||||||
focusStyle={ BOTTOM_BORDER_STYLE }
|
|
||||||
muiTheme={ muiTheme }
|
|
||||||
style={ BOTTOM_BORDER_STYLE }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ this.renderCurrentInput() }
|
|
||||||
{ this.renderRegistryValues() }
|
|
||||||
{ this.renderAccounts() }
|
{ this.renderAccounts() }
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
|
61
js/src/ui/Form/DappUrlInput/dappUrlInput.js
Normal file
61
js/src/ui/Form/DappUrlInput/dappUrlInput.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import keycode from 'keycode';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
export default class DappUrlInput extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
onGoto: PropTypes.func.isRequired,
|
||||||
|
onRestore: PropTypes.func.isRequired,
|
||||||
|
url: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, url } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={ className }
|
||||||
|
onChange={ this.onChange }
|
||||||
|
onKeyDown={ this.onKeyDown }
|
||||||
|
type='text'
|
||||||
|
value={ url }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (event) => {
|
||||||
|
this.props.onChange(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (event) => {
|
||||||
|
switch (keycode(event)) {
|
||||||
|
case 'esc':
|
||||||
|
this.props.onRestore();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'enter':
|
||||||
|
this.props.onGoto();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
70
js/src/ui/Form/DappUrlInput/dappUrlInput.spec.js
Normal file
70
js/src/ui/Form/DappUrlInput/dappUrlInput.spec.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import DappUrlInput from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let onChange;
|
||||||
|
let onGoto;
|
||||||
|
let onRestore;
|
||||||
|
|
||||||
|
function render (props = { url: 'http://some.url' }) {
|
||||||
|
onChange = sinon.stub();
|
||||||
|
onGoto = sinon.stub();
|
||||||
|
onRestore = sinon.stub();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<DappUrlInput
|
||||||
|
onChange={ onChange }
|
||||||
|
onGoto={ onGoto }
|
||||||
|
onRestore={ onRestore }
|
||||||
|
{ ...props }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/Form/DappUrlInput', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('events', () => {
|
||||||
|
describe('onChange', () => {
|
||||||
|
it('calls the onChange callback as provided', () => {
|
||||||
|
component.simulate('change', { target: { value: 'testing' } });
|
||||||
|
expect(onChange).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onKeyDown', () => {
|
||||||
|
it('calls the onGoto callback on enter', () => {
|
||||||
|
component.simulate('keyDown', { keyCode: 13 });
|
||||||
|
expect(onGoto).to.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the onRestor callback on esc', () => {
|
||||||
|
component.simulate('keyDown', { keyCode: 27 });
|
||||||
|
expect(onRestore).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/ui/Form/DappUrlInput/index.js
Normal file
17
js/src/ui/Form/DappUrlInput/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './dappUrlInput';
|
@ -78,6 +78,9 @@ class InputAddress extends Component {
|
|||||||
props.focused = focused;
|
props.focused = focused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: The is not advisable, fixes the display issue, however the name should come from
|
||||||
|
// a common component.
|
||||||
|
// account.name || (value ? 'UNNAMED' : value)
|
||||||
return (
|
return (
|
||||||
<div className={ containerClasses.join(' ') }>
|
<div className={ containerClasses.join(' ') }>
|
||||||
<Input
|
<Input
|
||||||
@ -96,7 +99,7 @@ class InputAddress extends Component {
|
|||||||
tabIndex={ tabIndex }
|
tabIndex={ tabIndex }
|
||||||
value={
|
value={
|
||||||
text && account
|
text && account
|
||||||
? account.name
|
? (account.name || (value ? 'UNNAMED' : value))
|
||||||
: (nullName || value)
|
: (nullName || value)
|
||||||
}
|
}
|
||||||
{ ...props }
|
{ ...props }
|
||||||
|
17
js/src/ui/Form/InputDate/index.js
Normal file
17
js/src/ui/Form/InputDate/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './inputDate';
|
22
js/src/ui/Form/InputDate/inputDate.css
Normal file
22
js/src/ui/Form/InputDate/inputDate.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity 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 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
53
js/src/ui/Form/InputDate/inputDate.js
Normal file
53
js/src/ui/Form/InputDate/inputDate.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { DatePicker } from 'material-ui';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import Label from '../Label';
|
||||||
|
|
||||||
|
import styles from './inputDate.css';
|
||||||
|
|
||||||
|
// NOTE: Has to be larger than Signer overlay Z, aligns with ../InputTime
|
||||||
|
const DIALOG_STYLE = { zIndex: 10010 };
|
||||||
|
|
||||||
|
export default class InputDate extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
hint: PropTypes.node,
|
||||||
|
label: PropTypes.node,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
value: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, hint, label, onChange, value } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ [styles.container, className].join(' ') }>
|
||||||
|
<Label label={ label } />
|
||||||
|
<DatePicker
|
||||||
|
autoOk
|
||||||
|
className={ styles.input }
|
||||||
|
dialogContainerStyle={ DIALOG_STYLE }
|
||||||
|
hintText={ hint }
|
||||||
|
onChange={ onChange }
|
||||||
|
value={ value }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/ui/Form/InputTime/index.js
Normal file
17
js/src/ui/Form/InputTime/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './inputTime';
|
22
js/src/ui/Form/InputTime/inputTime.css
Normal file
22
js/src/ui/Form/InputTime/inputTime.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity 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 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
54
js/src/ui/Form/InputTime/inputTime.js
Normal file
54
js/src/ui/Form/InputTime/inputTime.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { TimePicker } from 'material-ui';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import Label from '../Label';
|
||||||
|
|
||||||
|
import styles from './inputTime.css';
|
||||||
|
|
||||||
|
// NOTE: Has to be larger than Signer overlay Z, aligns with ../InputDate
|
||||||
|
const DIALOG_STYLE = { zIndex: 10010 };
|
||||||
|
|
||||||
|
export default class InputTime extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
hint: PropTypes.node,
|
||||||
|
label: PropTypes.node,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
value: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, hint, label, onChange, value } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ [styles.container, className].join(' ') }>
|
||||||
|
<Label label={ label } />
|
||||||
|
<TimePicker
|
||||||
|
autoOk
|
||||||
|
className={ styles.input }
|
||||||
|
dialogStyle={ DIALOG_STYLE }
|
||||||
|
format='24hr'
|
||||||
|
hintText={ hint }
|
||||||
|
onChange={ onChange }
|
||||||
|
value={ value }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/ui/Form/Label/index.js
Normal file
17
js/src/ui/Form/Label/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './label';
|
24
js/src/ui/Form/Label/label.css
Normal file
24
js/src/ui/Form/Label/label.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity 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 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$labelColor: rgba(255, 255, 255, 0.5);
|
||||||
|
$labelFontSize: 0.75rem;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: $labelColor;
|
||||||
|
font-size: $labelFontSize;
|
||||||
|
}
|
40
js/src/ui/Form/Label/label.js
Normal file
40
js/src/ui/Form/Label/label.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import styles from './label.css';
|
||||||
|
|
||||||
|
export default class Label extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
label: PropTypes.node
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, label } = this.props;
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className={ [styles.label, className].join(' ') }>
|
||||||
|
{ label }
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,18 +15,23 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.spaced {
|
.container {
|
||||||
margin: 0.25em 0;
|
.label {
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeContainer {
|
.radioButton {
|
||||||
display: flex;
|
margin: 0.25em 0;
|
||||||
flex-direction: column;
|
}
|
||||||
|
|
||||||
.desc {
|
.radioLabel {
|
||||||
font-size: 0.8em;
|
display: flex;
|
||||||
margin-bottom: 0.5em;
|
flex-direction: column;
|
||||||
color: #ccc;
|
|
||||||
z-index: 2;
|
.description {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
color: #ccc;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,14 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { arrayOrObjectProptype } from '~/util/proptypes';
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
|
import Label from '../Label';
|
||||||
import styles from './radioButtons.css';
|
import styles from './radioButtons.css';
|
||||||
|
|
||||||
export default class RadioButtons extends Component {
|
export default class RadioButtons extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
label: PropTypes.node,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
@ -34,10 +38,10 @@ export default class RadioButtons extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, values } = this.props;
|
const { className, label, value, values } = this.props;
|
||||||
|
|
||||||
const index = Number.isNaN(parseInt(value))
|
const index = Number.isNaN(parseInt(value))
|
||||||
? values.findIndex((val) => val.key === value)
|
? values.findIndex((_value) => _value.key === value)
|
||||||
: parseInt(value);
|
: parseInt(value);
|
||||||
const selectedValue = typeof value !== 'object'
|
const selectedValue = typeof value !== 'object'
|
||||||
? values[index]
|
? values[index]
|
||||||
@ -45,13 +49,19 @@ export default class RadioButtons extends Component {
|
|||||||
const key = this.getKey(selectedValue, index);
|
const key = this.getKey(selectedValue, index);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButtonGroup
|
<div className={ [styles.container, className].join(' ') }>
|
||||||
name={ name }
|
<Label
|
||||||
onChange={ this.onChange }
|
className={ styles.label }
|
||||||
valueSelected={ key }
|
label={ label }
|
||||||
>
|
/>
|
||||||
{ this.renderContent() }
|
<RadioButtonGroup
|
||||||
</RadioButtonGroup>
|
name={ name }
|
||||||
|
onChange={ this.onChange }
|
||||||
|
valueSelected={ key }
|
||||||
|
>
|
||||||
|
{ this.renderContent() }
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,14 +77,14 @@ export default class RadioButtons extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButton
|
<RadioButton
|
||||||
className={ styles.spaced }
|
className={ styles.radioButton }
|
||||||
key={ index }
|
key={ index }
|
||||||
label={
|
label={
|
||||||
<div className={ styles.typeContainer }>
|
<div className={ styles.radioLabel }>
|
||||||
<span>{ label }</span>
|
<span>{ label }</span>
|
||||||
{
|
{
|
||||||
description
|
description
|
||||||
? <span className={ styles.desc }>{ description }</span>
|
? <span className={ styles.description }>{ description }</span>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -97,7 +107,7 @@ export default class RadioButtons extends Component {
|
|||||||
|
|
||||||
onChange = (event, index) => {
|
onChange = (event, index) => {
|
||||||
const { onChange, values } = this.props;
|
const { onChange, values } = this.props;
|
||||||
const value = values[index] || values.find((v) => v.key === index);
|
const value = values[index] || values.find((value) => value.key === index);
|
||||||
|
|
||||||
onChange(value, index);
|
onChange(value, index);
|
||||||
}
|
}
|
||||||
|
@ -15,26 +15,34 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import AddressSelect from './AddressSelect';
|
import AddressSelect from './AddressSelect';
|
||||||
|
import DappUrlInput from './DappUrlInput';
|
||||||
import FormWrap from './FormWrap';
|
import FormWrap from './FormWrap';
|
||||||
import TypedInput from './TypedInput';
|
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import InputAddress from './InputAddress';
|
import InputAddress from './InputAddress';
|
||||||
import InputAddressSelect from './InputAddressSelect';
|
import InputAddressSelect from './InputAddressSelect';
|
||||||
import InputChip from './InputChip';
|
import InputChip from './InputChip';
|
||||||
|
import InputDate from './InputDate';
|
||||||
import InputInline from './InputInline';
|
import InputInline from './InputInline';
|
||||||
import Select from './Select';
|
import InputTime from './InputTime';
|
||||||
|
import Label from './Label';
|
||||||
import RadioButtons from './RadioButtons';
|
import RadioButtons from './RadioButtons';
|
||||||
|
import Select from './Select';
|
||||||
|
import TypedInput from './TypedInput';
|
||||||
|
|
||||||
export default from './form';
|
export default from './form';
|
||||||
export {
|
export {
|
||||||
AddressSelect,
|
AddressSelect,
|
||||||
|
DappUrlInput,
|
||||||
FormWrap,
|
FormWrap,
|
||||||
TypedInput,
|
|
||||||
Input,
|
Input,
|
||||||
InputAddress,
|
InputAddress,
|
||||||
InputAddressSelect,
|
InputAddressSelect,
|
||||||
InputChip,
|
InputChip,
|
||||||
|
InputDate,
|
||||||
InputInline,
|
InputInline,
|
||||||
|
InputTime,
|
||||||
|
Label,
|
||||||
|
RadioButtons,
|
||||||
Select,
|
Select,
|
||||||
RadioButtons
|
TypedInput
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conditionContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 0 1 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conditionRadio {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
&>label {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: auto !important;
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding-right: 1.5em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.graphContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -17,13 +17,44 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import Input from '../Form/Input';
|
import { Input, InputDate, InputTime, RadioButtons } from '../Form';
|
||||||
import GasPriceSelector from '../GasPriceSelector';
|
import GasPriceSelector from '../GasPriceSelector';
|
||||||
import Store from './store';
|
|
||||||
|
|
||||||
|
import Store, { CONDITIONS } from './store';
|
||||||
import styles from './gasPriceEditor.css';
|
import styles from './gasPriceEditor.css';
|
||||||
|
|
||||||
|
const CONDITION_VALUES = [
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.none'
|
||||||
|
defaultMessage='No conditions'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: CONDITIONS.NONE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.blocknumber'
|
||||||
|
defaultMessage='Send after BlockNumber'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: CONDITIONS.BLOCK
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.datetime'
|
||||||
|
defaultMessage='Send after Date & Time'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: CONDITIONS.TIME
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class GasPriceEditor extends Component {
|
export default class GasPriceEditor extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -41,7 +72,7 @@ export default class GasPriceEditor extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { children, store } = this.props;
|
const { children, store } = this.props;
|
||||||
const { errorGas, errorPrice, errorTotal, estimated, gas, histogram, price, priceDefault, totalValue } = store;
|
const { conditionType, errorGas, errorPrice, errorTotal, estimated, gas, histogram, price, priceDefault, totalValue } = store;
|
||||||
|
|
||||||
const eth = api.util.fromWei(totalValue).toFormat();
|
const eth = api.util.fromWei(totalValue).toFormat();
|
||||||
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
||||||
@ -49,46 +80,146 @@ export default class GasPriceEditor extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.graphColumn }>
|
<RadioButtons
|
||||||
<GasPriceSelector
|
className={ styles.conditionRadio }
|
||||||
histogram={ histogram }
|
label={
|
||||||
onChange={ this.onEditGasPrice }
|
<FormattedMessage
|
||||||
price={ price }
|
id='txEditor.condition.label'
|
||||||
/>
|
defaultMessage='Condition where transaction activates'
|
||||||
<div className={ styles.gasPriceDesc }>
|
/>
|
||||||
You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.
|
}
|
||||||
|
onChange={ this.onChangeConditionType }
|
||||||
|
value={ conditionType }
|
||||||
|
values={ CONDITION_VALUES }
|
||||||
|
/>
|
||||||
|
{ this.renderConditions() }
|
||||||
|
|
||||||
|
<div className={ styles.graphContainer }>
|
||||||
|
<div className={ styles.graphColumn }>
|
||||||
|
<GasPriceSelector
|
||||||
|
histogram={ histogram }
|
||||||
|
onChange={ this.onEditGasPrice }
|
||||||
|
price={ price }
|
||||||
|
/>
|
||||||
|
<div className={ styles.gasPriceDesc }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.gas.info'
|
||||||
|
defaultMessage='You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={ styles.editColumn }>
|
||||||
|
<div className={ styles.row }>
|
||||||
|
<Input
|
||||||
|
error={ errorGas }
|
||||||
|
hint='the amount of gas to use for the transaction'
|
||||||
|
label={ gasLabel }
|
||||||
|
min={ 1 }
|
||||||
|
onChange={ this.onEditGas }
|
||||||
|
type='number'
|
||||||
|
value={ gas }
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
error={ errorPrice }
|
||||||
|
hint='the price of gas to use for the transaction'
|
||||||
|
label={ priceLabel }
|
||||||
|
min={ 1 }
|
||||||
|
onChange={ this.onEditGasPrice }
|
||||||
|
type='number'
|
||||||
|
value={ price }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.row }>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
error={ errorTotal }
|
||||||
|
hint='the total amount of the transaction'
|
||||||
|
label='total transaction amount'
|
||||||
|
value={ `${eth} ETH` }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.row }>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<div className={ styles.editColumn }>
|
renderConditions () {
|
||||||
<div className={ styles.row }>
|
const { conditionType, condition, conditionBlockError } = this.props.store;
|
||||||
|
|
||||||
|
if (conditionType === CONDITIONS.NONE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditionType === CONDITIONS.BLOCK) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.conditionContainer }>
|
||||||
|
<div className={ styles.input }>
|
||||||
<Input
|
<Input
|
||||||
error={ errorGas }
|
error={ conditionBlockError }
|
||||||
hint='the amount of gas to use for the transaction'
|
hint={
|
||||||
label={ gasLabel }
|
<FormattedMessage
|
||||||
onChange={ this.onEditGas }
|
id='txEditor.condition.block.hint'
|
||||||
value={ gas }
|
defaultMessage='The minimum block to send from'
|
||||||
/>
|
/>
|
||||||
<Input
|
}
|
||||||
error={ errorPrice }
|
label={
|
||||||
hint='the price of gas to use for the transaction'
|
<FormattedMessage
|
||||||
label={ priceLabel }
|
id='txEditor.condition.block.label'
|
||||||
onChange={ this.onEditGasPrice }
|
defaultMessage='Transaction send block'
|
||||||
value={ price }
|
/>
|
||||||
|
}
|
||||||
|
min={ 1 }
|
||||||
|
onChange={ this.onChangeConditionBlock }
|
||||||
|
type='number'
|
||||||
|
value={ condition.block }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.row }>
|
</div>
|
||||||
<Input
|
);
|
||||||
disabled
|
}
|
||||||
error={ errorTotal }
|
|
||||||
hint='the total amount of the transaction'
|
return (
|
||||||
label='total transaction amount'
|
<div className={ styles.conditionContainer }>
|
||||||
value={ `${eth} ETH` }
|
<div className={ styles.input }>
|
||||||
/>
|
<InputDate
|
||||||
</div>
|
hint={
|
||||||
<div className={ styles.row }>
|
<FormattedMessage
|
||||||
{ children }
|
id='txEditor.condition.date.hint'
|
||||||
</div>
|
defaultMessage='The minimum date to send from'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.date.label'
|
||||||
|
defaultMessage='Transaction send date'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onChangeConditionDateTime }
|
||||||
|
value={ condition.time }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.input }>
|
||||||
|
<InputTime
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.time.hint'
|
||||||
|
defaultMessage='The minimum time to send from'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='txEditor.condition.time.label'
|
||||||
|
defaultMessage='Transaction send time'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onChangeConditionDateTime }
|
||||||
|
value={ condition.time }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -107,4 +238,16 @@ export default class GasPriceEditor extends Component {
|
|||||||
store.setPrice(price);
|
store.setPrice(price);
|
||||||
onChange && onChange('gasPrice', price);
|
onChange && onChange('gasPrice', price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeConditionType = (conditionType) => {
|
||||||
|
this.props.store.setConditionType(conditionType.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeConditionBlock = (event, blockNumber) => {
|
||||||
|
this.props.store.setConditionBlockNumber(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeConditionDateTime = (event, datetime) => {
|
||||||
|
this.props.store.setConditionDateTime(datetime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,26 +21,64 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import GasPriceEditor from './';
|
import GasPriceEditor from './';
|
||||||
|
|
||||||
const api = {
|
let api;
|
||||||
util: {
|
let component;
|
||||||
fromWei: (value) => new BigNumber(value)
|
let store;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const store = {
|
function createApi () {
|
||||||
estimated: '123',
|
api = {
|
||||||
histogram: {},
|
eth: {
|
||||||
priceDefault: '456',
|
blockNumber: sinon.stub().resolves(new BigNumber(3))
|
||||||
totalValue: '789',
|
},
|
||||||
setGas: sinon.stub(),
|
util: {
|
||||||
setPrice: sinon.stub()
|
fromWei: (value) => new BigNumber(value)
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
createApi();
|
||||||
|
|
||||||
|
store = {
|
||||||
|
_api: api,
|
||||||
|
conditionType: 'none',
|
||||||
|
estimated: '123',
|
||||||
|
histogram: {},
|
||||||
|
priceDefault: '456',
|
||||||
|
totalValue: '789',
|
||||||
|
setGas: sinon.stub(),
|
||||||
|
setPrice: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
createStore();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<GasPriceEditor
|
||||||
|
store={ store }
|
||||||
|
{ ...props }
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
api
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
describe('ui/GasPriceEditor', () => {
|
describe('ui/GasPriceEditor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(shallow(
|
expect(component).to.be.ok;
|
||||||
<GasPriceEditor store={ store } />,
|
|
||||||
{ context: { api } }
|
|
||||||
)).to.be.ok;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,17 @@ import { action, computed, observable, transaction } from 'mobx';
|
|||||||
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
||||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
|
|
||||||
|
const CONDITIONS = {
|
||||||
|
NONE: 'none',
|
||||||
|
BLOCK: 'blockNumber',
|
||||||
|
TIME: 'timestamp'
|
||||||
|
};
|
||||||
|
|
||||||
export default class GasPriceEditor {
|
export default class GasPriceEditor {
|
||||||
|
@observable blockNumber = 0;
|
||||||
|
@observable condition = {};
|
||||||
|
@observable conditionBlockError = null;
|
||||||
|
@observable conditionType = CONDITIONS.NONE;
|
||||||
@observable errorEstimated = null;
|
@observable errorEstimated = null;
|
||||||
@observable errorGas = null;
|
@observable errorGas = null;
|
||||||
@observable errorPrice = null;
|
@observable errorPrice = null;
|
||||||
@ -34,13 +44,23 @@ export default class GasPriceEditor {
|
|||||||
@observable priceDefault;
|
@observable priceDefault;
|
||||||
@observable weiValue = '0';
|
@observable weiValue = '0';
|
||||||
|
|
||||||
constructor (api, { gas, gasLimit, gasPrice }) {
|
constructor (api, { gas, gasLimit, gasPrice, condition = null }) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
|
|
||||||
this.gas = gas;
|
this.gas = gas;
|
||||||
this.gasLimit = gasLimit;
|
this.gasLimit = gasLimit;
|
||||||
this.price = gasPrice;
|
this.price = gasPrice;
|
||||||
|
|
||||||
|
if (condition) {
|
||||||
|
if (condition.block) {
|
||||||
|
this.condition = { block: condition.block.toFixed(0) };
|
||||||
|
this.conditionType = CONDITIONS.BLOCK;
|
||||||
|
} else if (condition.time) {
|
||||||
|
this.condition = { time: condition.time };
|
||||||
|
this.conditionType = CONDITIONS.TIME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (api) {
|
if (api) {
|
||||||
this.loadDefaults();
|
this.loadDefaults();
|
||||||
}
|
}
|
||||||
@ -54,6 +74,39 @@ export default class GasPriceEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setConditionType = (conditionType = CONDITIONS.NONE) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.conditionBlockError = null;
|
||||||
|
this.conditionType = conditionType;
|
||||||
|
|
||||||
|
switch (conditionType) {
|
||||||
|
case CONDITIONS.BLOCK:
|
||||||
|
this.condition = Object.assign({}, this.condition, { block: this.blockNumber || 1 });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONDITIONS.TIME:
|
||||||
|
this.condition = Object.assign({}, this.condition, { time: new Date() });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONDITIONS.NONE:
|
||||||
|
default:
|
||||||
|
this.condition = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setConditionBlockNumber = (block) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.conditionBlockError = validatePositiveNumber(block).numberError;
|
||||||
|
this.condition = Object.assign({}, this.condition, { block });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setConditionDateTime = (time) => {
|
||||||
|
this.condition = Object.assign({}, this.condition, { time });
|
||||||
|
}
|
||||||
|
|
||||||
@action setEditing = (isEditing) => {
|
@action setEditing = (isEditing) => {
|
||||||
this.isEditing = isEditing;
|
this.isEditing = isEditing;
|
||||||
}
|
}
|
||||||
@ -130,9 +183,10 @@ export default class GasPriceEditor {
|
|||||||
bucket_bounds: [],
|
bucket_bounds: [],
|
||||||
counts: []
|
counts: []
|
||||||
})),
|
})),
|
||||||
this._api.eth.gasPrice()
|
this._api.eth.gasPrice(),
|
||||||
|
this._api.eth.blockNumber()
|
||||||
])
|
])
|
||||||
.then(([histogram, _price]) => {
|
.then(([histogram, _price, blockNumber]) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
const price = _price.toFixed(0);
|
const price = _price.toFixed(0);
|
||||||
|
|
||||||
@ -142,6 +196,7 @@ export default class GasPriceEditor {
|
|||||||
this.setHistogram(histogram);
|
this.setHistogram(histogram);
|
||||||
|
|
||||||
this.priceDefault = price;
|
this.priceDefault = price;
|
||||||
|
this.blockNumber = blockNumber.toNumber();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -150,13 +205,37 @@ export default class GasPriceEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
overrideTransaction = (transaction) => {
|
overrideTransaction = (transaction) => {
|
||||||
if (this.errorGas || this.errorPrice) {
|
if (this.errorGas || this.errorPrice || this.conditionBlockError) {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, transaction, {
|
const override = {
|
||||||
|
condition: this.condition,
|
||||||
gas: new BigNumber(this.gas || DEFAULT_GAS),
|
gas: new BigNumber(this.gas || DEFAULT_GAS),
|
||||||
gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
|
gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const result = Object.assign({}, transaction, override);
|
||||||
|
|
||||||
|
switch (this.conditionType) {
|
||||||
|
case CONDITIONS.BLOCK:
|
||||||
|
result.condition = { block: new BigNumber(this.condition.block || 0) };
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONDITIONS.TIME:
|
||||||
|
result.condition = { time: this.condition.time };
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONDITIONS.NONE:
|
||||||
|
default:
|
||||||
|
delete result.condition;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
CONDITIONS
|
||||||
|
};
|
||||||
|
@ -21,6 +21,7 @@ import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/consta
|
|||||||
import { ERRORS } from '~/util/validation';
|
import { ERRORS } from '~/util/validation';
|
||||||
|
|
||||||
import GasPriceEditor from './gasPriceEditor';
|
import GasPriceEditor from './gasPriceEditor';
|
||||||
|
import { CONDITIONS } from './store';
|
||||||
|
|
||||||
const { Store } = GasPriceEditor;
|
const { Store } = GasPriceEditor;
|
||||||
|
|
||||||
@ -31,18 +32,30 @@ const HISTOGRAM = {
|
|||||||
counts: [3, 4]
|
counts: [3, 4]
|
||||||
};
|
};
|
||||||
|
|
||||||
const api = {
|
let api;
|
||||||
eth: {
|
|
||||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
|
||||||
},
|
|
||||||
parity: {
|
|
||||||
gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('ui/GasPriceEditor/store', () => {
|
// TODO: share with gasPriceEditor.spec.js
|
||||||
|
function createApi () {
|
||||||
|
api = {
|
||||||
|
eth: {
|
||||||
|
blockNumber: sinon.stub().resolves(new BigNumber(2)),
|
||||||
|
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||||
|
},
|
||||||
|
parity: {
|
||||||
|
gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/GasPriceEditor/Store', () => {
|
||||||
let store = null;
|
let store = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
createApi();
|
||||||
|
});
|
||||||
|
|
||||||
it('is available via GasPriceEditor.Store', () => {
|
it('is available via GasPriceEditor.Store', () => {
|
||||||
expect(new Store(null, {})).to.be.ok;
|
expect(new Store(null, {})).to.be.ok;
|
||||||
});
|
});
|
||||||
@ -65,6 +78,7 @@ describe('ui/GasPriceEditor/store', () => {
|
|||||||
describe('constructor (defaults) when histogram not available', () => {
|
describe('constructor (defaults) when histogram not available', () => {
|
||||||
const api = {
|
const api = {
|
||||||
eth: {
|
eth: {
|
||||||
|
blockNumber: sinon.stub().resolves(new BigNumber(2)),
|
||||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||||
},
|
},
|
||||||
parity: {
|
parity: {
|
||||||
@ -92,6 +106,67 @@ describe('ui/GasPriceEditor/store', () => {
|
|||||||
store = new Store(null, { gasLimit: GASLIMIT });
|
store = new Store(null, { gasLimit: GASLIMIT });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setConditionType', () => {
|
||||||
|
it('sets the actual type', () => {
|
||||||
|
store.setConditionType('testingType');
|
||||||
|
expect(store.conditionType).to.equal('testingType');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears any block error on changing type', () => {
|
||||||
|
store.setConditionBlockNumber(-1);
|
||||||
|
expect(store.conditionBlockError).not.to.be.null;
|
||||||
|
store.setConditionType(CONDITIONS.BLOCK);
|
||||||
|
expect(store.conditionBlockError).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets condition.block when type === CONDITIONS.BLOCK', () => {
|
||||||
|
store.setConditionType(CONDITIONS.BLOCK);
|
||||||
|
expect(store.condition.block).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears condition when type === CONDITIONS.NONE', () => {
|
||||||
|
store.setConditionType(CONDITIONS.BLOCK);
|
||||||
|
store.setConditionType(CONDITIONS.NONE);
|
||||||
|
expect(store.condition).to.deep.equal({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets condition.time when type === CONDITIONS.TIME', () => {
|
||||||
|
store.setConditionType(CONDITIONS.TIME);
|
||||||
|
expect(store.condition.time).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setConditionBlockNumber', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setConditionBlockNumber('testingBlock');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the blockNumber', () => {
|
||||||
|
expect(store.condition.block).to.equal('testingBlock');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the error on invalid numbers', () => {
|
||||||
|
expect(store.conditionBlockError).not.to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the error on negative numbers', () => {
|
||||||
|
store.setConditionBlockNumber(-1);
|
||||||
|
expect(store.conditionBlockError).not.to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears the error on positive numbers', () => {
|
||||||
|
store.setConditionBlockNumber(1000);
|
||||||
|
expect(store.conditionBlockError).to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setConditionDateTime', () => {
|
||||||
|
it('sets the datatime', () => {
|
||||||
|
store.setConditionDateTime('testingDateTime');
|
||||||
|
expect(store.condition.time).to.equal('testingDateTime');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setEditing', () => {
|
describe('setEditing', () => {
|
||||||
it('sets the value', () => {
|
it('sets the value', () => {
|
||||||
expect(store.isEditing).to.be.false;
|
expect(store.isEditing).to.be.false;
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { CircularProgress } from 'material-ui';
|
||||||
|
import moment from 'moment';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import CircularProgress from 'material-ui/CircularProgress';
|
|
||||||
|
|
||||||
import { TypedInput, InputAddress } from '../Form';
|
import { TypedInput, InputAddress } from '../Form';
|
||||||
import MethodDecodingStore from './methodDecodingStore';
|
import MethodDecodingStore from './methodDecodingStore';
|
||||||
@ -128,15 +129,25 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
renderMinBlock () {
|
renderMinBlock () {
|
||||||
const { historic, transaction } = this.props;
|
const { historic, transaction } = this.props;
|
||||||
const { minBlock } = transaction;
|
const { condition } = transaction;
|
||||||
|
|
||||||
if (!minBlock || minBlock.eq(0)) {
|
if (!condition) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (condition.block && condition.block.gt(0)) {
|
||||||
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ minBlock.toFormat(0) }</span></span>
|
return (
|
||||||
);
|
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ condition.block.toFormat(0) }</span></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition.time) {
|
||||||
|
return (
|
||||||
|
<span>, { historic ? 'Submitted' : 'Submission' } at <span className={ styles.highlight }>{ moment(condition.time).format('LLLL') }</span></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAction () {
|
renderAction () {
|
||||||
|
@ -47,20 +47,6 @@
|
|||||||
.title {
|
.title {
|
||||||
background: rgba(0, 0, 0, 0.25) !important;
|
background: rgba(0, 0, 0, 0.25) !important;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps {
|
|
||||||
margin-bottom: -1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.waiting {
|
|
||||||
margin: 1em -1em -1em -1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
|
@ -22,7 +22,7 @@ import { connect } from 'react-redux';
|
|||||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import Container from '../Container';
|
import Container from '../Container';
|
||||||
import Title from './Title';
|
import Title from '../Title';
|
||||||
|
|
||||||
const ACTIONS_STYLE = { borderStyle: 'none' };
|
const ACTIONS_STYLE = { borderStyle: 'none' };
|
||||||
const TITLE_STYLE = { borderStyle: 'none' };
|
const TITLE_STYLE = { borderStyle: 'none' };
|
||||||
@ -63,11 +63,12 @@ class Modal extends Component {
|
|||||||
const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed);
|
const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed);
|
||||||
const header = (
|
const header = (
|
||||||
<Title
|
<Title
|
||||||
|
activeStep={ current }
|
||||||
busy={ busy }
|
busy={ busy }
|
||||||
current={ current }
|
busySteps={ waiting }
|
||||||
|
className={ styles.title }
|
||||||
steps={ steps }
|
steps={ steps }
|
||||||
title={ title }
|
title={ title }
|
||||||
waiting={ waiting }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const classes = `${styles.dialog} ${className}`;
|
const classes = `${styles.dialog} ${className}`;
|
||||||
|
@ -16,13 +16,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$modalMargin: 1.5em;
|
$modalMargin: 1.5em;
|
||||||
|
$modalPadding: 1.5em;
|
||||||
$modalBackZ: 2500;
|
$modalBackZ: 2500;
|
||||||
|
|
||||||
/* This should be the default case, the Portal used as a stand-alone modal */
|
/* This should be the default case, the Portal used as a stand-alone modal */
|
||||||
$modalBottom: 15vh;
|
$modalBottom: $modalMargin;
|
||||||
$modalLeft: $modalMargin;
|
$modalLeft: $modalMargin;
|
||||||
$modalRight: $modalMargin;
|
$modalRight: $modalMargin;
|
||||||
$modalTop: 0;
|
$modalTop: $modalMargin;
|
||||||
$modalZ: 3500;
|
$modalZ: 3500;
|
||||||
|
|
||||||
/* This is the case where popped-up over another modal, Portal or otherwise */
|
/* This is the case where popped-up over another modal, Portal or otherwise */
|
||||||
@ -55,7 +56,9 @@ $popoverZ: 3600;
|
|||||||
background-color: rgba(0, 0, 0, 1);
|
background-color: rgba(0, 0, 0, 1);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1.5em;
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $modalPadding;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@ -77,22 +80,48 @@ $popoverZ: 3600;
|
|||||||
width: calc(100vw - $popoverLeft - $popoverRight);
|
width: calc(100vw - $popoverLeft - $popoverRight);
|
||||||
z-index: $popoverZ;
|
z-index: $popoverZ;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.closeIcon {
|
.buttonRow {
|
||||||
font-size: 4em;
|
display: flex;
|
||||||
position: absolute;
|
flex-direction: row;
|
||||||
right: 1rem;
|
flex-wrap: nowrap;
|
||||||
top: 0.5rem;
|
justify-content: flex-end;
|
||||||
z-index: 100;
|
padding: $modalPadding 0 0 0;
|
||||||
|
|
||||||
&, * {
|
button:not([disabled]) {
|
||||||
height: 48px !important;
|
color: white !important;
|
||||||
width: 48px !important;
|
|
||||||
|
svg {
|
||||||
|
fill: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.childContainer {
|
||||||
cursor: pointer;
|
flex: 1;
|
||||||
opacity: 0.5;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeIcon {
|
||||||
|
font-size: 4em;
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 0.5rem;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
&, * {
|
||||||
|
height: 48px !important;
|
||||||
|
width: 48px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleRow {
|
||||||
|
margin-bottom: $modalPadding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { Button } from '~/ui';
|
||||||
import PlaygroundExample from '~/playground/playgroundExample';
|
import PlaygroundExample from '~/playground/playgroundExample';
|
||||||
|
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
@ -77,6 +78,29 @@ export default class PortalExample extends Component {
|
|||||||
</Portal>
|
</Portal>
|
||||||
</div>
|
</div>
|
||||||
</PlaygroundExample>
|
</PlaygroundExample>
|
||||||
|
|
||||||
|
<PlaygroundExample name='Portal with Buttons'>
|
||||||
|
<div>
|
||||||
|
<button onClick={ this.handleOpen(4) }>Open</button>
|
||||||
|
<Portal
|
||||||
|
activeStep={ 0 }
|
||||||
|
buttons={ [
|
||||||
|
<Button
|
||||||
|
key='close'
|
||||||
|
label='close'
|
||||||
|
onClick={ this.handleClose }
|
||||||
|
/>
|
||||||
|
] }
|
||||||
|
isChildModal
|
||||||
|
open={ open[4] || false }
|
||||||
|
onClose={ this.handleClose }
|
||||||
|
steps={ [ 'step 1', 'step 2' ] }
|
||||||
|
title='Portal with button'
|
||||||
|
>
|
||||||
|
<p>This is the fourth portal</p>
|
||||||
|
</Portal>
|
||||||
|
</div>
|
||||||
|
</PlaygroundExample>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,10 @@ import ReactDOM from 'react-dom';
|
|||||||
import ReactPortal from 'react-portal';
|
import ReactPortal from 'react-portal';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
|
|
||||||
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
import { CloseIcon } from '~/ui/Icons';
|
import { CloseIcon } from '~/ui/Icons';
|
||||||
import ParityBackground from '~/ui/ParityBackground';
|
import ParityBackground from '~/ui/ParityBackground';
|
||||||
|
import Title from '~/ui/Title';
|
||||||
|
|
||||||
import styles from './portal.css';
|
import styles from './portal.css';
|
||||||
|
|
||||||
@ -29,14 +31,35 @@ export default class Portal extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
open: PropTypes.bool.isRequired,
|
open: PropTypes.bool.isRequired,
|
||||||
|
activeStep: PropTypes.number,
|
||||||
|
busy: PropTypes.bool,
|
||||||
|
busySteps: PropTypes.array,
|
||||||
|
buttons: PropTypes.array,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
hideClose: PropTypes.bool,
|
||||||
isChildModal: PropTypes.bool,
|
isChildModal: PropTypes.bool,
|
||||||
onKeyDown: PropTypes.func
|
onKeyDown: PropTypes.func,
|
||||||
|
steps: PropTypes.array,
|
||||||
|
title: nodeOrStringProptype()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.setBodyOverflow(this.props.open);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.open !== this.props.open) {
|
||||||
|
this.setBodyOverflow(nextProps.open);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.setBodyOverflow(false);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, className, isChildModal, open } = this.props;
|
const { activeStep, busy, busySteps, children, className, isChildModal, open, steps, title } = this.props;
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
return null;
|
return null;
|
||||||
@ -69,32 +92,72 @@ export default class Portal extends Component {
|
|||||||
onKeyUp={ this.handleKeyUp }
|
onKeyUp={ this.handleKeyUp }
|
||||||
/>
|
/>
|
||||||
<ParityBackground className={ styles.parityBackground } />
|
<ParityBackground className={ styles.parityBackground } />
|
||||||
<div
|
{ this.renderClose() }
|
||||||
className={ styles.closeIcon }
|
<Title
|
||||||
onClick={ this.handleClose }
|
activeStep={ activeStep }
|
||||||
>
|
busy={ busy }
|
||||||
<CloseIcon />
|
busySteps={ busySteps }
|
||||||
|
className={ styles.titleRow }
|
||||||
|
steps={ steps }
|
||||||
|
title={ title }
|
||||||
|
/>
|
||||||
|
<div className={ styles.childContainer }>
|
||||||
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
{ children }
|
{ this.renderButtons() }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ReactPortal>
|
</ReactPortal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
const { buttons } = this.props;
|
||||||
|
|
||||||
|
if (!buttons) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.buttonRow }>
|
||||||
|
{ buttons }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderClose () {
|
||||||
|
const { hideClose } = this.props;
|
||||||
|
|
||||||
|
if (hideClose) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CloseIcon
|
||||||
|
className={ styles.closeIcon }
|
||||||
|
onClick={ this.handleClose }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
stopEvent = (event) => {
|
stopEvent = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
this.props.onClose();
|
const { hideClose, onClose } = this.props;
|
||||||
|
|
||||||
|
if (!hideClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (event) => {
|
handleKeyDown = (event) => {
|
||||||
const { onKeyDown } = this.props;
|
const { onKeyDown } = this.props;
|
||||||
|
|
||||||
event.persist();
|
event.persist();
|
||||||
|
|
||||||
return onKeyDown
|
return onKeyDown
|
||||||
? onKeyDown(event)
|
? onKeyDown(event)
|
||||||
: false;
|
: false;
|
||||||
@ -111,10 +174,11 @@ export default class Portal extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDOMAction = (ref, method) => {
|
handleDOMAction = (ref, method) => {
|
||||||
const refItem = typeof ref === 'string'
|
const element = ReactDOM.findDOMNode(
|
||||||
? this.refs[ref]
|
typeof ref === 'string'
|
||||||
: ref;
|
? this.refs[ref]
|
||||||
const element = ReactDOM.findDOMNode(refItem);
|
: ref
|
||||||
|
);
|
||||||
|
|
||||||
if (!element || typeof element[method] !== 'function') {
|
if (!element || typeof element[method] !== 'function') {
|
||||||
console.warn('could not find', ref, 'or method', method);
|
console.warn('could not find', ref, 'or method', method);
|
||||||
@ -123,4 +187,12 @@ export default class Portal extends Component {
|
|||||||
|
|
||||||
return element[method]();
|
return element[method]();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBodyOverflow (open) {
|
||||||
|
if (!this.props.isChildModal) {
|
||||||
|
document.body.style.overflow = open
|
||||||
|
? 'hidden'
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,4 +44,21 @@ describe('ui/Portal', () => {
|
|||||||
it('renders defaults', () => {
|
it('renders defaults', () => {
|
||||||
expect(component).to.be.ok;
|
expect(component).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('title rendering', () => {
|
||||||
|
const TITLE = 'some test title';
|
||||||
|
let title;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
title = render({ title: TITLE }).find('Title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the specified title', () => {
|
||||||
|
expect(title).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the passed title', () => {
|
||||||
|
expect(title.props().title).to.equal(TITLE);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
26
js/src/ui/Title/title.css
Normal file
26
js/src/ui/Title/title.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity 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 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.title {
|
||||||
|
.steps {
|
||||||
|
margin: -0.5em 0 -1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waiting {
|
||||||
|
margin: 1em -1em -1em -1em;
|
||||||
|
}
|
||||||
|
}
|
@ -14,35 +14,49 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { LinearProgress } from 'material-ui';
|
import { LinearProgress } from 'material-ui';
|
||||||
import { Step, Stepper, StepLabel } from 'material-ui/Stepper';
|
import { Step, Stepper, StepLabel } from 'material-ui/Stepper';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
// TODO: It would make sense (going forward) to replace all uses of
|
||||||
|
// ContainerTitle with this component. In that case the styles for the
|
||||||
|
// h3 (title) can be pulled from there. (As it stands the duplication
|
||||||
|
// between the 2 has been removed, but as a short-term DRY only)
|
||||||
|
import { Title as ContainerTitle } from '~/ui/Container';
|
||||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from '../modal.css';
|
import styles from './title.css';
|
||||||
|
|
||||||
export default class Title extends Component {
|
export default class Title extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
activeStep: PropTypes.number,
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
current: PropTypes.number,
|
busySteps: PropTypes.array,
|
||||||
|
className: PropTypes.string,
|
||||||
steps: PropTypes.array,
|
steps: PropTypes.array,
|
||||||
waiting: PropTypes.array,
|
|
||||||
title: nodeOrStringProptype()
|
title: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { current, steps, title } = this.props;
|
const { activeStep, className, steps, title } = this.props;
|
||||||
|
|
||||||
|
if (!title && !steps) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.title }>
|
<div
|
||||||
<h3>
|
className={
|
||||||
{
|
[styles.title, className].join(' ')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ContainerTitle
|
||||||
|
title={
|
||||||
steps
|
steps
|
||||||
? steps[current]
|
? steps[activeStep || 0]
|
||||||
: title
|
: title
|
||||||
}
|
}
|
||||||
</h3>
|
/>
|
||||||
{ this.renderSteps() }
|
{ this.renderSteps() }
|
||||||
{ this.renderWaiting() }
|
{ this.renderWaiting() }
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +64,7 @@ export default class Title extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSteps () {
|
renderSteps () {
|
||||||
const { current, steps } = this.props;
|
const { activeStep, steps } = this.props;
|
||||||
|
|
||||||
if (!steps) {
|
if (!steps) {
|
||||||
return;
|
return;
|
||||||
@ -58,7 +72,7 @@ export default class Title extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.steps }>
|
<div className={ styles.steps }>
|
||||||
<Stepper activeStep={ current }>
|
<Stepper activeStep={ activeStep }>
|
||||||
{ this.renderTimeline() }
|
{ this.renderTimeline() }
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</div>
|
</div>
|
||||||
@ -80,8 +94,8 @@ export default class Title extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderWaiting () {
|
renderWaiting () {
|
||||||
const { current, busy, waiting } = this.props;
|
const { activeStep, busy, busySteps } = this.props;
|
||||||
const isWaiting = busy || (waiting || []).includes(current);
|
const isWaiting = busy || (busySteps || []).includes(activeStep);
|
||||||
|
|
||||||
if (!isWaiting) {
|
if (!isWaiting) {
|
||||||
return null;
|
return null;
|
@ -35,7 +35,7 @@ import DappIcon from './DappIcon';
|
|||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import Errors from './Errors';
|
import Errors from './Errors';
|
||||||
import Features, { FEATURES, FeaturesStore } from './Features';
|
import Features, { FEATURES, FeaturesStore } from './Features';
|
||||||
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
|
import Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
|
||||||
import GasPriceEditor from './GasPriceEditor';
|
import GasPriceEditor from './GasPriceEditor';
|
||||||
import GasPriceSelector from './GasPriceSelector';
|
import GasPriceSelector from './GasPriceSelector';
|
||||||
import Icons from './Icons';
|
import Icons from './Icons';
|
||||||
@ -55,6 +55,7 @@ import SectionList from './SectionList';
|
|||||||
import ShortenedHash from './ShortenedHash';
|
import ShortenedHash from './ShortenedHash';
|
||||||
import SignerIcon from './SignerIcon';
|
import SignerIcon from './SignerIcon';
|
||||||
import Tags from './Tags';
|
import Tags from './Tags';
|
||||||
|
import Title from './Title';
|
||||||
import Tooltips, { Tooltip } from './Tooltips';
|
import Tooltips, { Tooltip } from './Tooltips';
|
||||||
import TxHash from './TxHash';
|
import TxHash from './TxHash';
|
||||||
import TxList from './TxList';
|
import TxList from './TxList';
|
||||||
@ -79,8 +80,9 @@ export {
|
|||||||
ContextProvider,
|
ContextProvider,
|
||||||
CopyToClipboard,
|
CopyToClipboard,
|
||||||
CurrencySymbol,
|
CurrencySymbol,
|
||||||
DappIcon,
|
|
||||||
DappCard,
|
DappCard,
|
||||||
|
DappIcon,
|
||||||
|
DappUrlInput,
|
||||||
Editor,
|
Editor,
|
||||||
Errors,
|
Errors,
|
||||||
FEATURES,
|
FEATURES,
|
||||||
@ -95,9 +97,12 @@ export {
|
|||||||
InputAddress,
|
InputAddress,
|
||||||
InputAddressSelect,
|
InputAddressSelect,
|
||||||
InputChip,
|
InputChip,
|
||||||
|
InputDate,
|
||||||
InputInline,
|
InputInline,
|
||||||
|
InputTime,
|
||||||
IdentityIcon,
|
IdentityIcon,
|
||||||
IdentityName,
|
IdentityName,
|
||||||
|
Label,
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
Loading,
|
Loading,
|
||||||
MethodDecoding,
|
MethodDecoding,
|
||||||
@ -116,6 +121,7 @@ export {
|
|||||||
SectionList,
|
SectionList,
|
||||||
SignerIcon,
|
SignerIcon,
|
||||||
Tags,
|
Tags,
|
||||||
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Tooltips,
|
Tooltips,
|
||||||
TxHash,
|
TxHash,
|
||||||
|
60
js/src/util/dapplink.js
Normal file
60
js/src/util/dapplink.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import base32 from 'base32.js';
|
||||||
|
|
||||||
|
const BASE_URL = '.web.web3.site';
|
||||||
|
const ENCODER_OPTS = { type: 'crockford' };
|
||||||
|
|
||||||
|
export function encodePath (token, url) {
|
||||||
|
const encoder = new base32.Encoder(ENCODER_OPTS);
|
||||||
|
const chars = `${token}+${url}`
|
||||||
|
.split('')
|
||||||
|
.map((char) => char.charCodeAt(0));
|
||||||
|
|
||||||
|
return encoder
|
||||||
|
.write(chars) // add the characters to encode
|
||||||
|
.finalize(); // create the encoded string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeUrl (token, url) {
|
||||||
|
const encoded = encodePath(token, url)
|
||||||
|
.match(/.{1,63}/g) // split into 63-character chunks, max length is 64 for URLs parts
|
||||||
|
.join('.'); // add '.' between URL parts
|
||||||
|
|
||||||
|
return `${encoded}${BASE_URL}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This export is really more a helper along the way of verifying the actual
|
||||||
|
// encoding (being able to decode test values from the node layer), than meant to
|
||||||
|
// be used as-is. Should the need arrise to decode URLs as well (instead of just
|
||||||
|
// producing), it would make sense to further split the output into the token/URL
|
||||||
|
export function decode (encoded) {
|
||||||
|
const decoder = new base32.Decoder(ENCODER_OPTS);
|
||||||
|
const sanitized = encoded
|
||||||
|
.replace(BASE_URL, '') // remove the BASE URL
|
||||||
|
.split('.') // split the string on the '.' (63-char boundaries)
|
||||||
|
.join(''); // combine without the '.'
|
||||||
|
|
||||||
|
return decoder
|
||||||
|
.write(sanitized) // add the string to decode
|
||||||
|
.finalize() // create the decoded buffer
|
||||||
|
.toString(); // create string from buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
BASE_URL
|
||||||
|
};
|
83
js/src/util/dapplink.spec.js
Normal file
83
js/src/util/dapplink.spec.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { BASE_URL, decode, encodePath, encodeUrl } from './dapplink';
|
||||||
|
|
||||||
|
const TEST_TOKEN = 'token';
|
||||||
|
const TEST_URL = 'https://parity.io';
|
||||||
|
const TEST_URL_LONG = 'http://some.very.very.very.long.long.long.domain.example.com';
|
||||||
|
const TEST_PREFIX = 'EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY';
|
||||||
|
const TEST_PREFIX_LONG = [
|
||||||
|
'EHQPPSBE5DM78X3G78QJYWVFDNJJWXK5E9WJWXK5E9WJWXK5E9WJWV3FDSKJWV3', 'FDSKJWV3FDSKJWS3FDNGPJVHECNW62VBGDHJJWRVFDM'
|
||||||
|
].join('.');
|
||||||
|
const TEST_RESULT = `${TEST_PREFIX}${BASE_URL}`;
|
||||||
|
const TEST_ENCODED = `${TEST_TOKEN}+${TEST_URL}`;
|
||||||
|
|
||||||
|
describe('util/ethlink', () => {
|
||||||
|
describe('decode', () => {
|
||||||
|
it('decodes into encoded url', () => {
|
||||||
|
expect(decode(TEST_PREFIX)).to.equal(TEST_ENCODED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes full into encoded url', () => {
|
||||||
|
expect(decode(TEST_RESULT)).to.equal(TEST_ENCODED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encodePath', () => {
|
||||||
|
it('encodes a url/token combination', () => {
|
||||||
|
expect(encodePath(TEST_TOKEN, TEST_URL)).to.equal(TEST_PREFIX);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes when token changes', () => {
|
||||||
|
expect(encodePath('test-token-2', TEST_URL)).not.to.equal(TEST_PREFIX);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes when url changes', () => {
|
||||||
|
expect(encodePath(TEST_TOKEN, 'http://other.example.com')).not.to.equal(TEST_PREFIX);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encodeUrl', () => {
|
||||||
|
it('encodes a url/token combination', () => {
|
||||||
|
expect(encodeUrl(TEST_TOKEN, TEST_URL)).to.equal(TEST_RESULT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes when token changes', () => {
|
||||||
|
expect(encodeUrl('test-token-2', TEST_URL)).not.to.equal(TEST_RESULT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes when url changes', () => {
|
||||||
|
expect(encodeUrl(TEST_TOKEN, 'http://other.example.com')).not.to.equal(TEST_RESULT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('splitting', () => {
|
||||||
|
let encoded;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
encoded = encodeUrl(TEST_TOKEN, TEST_URL_LONG);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('splits long values into boundary parts', () => {
|
||||||
|
expect(encoded).to.equal(`${TEST_PREFIX_LONG}${BASE_URL}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('first part 63 characters', () => {
|
||||||
|
expect(encoded.split('.')[0].length).to.equal(63);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
52
js/src/views/Application/Extension/extension.css
Normal file
52
js/src/views/Application/Extension/extension.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
/* This file is part of Parity.
|
||||||
|
/*
|
||||||
|
/* Parity 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 3 of the License, or
|
||||||
|
/* (at your option) any later version.
|
||||||
|
/*
|
||||||
|
/* Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.body {
|
||||||
|
background: #f80;
|
||||||
|
color: white;
|
||||||
|
opacity: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 1em 4em 1em 2em;
|
||||||
|
position: fixed;
|
||||||
|
right: 1.5em;
|
||||||
|
top: 1.5em;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
color: white !important;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonrow {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
}
|
||||||
|
}
|
74
js/src/views/Application/Extension/extension.js
Normal file
74
js/src/views/Application/Extension/extension.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Button } from '~/ui';
|
||||||
|
import { CloseIcon, CheckIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
import styles from './extension.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Extension extends Component {
|
||||||
|
store = new Store();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { showWarning } = this.store;
|
||||||
|
|
||||||
|
if (!showWarning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.body }>
|
||||||
|
<CloseIcon
|
||||||
|
className={ styles.close }
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='extension.intro'
|
||||||
|
defaultMessage='Parity now has an extension available for Chrome that allows safe browsing of Ethereum-enabled distributed applications. It is highly recommended that you install this extension to further enhance your Parity experience.'
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className={ styles.buttonrow }>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
icon={ <CheckIcon /> }
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='extension.install'
|
||||||
|
defaultMessage='Install the extension now'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onInstallClick }
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.store.snoozeWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
onInstallClick = () => {
|
||||||
|
this.store.installExtension();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/views/Application/Extension/index.js
Normal file
17
js/src/views/Application/Extension/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './extension';
|
89
js/src/views/Application/Extension/store.js
Normal file
89
js/src/views/Application/Extension/store.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* global chrome */
|
||||||
|
|
||||||
|
import { action, computed, observable } from 'mobx';
|
||||||
|
|
||||||
|
import store from 'store';
|
||||||
|
import browser from 'useragent.js/lib/browser';
|
||||||
|
|
||||||
|
const A_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay';
|
||||||
|
|
||||||
|
// 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig';
|
||||||
|
const EXTENSION_PAGE = 'https://chrome.google.com/webstore/detail/himekenlppkgeaoeddcliojfddemadig';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable isInstalling = false;
|
||||||
|
@observable nextDisplay = 0;
|
||||||
|
@observable shouldInstall = false;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this.nextDisplay = store.get(NEXT_DISPLAY) || 0;
|
||||||
|
this.testInstall();
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get showWarning () {
|
||||||
|
return !this.isInstalling && this.shouldInstall && (Date.now() > this.nextDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setInstalling = (isInstalling) => {
|
||||||
|
this.isInstalling = isInstalling;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action snoozeWarning = (sleep = A_DAY) => {
|
||||||
|
this.nextDisplay = Date.now() + sleep;
|
||||||
|
store.set(NEXT_DISPLAY, this.nextDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action testInstall = () => {
|
||||||
|
this.shouldInstall = this.readStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
readStatus = () => {
|
||||||
|
const hasExtension = Symbol.for('parity.extension') in window;
|
||||||
|
const ua = browser.analyze(navigator.userAgent || '');
|
||||||
|
|
||||||
|
if (hasExtension) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ua || {}).name.toLowerCase() === 'chrome';
|
||||||
|
}
|
||||||
|
|
||||||
|
installExtension = () => {
|
||||||
|
this.setInstalling(true);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
|
||||||
|
link.setAttribute('rel', 'chrome-webstore-item');
|
||||||
|
link.setAttribute('href', EXTENSION_PAGE);
|
||||||
|
document.querySelector('head').appendChild(link);
|
||||||
|
|
||||||
|
if (chrome && chrome.webstore && chrome.webstore.install) {
|
||||||
|
chrome.webstore.install(EXTENSION_PAGE, resolve, reject);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Direct installation failed.'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('Unable to perform direct install', error);
|
||||||
|
window.open(EXTENSION_PAGE, '_blank');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ import ParityBar from '../ParityBar';
|
|||||||
import Snackbar from './Snackbar';
|
import Snackbar from './Snackbar';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import DappContainer from './DappContainer';
|
import DappContainer from './DappContainer';
|
||||||
|
import Extension from './Extension';
|
||||||
import FrameError from './FrameError';
|
import FrameError from './FrameError';
|
||||||
import Status from './Status';
|
import Status from './Status';
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
@ -106,6 +107,7 @@ class Application extends Component {
|
|||||||
? <Status upgradeStore={ this.upgradeStore } />
|
? <Status upgradeStore={ this.upgradeStore } />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
<Extension />
|
||||||
<Snackbar />
|
<Snackbar />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$overlayZ: 10000;
|
||||||
|
$modalZ: 10001;
|
||||||
|
|
||||||
.account {
|
.account {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -61,7 +64,7 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: rgba(255, 255, 255, 0.5);
|
background: rgba(255, 255, 255, 0.5);
|
||||||
z-index: 10000;
|
z-index: $overlayZ;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +73,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
z-index: 10001;
|
z-index: $modalZ;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +112,8 @@
|
|||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 50vh;
|
min-height: 30vh;
|
||||||
|
max-height: 80vh;
|
||||||
max-width: calc(100vw - 1em);
|
max-width: calc(100vw - 1em);
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -73,14 +73,14 @@ export default class TransactionMainDetails extends Component {
|
|||||||
: transaction
|
: transaction
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{ this.renderEditGas() }
|
{ this.renderEditTx() }
|
||||||
</div>
|
</div>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditGas () {
|
renderEditTx () {
|
||||||
const { gasStore } = this.props;
|
const { gasStore } = this.props;
|
||||||
|
|
||||||
if (!gasStore) {
|
if (!gasStore) {
|
||||||
@ -91,7 +91,7 @@ export default class TransactionMainDetails extends Component {
|
|||||||
<div className={ styles.editButtonRow }>
|
<div className={ styles.editButtonRow }>
|
||||||
<Button
|
<Button
|
||||||
icon={ <MapsLocalGasStation /> }
|
icon={ <MapsLocalGasStation /> }
|
||||||
label='Edit gas/gasPrice'
|
label='Edit conditions/gas/gasPrice'
|
||||||
onClick={ this.toggleGasEditor }
|
onClick={ this.toggleGasEditor }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +45,7 @@ export default class TransactionPending extends Component {
|
|||||||
onReject: PropTypes.func.isRequired,
|
onReject: PropTypes.func.isRequired,
|
||||||
store: PropTypes.object.isRequired,
|
store: PropTypes.object.isRequired,
|
||||||
transaction: PropTypes.shape({
|
transaction: PropTypes.shape({
|
||||||
|
condition: PropTypes.object,
|
||||||
data: PropTypes.string,
|
data: PropTypes.string,
|
||||||
from: PropTypes.string.isRequired,
|
from: PropTypes.string.isRequired,
|
||||||
gas: PropTypes.object.isRequired,
|
gas: PropTypes.object.isRequired,
|
||||||
@ -59,6 +60,7 @@ export default class TransactionPending extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
gasStore = new GasPriceEditor.Store(this.context.api, {
|
gasStore = new GasPriceEditor.Store(this.context.api, {
|
||||||
|
condition: this.props.transaction.condition,
|
||||||
gas: this.props.transaction.gas.toFixed(),
|
gas: this.props.transaction.gas.toFixed(),
|
||||||
gasLimit: this.props.gasLimit,
|
gasLimit: this.props.gasLimit,
|
||||||
gasPrice: this.props.transaction.gasPrice.toFixed()
|
gasPrice: this.props.transaction.gasPrice.toFixed()
|
||||||
@ -80,7 +82,7 @@ export default class TransactionPending extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
return this.gasStore.isEditing
|
return this.gasStore.isEditing
|
||||||
? this.renderGasEditor()
|
? this.renderTxEditor()
|
||||||
: this.renderTransaction();
|
: this.renderTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +117,7 @@ export default class TransactionPending extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGasEditor () {
|
renderTxEditor () {
|
||||||
const { className } = this.props;
|
const { className } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -133,15 +135,21 @@ export default class TransactionPending extends Component {
|
|||||||
onConfirm = (data) => {
|
onConfirm = (data) => {
|
||||||
const { id, transaction } = this.props;
|
const { id, transaction } = this.props;
|
||||||
const { password, wallet } = data;
|
const { password, wallet } = data;
|
||||||
const { gas, gasPrice } = this.gasStore.overrideTransaction(transaction);
|
const { condition, gas, gasPrice } = this.gasStore.overrideTransaction(transaction);
|
||||||
|
|
||||||
this.props.onConfirm({
|
const options = {
|
||||||
gas,
|
gas,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
id,
|
id,
|
||||||
password,
|
password,
|
||||||
wallet
|
wallet
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (condition && (condition.block || condition.time)) {
|
||||||
|
options.condition = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onConfirm(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
onReject = () => {
|
onReject = () => {
|
||||||
|
@ -14,101 +14,62 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import Refresh from 'material-ui/svg-icons/navigation/refresh';
|
|
||||||
import Close from 'material-ui/svg-icons/navigation/close';
|
|
||||||
import Subdirectory from 'material-ui/svg-icons/navigation/subdirectory-arrow-left';
|
import Subdirectory from 'material-ui/svg-icons/navigation/subdirectory-arrow-left';
|
||||||
|
|
||||||
import { Button } from '~/ui';
|
import { Button, DappUrlInput } from '~/ui';
|
||||||
|
import { CloseIcon, RefreshIcon } from '~/ui/Icons';
|
||||||
const KEY_ESC = 27;
|
|
||||||
const KEY_ENTER = 13;
|
|
||||||
|
|
||||||
|
@observer
|
||||||
export default class AddressBar extends Component {
|
export default class AddressBar extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
isLoading: PropTypes.bool.isRequired,
|
store: PropTypes.object.isRequired
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onRefresh: PropTypes.func.isRequired,
|
|
||||||
url: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
currentUrl: this.props.url
|
|
||||||
};
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (this.props.url === nextProps.url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
currentUrl: nextProps.url
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isPristine () {
|
|
||||||
return this.state.currentUrl === this.props.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isLoading } = this.props;
|
const { isLoading, isPristine, nextUrl } = this.props.store;
|
||||||
const { currentUrl } = this.state;
|
|
||||||
const isPristine = this.isPristine();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ this.props.className }>
|
<div className={ this.props.className }>
|
||||||
<Button
|
<Button
|
||||||
disabled={ isLoading }
|
disabled={ isLoading }
|
||||||
|
onClick={ this.onRefreshUrl }
|
||||||
icon={
|
icon={
|
||||||
isLoading
|
isLoading
|
||||||
? <Close />
|
? <CloseIcon />
|
||||||
: <Refresh />
|
: <RefreshIcon />
|
||||||
}
|
}
|
||||||
onClick={ this.onGo }
|
|
||||||
/>
|
/>
|
||||||
<input
|
<DappUrlInput
|
||||||
onChange={ this.onUpdateUrl }
|
onChange={ this.onChangeUrl }
|
||||||
onKeyDown={ this.onKey }
|
onGoto={ this.onGotoUrl }
|
||||||
type='text'
|
onRestore={ this.onRestoreUrl }
|
||||||
value={ currentUrl }
|
url={ nextUrl }
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={ isPristine }
|
disabled={ isPristine }
|
||||||
|
onClick={ this.onGotoUrl }
|
||||||
icon={ <Subdirectory /> }
|
icon={ <Subdirectory /> }
|
||||||
onClick={ this.onGo }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateUrl = (ev) => {
|
onRefreshUrl = () => {
|
||||||
this.setState({
|
this.props.store.reload();
|
||||||
currentUrl: ev.target.value
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onKey = (ev) => {
|
onChangeUrl = (url) => {
|
||||||
const key = ev.which;
|
this.props.store.setNextUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
if (key === KEY_ESC) {
|
onGotoUrl = () => {
|
||||||
this.setState({
|
this.props.store.gotoUrl();
|
||||||
currentUrl: this.props.url
|
}
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === KEY_ENTER) {
|
onRestoreUrl = () => {
|
||||||
this.onGo();
|
this.props.store.restoreUrl();
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onGo = () => {
|
|
||||||
if (this.isPristine()) {
|
|
||||||
this.props.onRefresh();
|
|
||||||
} else {
|
|
||||||
this.props.onChange(this.state.currentUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
48
js/src/views/Web/AddressBar/addressBar.spec.js
Normal file
48
js/src/views/Web/AddressBar/addressBar.spec.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AddressBar from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
store = {
|
||||||
|
nextUrl: 'https://parity.io'
|
||||||
|
};
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
component = shallow(
|
||||||
|
<AddressBar
|
||||||
|
className='testClass'
|
||||||
|
store={ createStore() }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Web/AddressBar', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
158
js/src/views/Web/store.js
Normal file
158
js/src/views/Web/store.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
import localStore from 'store';
|
||||||
|
import { parse as parseUrl } from 'url';
|
||||||
|
|
||||||
|
import { encodePath, encodeUrl } from '~/util/dapplink';
|
||||||
|
|
||||||
|
const DEFAULT_URL = 'https://mkr.market';
|
||||||
|
const LS_LAST_ADDRESS = '_parity::webLastAddress';
|
||||||
|
|
||||||
|
const hasProtocol = /^https?:\/\//;
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable counter = Date.now();
|
||||||
|
@observable currentUrl = null;
|
||||||
|
@observable history = [];
|
||||||
|
@observable isLoading = false;
|
||||||
|
@observable parsedUrl = null;
|
||||||
|
@observable nextUrl = null;
|
||||||
|
@observable token = null;
|
||||||
|
|
||||||
|
constructor (api) {
|
||||||
|
this._api = api;
|
||||||
|
|
||||||
|
this.nextUrl = this.currentUrl = this.loadLastUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get encodedPath () {
|
||||||
|
return `${this._api.dappsUrl}/web/${encodePath(this.token, this.currentUrl)}?t=${this.counter}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get encodedUrl () {
|
||||||
|
return `http://${encodeUrl(this.token, this.currentUrl)}:${this._api.dappsPort}?t=${this.counter}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get frameId () {
|
||||||
|
return `_web_iframe_${this.counter}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isPristine () {
|
||||||
|
return this.currentUrl === this.nextUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action gotoUrl = (_url) => {
|
||||||
|
transaction(() => {
|
||||||
|
let url = (_url || this.nextUrl).trim().replace(/\/+$/, '');
|
||||||
|
|
||||||
|
if (!hasProtocol.test(url)) {
|
||||||
|
url = `https://${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setNextUrl(url);
|
||||||
|
this.setCurrentUrl(this.nextUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action reload = () => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setLoading(true);
|
||||||
|
this.counter = Date.now();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action restoreUrl = () => {
|
||||||
|
this.setNextUrl(this.currentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setHistory = (history) => {
|
||||||
|
this.history = history;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setLoading = (isLoading) => {
|
||||||
|
this.isLoading = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setToken = (token) => {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCurrentUrl = (_url) => {
|
||||||
|
const url = _url || this.currentUrl;
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
|
this.currentUrl = url;
|
||||||
|
this.parsedUrl = parseUrl(url);
|
||||||
|
|
||||||
|
this.saveLastUrl();
|
||||||
|
|
||||||
|
this.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNextUrl = (url) => {
|
||||||
|
this.nextUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateToken = () => {
|
||||||
|
this.setToken(null);
|
||||||
|
|
||||||
|
return this._api.signer
|
||||||
|
.generateWebProxyAccessToken()
|
||||||
|
.then((token) => {
|
||||||
|
this.setToken(token);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('generateToken', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadHistory = () => {
|
||||||
|
return this._api.parity
|
||||||
|
.listRecentDapps()
|
||||||
|
.then((apps) => {
|
||||||
|
this.setHistory(apps);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('loadHistory', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLastUrl = () => {
|
||||||
|
return localStore.get(LS_LAST_ADDRESS) || DEFAULT_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveLastUrl = () => {
|
||||||
|
return localStore.set(LS_LAST_ADDRESS, this.currentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get (api) {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new Store(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DEFAULT_URL,
|
||||||
|
LS_LAST_ADDRESS
|
||||||
|
};
|
202
js/src/views/Web/store.spec.js
Normal file
202
js/src/views/Web/store.spec.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
|
||||||
|
const TEST_HISTORY = ['somethingA', 'somethingB'];
|
||||||
|
const TEST_TOKEN = 'testing-123';
|
||||||
|
const TEST_URL1 = 'http://some.test.domain.com';
|
||||||
|
const TEST_URL2 = 'http://something.different.com';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
api = {
|
||||||
|
dappsPort: 8080,
|
||||||
|
dappsUrl: 'http://home.web3.site:8080',
|
||||||
|
parity: {
|
||||||
|
listRecentDapps: sinon.stub().resolves(TEST_HISTORY)
|
||||||
|
},
|
||||||
|
signer: {
|
||||||
|
generateWebProxyAccessToken: sinon.stub().resolves(TEST_TOKEN)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create () {
|
||||||
|
store = new Store(createApi());
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Web/Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
create();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@action', () => {
|
||||||
|
describe('gotoUrl', () => {
|
||||||
|
it('uses the nextUrl when none specified', () => {
|
||||||
|
store.setNextUrl('https://parity.io');
|
||||||
|
store.gotoUrl();
|
||||||
|
|
||||||
|
expect(store.currentUrl).to.equal('https://parity.io');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds https when no protocol', () => {
|
||||||
|
store.gotoUrl('google.com');
|
||||||
|
|
||||||
|
expect(store.currentUrl).to.equal('https://google.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('restoreUrl', () => {
|
||||||
|
it('sets the nextUrl to the currentUrl', () => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
store.setNextUrl(TEST_URL2);
|
||||||
|
store.restoreUrl();
|
||||||
|
|
||||||
|
expect(store.nextUrl).to.equal(TEST_URL1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setCurrentUrl', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the url', () => {
|
||||||
|
expect(store.currentUrl).to.equal(TEST_URL1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setHistory', () => {
|
||||||
|
it('sets the history', () => {
|
||||||
|
store.setHistory(TEST_HISTORY);
|
||||||
|
expect(store.history.peek()).to.deep.equal(TEST_HISTORY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setLoading', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setLoading(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the loading state (true)', () => {
|
||||||
|
expect(store.isLoading).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the loading state (false)', () => {
|
||||||
|
store.setLoading(false);
|
||||||
|
|
||||||
|
expect(store.isLoading).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setNextUrl', () => {
|
||||||
|
it('sets the url', () => {
|
||||||
|
store.setNextUrl(TEST_URL1);
|
||||||
|
|
||||||
|
expect(store.nextUrl).to.equal(TEST_URL1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setToken', () => {
|
||||||
|
it('sets the token', () => {
|
||||||
|
store.setToken(TEST_TOKEN);
|
||||||
|
|
||||||
|
expect(store.token).to.equal(TEST_TOKEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@computed', () => {
|
||||||
|
describe('encodedUrl', () => {
|
||||||
|
describe('encodedPath', () => {
|
||||||
|
it('encodes current', () => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
expect(store.encodedPath).to.match(
|
||||||
|
/http:\/\/home\.web3\.site:8080\/web\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\?t=[0-9]*$/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes current', () => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
expect(store.encodedUrl).to.match(
|
||||||
|
/^http:\/\/DSTPRV1BD1T78W1T5WQQ6VVDCMQ78SBKEGQ68VVDC5MPWBK3DXPG\.web\.web3\.site:8080\?t=[0-9]*$/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('frameId', () => {
|
||||||
|
it('creates an id', () => {
|
||||||
|
expect(store.frameId).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isPristine', () => {
|
||||||
|
it('is true when current === next', () => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
store.setNextUrl(TEST_URL1);
|
||||||
|
|
||||||
|
expect(store.isPristine).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is false when current !== next', () => {
|
||||||
|
store.setCurrentUrl(TEST_URL1);
|
||||||
|
store.setNextUrl(TEST_URL2);
|
||||||
|
|
||||||
|
expect(store.isPristine).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('operations', () => {
|
||||||
|
describe('generateToken', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
return store.generateToken();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls signer_generateWebProxyAccessToken', () => {
|
||||||
|
expect(api.signer.generateWebProxyAccessToken).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the token as retrieved', () => {
|
||||||
|
expect(store.token).to.equal(TEST_TOKEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadHistory', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
return store.loadHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity_listRecentDapps', () => {
|
||||||
|
expect(api.parity.listRecentDapps).to.have.been.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the history as retrieved', () => {
|
||||||
|
expect(store.history.peek()).to.deep.equal(TEST_HISTORY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -14,19 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import store from 'store';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { parse as parseUrl, format as formatUrl } from 'url';
|
|
||||||
import { parse as parseQuery } from 'querystring';
|
|
||||||
|
|
||||||
import AddressBar from './AddressBar';
|
import AddressBar from './AddressBar';
|
||||||
|
import Store from './store';
|
||||||
|
|
||||||
import styles from './web.css';
|
import styles from './web.css';
|
||||||
|
|
||||||
const LS_LAST_ADDRESS = '_parity::webLastAddress';
|
@observer
|
||||||
|
|
||||||
const hasProtocol = /^https?:\/\//;
|
|
||||||
|
|
||||||
export default class Web extends Component {
|
export default class Web extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
@ -36,120 +33,62 @@ export default class Web extends Component {
|
|||||||
params: PropTypes.object.isRequired
|
params: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
store = Store.get(this.context.api);
|
||||||
displayedUrl: null,
|
|
||||||
isLoading: true,
|
|
||||||
token: null,
|
|
||||||
url: null
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { api } = this.context;
|
this.store.gotoUrl(this.props.params.url);
|
||||||
const { params } = this.props;
|
return this.store.generateToken();
|
||||||
|
|
||||||
api
|
|
||||||
.signer
|
|
||||||
.generateWebProxyAccessToken()
|
|
||||||
.then((token) => {
|
|
||||||
this.setState({ token });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setUrl(params.url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (props) {
|
componentWillReceiveProps (props) {
|
||||||
this.setUrl(props.params.url);
|
this.store.gotoUrl(props.params.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
setUrl = (url) => {
|
|
||||||
url = url || store.get(LS_LAST_ADDRESS) || 'https://mkr.market';
|
|
||||||
if (!hasProtocol.test(url)) {
|
|
||||||
url = `https://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ url, displayedUrl: url });
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { displayedUrl, isLoading, token } = this.state;
|
const { currentUrl, token } = this.store;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.wrapper }>
|
<div className={ styles.wrapper }>
|
||||||
<h1 className={ styles.loading }>
|
<h1 className={ styles.loading }>
|
||||||
Requesting access token...
|
<FormattedMessage
|
||||||
|
id='web.requestToken'
|
||||||
|
defaultMessage='Requesting access token...'
|
||||||
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { dappsUrl } = this.context.api;
|
return currentUrl
|
||||||
const { url } = this.state;
|
? this.renderFrame()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!url || !token) {
|
renderFrame () {
|
||||||
return null;
|
const { encodedPath, frameId } = this.store;
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = parseUrl(url);
|
|
||||||
const { protocol, host, path } = parsed;
|
|
||||||
const address = `${dappsUrl}/web/${token}/${protocol.slice(0, -1)}/${host}${path}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.wrapper }>
|
<div className={ styles.wrapper }>
|
||||||
<AddressBar
|
<AddressBar
|
||||||
className={ styles.url }
|
className={ styles.url }
|
||||||
isLoading={ isLoading }
|
store={ this.store }
|
||||||
onChange={ this.onUrlChange }
|
|
||||||
onRefresh={ this.onRefresh }
|
|
||||||
url={ displayedUrl }
|
|
||||||
/>
|
/>
|
||||||
<iframe
|
<iframe
|
||||||
className={ styles.frame }
|
className={ styles.frame }
|
||||||
frameBorder={ 0 }
|
frameBorder={ 0 }
|
||||||
name={ name }
|
id={ frameId }
|
||||||
|
name={ frameId }
|
||||||
onLoad={ this.iframeOnLoad }
|
onLoad={ this.iframeOnLoad }
|
||||||
sandbox='allow-forms allow-same-origin allow-scripts'
|
sandbox='allow-forms allow-same-origin allow-scripts'
|
||||||
scrolling='auto'
|
scrolling='auto'
|
||||||
src={ address }
|
src={ encodedPath }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUrlChange = (url) => {
|
|
||||||
if (!hasProtocol.test(url)) {
|
|
||||||
url = `https://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.set(LS_LAST_ADDRESS, url);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isLoading: true,
|
|
||||||
displayedUrl: url,
|
|
||||||
url: url
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onRefresh = () => {
|
|
||||||
const { displayedUrl } = this.state;
|
|
||||||
|
|
||||||
// Insert timestamp
|
|
||||||
// This is a hack to prevent caching.
|
|
||||||
const parsed = parseUrl(displayedUrl);
|
|
||||||
|
|
||||||
parsed.query = parseQuery(parsed.query);
|
|
||||||
parsed.query.t = Date.now().toString();
|
|
||||||
delete parsed.search;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isLoading: true,
|
|
||||||
url: formatUrl(parsed)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
iframeOnLoad = () => {
|
iframeOnLoad = () => {
|
||||||
this.setState({
|
this.store.setLoading(false);
|
||||||
isLoading: false
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
56
js/src/views/Web/web.spec.js
Normal file
56
js/src/views/Web/web.spec.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Web from './';
|
||||||
|
|
||||||
|
const TEST_URL = 'https://mkr.market';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let component;
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
api = {};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (url = TEST_URL) {
|
||||||
|
component = shallow(
|
||||||
|
<Web params={ { url } } />,
|
||||||
|
{
|
||||||
|
context: { api: createApi() }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('views/Web', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders loading with no token', () => {
|
||||||
|
expect(component.find('FormattedMessage').props().id).to.equal('web.requestToken');
|
||||||
|
});
|
||||||
|
});
|
@ -242,7 +242,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let informant = Arc::new(Informant::new(client.clone(), None, None, None, cmd.with_color));
|
let informant = Arc::new(Informant::new(client.clone(), None, None, None, None, cmd.with_color));
|
||||||
service.register_io_handler(informant).map_err(|_| "Unable to register informant handler".to_owned())?;
|
service.register_io_handler(informant).map_err(|_| "Unable to register informant handler".to_owned())?;
|
||||||
|
|
||||||
let do_import = |bytes| {
|
let do_import = |bytes| {
|
||||||
|
@ -19,6 +19,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use dir::default_data_path;
|
use dir::default_data_path;
|
||||||
use ethcore::client::Client;
|
use ethcore::client::Client;
|
||||||
|
use ethcore_rpc::informant::RpcStats;
|
||||||
use ethsync::SyncProvider;
|
use ethsync::SyncProvider;
|
||||||
use hash_fetch::fetch::Client as FetchClient;
|
use hash_fetch::fetch::Client as FetchClient;
|
||||||
use helpers::replace_home;
|
use helpers::replace_home;
|
||||||
@ -64,6 +65,7 @@ pub struct Dependencies {
|
|||||||
pub remote: Remote,
|
pub remote: Remote,
|
||||||
pub fetch: FetchClient,
|
pub fetch: FetchClient,
|
||||||
pub signer: Arc<SignerService>,
|
pub signer: Arc<SignerService>,
|
||||||
|
pub stats: Arc<RpcStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> {
|
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<WebappServer>, String> {
|
||||||
@ -174,7 +176,7 @@ mod server {
|
|||||||
} else {
|
} else {
|
||||||
rpc_apis::ApiSet::UnsafeContext
|
rpc_apis::ApiSet::UnsafeContext
|
||||||
};
|
};
|
||||||
let apis = rpc_apis::setup_rpc(Default::default(), deps.apis.clone(), api_set);
|
let apis = rpc_apis::setup_rpc(deps.stats, deps.apis.clone(), api_set);
|
||||||
let handler = RpcHandler::new(Arc::new(apis), deps.remote);
|
let handler = RpcHandler::new(Arc::new(apis), deps.remote);
|
||||||
let start_result = match auth {
|
let start_result = match auth {
|
||||||
None => {
|
None => {
|
||||||
|
@ -30,7 +30,8 @@ use ethcore::service::ClientIoMessage;
|
|||||||
use ethcore::snapshot::service::Service as SnapshotService;
|
use ethcore::snapshot::service::Service as SnapshotService;
|
||||||
use ethcore::snapshot::{RestorationStatus, SnapshotService as SS};
|
use ethcore::snapshot::{RestorationStatus, SnapshotService as SS};
|
||||||
use number_prefix::{binary_prefix, Standalone, Prefixed};
|
use number_prefix::{binary_prefix, Standalone, Prefixed};
|
||||||
use ethcore_rpc::is_major_importing;
|
use ethcore_rpc::{is_major_importing};
|
||||||
|
use ethcore_rpc::informant::RpcStats;
|
||||||
use rlp::View;
|
use rlp::View;
|
||||||
|
|
||||||
pub struct Informant {
|
pub struct Informant {
|
||||||
@ -41,6 +42,7 @@ pub struct Informant {
|
|||||||
snapshot: Option<Arc<SnapshotService>>,
|
snapshot: Option<Arc<SnapshotService>>,
|
||||||
sync: Option<Arc<SyncProvider>>,
|
sync: Option<Arc<SyncProvider>>,
|
||||||
net: Option<Arc<ManageNetwork>>,
|
net: Option<Arc<ManageNetwork>>,
|
||||||
|
rpc_stats: Option<Arc<RpcStats>>,
|
||||||
last_import: Mutex<Instant>,
|
last_import: Mutex<Instant>,
|
||||||
skipped: AtomicUsize,
|
skipped: AtomicUsize,
|
||||||
skipped_txs: AtomicUsize,
|
skipped_txs: AtomicUsize,
|
||||||
@ -63,13 +65,20 @@ pub trait MillisecondDuration {
|
|||||||
|
|
||||||
impl MillisecondDuration for Duration {
|
impl MillisecondDuration for Duration {
|
||||||
fn as_milliseconds(&self) -> u64 {
|
fn as_milliseconds(&self) -> u64 {
|
||||||
self.as_secs() * 1000 + self.subsec_nanos() as u64 / 1000000
|
self.as_secs() * 1000 + self.subsec_nanos() as u64 / 1_000_000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Informant {
|
impl Informant {
|
||||||
/// Make a new instance potentially `with_color` output.
|
/// Make a new instance potentially `with_color` output.
|
||||||
pub fn new(client: Arc<Client>, sync: Option<Arc<SyncProvider>>, net: Option<Arc<ManageNetwork>>, snapshot: Option<Arc<SnapshotService>>, with_color: bool) -> Self {
|
pub fn new(
|
||||||
|
client: Arc<Client>,
|
||||||
|
sync: Option<Arc<SyncProvider>>,
|
||||||
|
net: Option<Arc<ManageNetwork>>,
|
||||||
|
snapshot: Option<Arc<SnapshotService>>,
|
||||||
|
rpc_stats: Option<Arc<RpcStats>>,
|
||||||
|
with_color: bool,
|
||||||
|
) -> Self {
|
||||||
Informant {
|
Informant {
|
||||||
report: RwLock::new(None),
|
report: RwLock::new(None),
|
||||||
last_tick: RwLock::new(Instant::now()),
|
last_tick: RwLock::new(Instant::now()),
|
||||||
@ -78,6 +87,7 @@ impl Informant {
|
|||||||
snapshot: snapshot,
|
snapshot: snapshot,
|
||||||
sync: sync,
|
sync: sync,
|
||||||
net: net,
|
net: net,
|
||||||
|
rpc_stats: rpc_stats,
|
||||||
last_import: Mutex::new(Instant::now()),
|
last_import: Mutex::new(Instant::now()),
|
||||||
skipped: AtomicUsize::new(0),
|
skipped: AtomicUsize::new(0),
|
||||||
skipped_txs: AtomicUsize::new(0),
|
skipped_txs: AtomicUsize::new(0),
|
||||||
@ -102,6 +112,7 @@ impl Informant {
|
|||||||
let cache_info = self.client.blockchain_cache_info();
|
let cache_info = self.client.blockchain_cache_info();
|
||||||
let network_config = self.net.as_ref().map(|n| n.network_config());
|
let network_config = self.net.as_ref().map(|n| n.network_config());
|
||||||
let sync_status = self.sync.as_ref().map(|s| s.status());
|
let sync_status = self.sync.as_ref().map(|s| s.status());
|
||||||
|
let rpc_stats = self.rpc_stats.as_ref();
|
||||||
|
|
||||||
let importing = is_major_importing(sync_status.map(|s| s.state), self.client.queue_info());
|
let importing = is_major_importing(sync_status.map(|s| s.state), self.client.queue_info());
|
||||||
let (snapshot_sync, snapshot_current, snapshot_total) = self.snapshot.as_ref().map_or((false, 0, 0), |s|
|
let (snapshot_sync, snapshot_current, snapshot_total) = self.snapshot.as_ref().map_or((false, 0, 0), |s|
|
||||||
@ -126,10 +137,10 @@ impl Informant {
|
|||||||
false => t,
|
false => t,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(target: "import", "{} {} {}",
|
info!(target: "import", "{} {} {} {}",
|
||||||
match importing {
|
match importing {
|
||||||
true => match snapshot_sync {
|
true => match snapshot_sync {
|
||||||
false => format!("Syncing {} {} {} {}+{} Qed",
|
false => format!("Syncing {} {} {} {}+{} Qed",
|
||||||
paint(White.bold(), format!("{:>8}", format!("#{}", chain_info.best_block_number))),
|
paint(White.bold(), format!("{:>8}", format!("#{}", chain_info.best_block_number))),
|
||||||
paint(White.bold(), format!("{}", chain_info.best_block_hash)),
|
paint(White.bold(), format!("{}", chain_info.best_block_hash)),
|
||||||
{
|
{
|
||||||
@ -170,7 +181,16 @@ impl Informant {
|
|||||||
Some(ref sync_info) => format!(" {} sync", paint(Blue.bold(), format!("{:>8}", format_bytes(sync_info.mem_used)))),
|
Some(ref sync_info) => format!(" {} sync", paint(Blue.bold(), format!("{:>8}", format_bytes(sync_info.mem_used)))),
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
|
match rpc_stats {
|
||||||
|
Some(ref rpc_stats) => format!(
|
||||||
|
"RPC: {} conn, {} req/s, {} µs",
|
||||||
|
paint(Blue.bold(), format!("{:2}", rpc_stats.sessions())),
|
||||||
|
paint(Blue.bold(), format!("{:2}", rpc_stats.requests_rate())),
|
||||||
|
paint(Blue.bold(), format!("{:3}", rpc_stats.approximated_roundtrip())),
|
||||||
|
),
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
*write_report = Some(report);
|
*write_report = Some(report);
|
||||||
|
@ -22,6 +22,7 @@ use io::PanicHandler;
|
|||||||
|
|
||||||
use dir::default_data_path;
|
use dir::default_data_path;
|
||||||
use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata};
|
use ethcore_rpc::{self as rpc, RpcServerError, IpcServerError, Metadata};
|
||||||
|
use ethcore_rpc::informant::{RpcStats, Middleware};
|
||||||
use helpers::parity_ipc_path;
|
use helpers::parity_ipc_path;
|
||||||
use jsonrpc_core::MetaIoHandler;
|
use jsonrpc_core::MetaIoHandler;
|
||||||
use jsonrpc_core::reactor::{RpcHandler, Remote};
|
use jsonrpc_core::reactor::{RpcHandler, Remote};
|
||||||
@ -85,6 +86,7 @@ pub struct Dependencies {
|
|||||||
pub panic_handler: Arc<PanicHandler>,
|
pub panic_handler: Arc<PanicHandler>,
|
||||||
pub apis: Arc<rpc_apis::Dependencies>,
|
pub apis: Arc<rpc_apis::Dependencies>,
|
||||||
pub remote: Remote,
|
pub remote: Remote,
|
||||||
|
pub stats: Arc<RpcStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result<Option<HttpServer>, String> {
|
pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result<Option<HttpServer>, String> {
|
||||||
@ -97,8 +99,8 @@ pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Result<Option<H
|
|||||||
Ok(Some(setup_http_rpc_server(deps, &addr, conf.cors, conf.hosts, conf.apis)?))
|
Ok(Some(setup_http_rpc_server(deps, &addr, conf.cors, conf.hosts, conf.apis)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler<Metadata> {
|
fn setup_apis(apis: ApiSet, deps: &Dependencies) -> MetaIoHandler<Metadata, Middleware> {
|
||||||
rpc_apis::setup_rpc(MetaIoHandler::default(), deps.apis.clone(), apis)
|
rpc_apis::setup_rpc(deps.stats.clone(), deps.apis.clone(), apis)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_http_rpc_server(
|
pub fn setup_http_rpc_server(
|
||||||
@ -122,12 +124,12 @@ pub fn setup_http_rpc_server(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Result<Option<IpcServer<Metadata>>, String> {
|
pub fn new_ipc(conf: IpcConfiguration, deps: &Dependencies) -> Result<Option<IpcServer<Metadata, Middleware>>, String> {
|
||||||
if !conf.enabled { return Ok(None); }
|
if !conf.enabled { return Ok(None); }
|
||||||
Ok(Some(setup_ipc_rpc_server(deps, &conf.socket_addr, conf.apis)?))
|
Ok(Some(setup_ipc_rpc_server(deps, &conf.socket_addr, conf.apis)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: ApiSet) -> Result<IpcServer<Metadata>, String> {
|
pub fn setup_ipc_rpc_server(dependencies: &Dependencies, addr: &str, apis: ApiSet) -> Result<IpcServer<Metadata, Middleware>, String> {
|
||||||
let apis = setup_apis(apis, dependencies);
|
let apis = setup_apis(apis, dependencies);
|
||||||
let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone());
|
let handler = RpcHandler::new(Arc::new(apis), dependencies.remote.clone());
|
||||||
match rpc::start_ipc(addr, handler) {
|
match rpc::start_ipc(addr, handler) {
|
||||||
|
@ -14,22 +14,25 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::cmp::PartialEq;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::cmp::PartialEq;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::RotatingLogger;
|
|
||||||
use jsonrpc_core::{MetaIoHandler};
|
|
||||||
use ethcore::miner::{Miner, ExternalMiner};
|
|
||||||
use ethcore::client::Client;
|
|
||||||
use ethcore::account_provider::AccountProvider;
|
|
||||||
use ethcore::snapshot::SnapshotService;
|
|
||||||
use ethsync::{ManageNetwork, SyncProvider};
|
|
||||||
use ethcore_rpc::{Metadata, NetworkSettings};
|
|
||||||
pub use ethcore_rpc::SignerService;
|
pub use ethcore_rpc::SignerService;
|
||||||
use updater::Updater;
|
|
||||||
|
use ethcore::account_provider::AccountProvider;
|
||||||
|
use ethcore::client::Client;
|
||||||
|
use ethcore::miner::{Miner, ExternalMiner};
|
||||||
|
use ethcore::snapshot::SnapshotService;
|
||||||
|
use ethcore_rpc::{Metadata, NetworkSettings};
|
||||||
|
use ethcore_rpc::informant::{Middleware, RpcStats, ClientNotifier};
|
||||||
|
use ethsync::{ManageNetwork, SyncProvider};
|
||||||
use hash_fetch::fetch::Client as FetchClient;
|
use hash_fetch::fetch::Client as FetchClient;
|
||||||
|
use jsonrpc_core::{MetaIoHandler};
|
||||||
|
use updater::Updater;
|
||||||
|
use util::RotatingLogger;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||||
pub enum Api {
|
pub enum Api {
|
||||||
@ -182,9 +185,13 @@ macro_rules! add_signing_methods {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_rpc(mut handler: MetaIoHandler<Metadata>, deps: Arc<Dependencies>, apis: ApiSet) -> MetaIoHandler<Metadata> {
|
pub fn setup_rpc(stats: Arc<RpcStats>, deps: Arc<Dependencies>, apis: ApiSet) -> MetaIoHandler<Metadata, Middleware> {
|
||||||
use ethcore_rpc::v1::*;
|
use ethcore_rpc::v1::*;
|
||||||
|
|
||||||
|
let mut handler = MetaIoHandler::with_middleware(Middleware::new(stats, ClientNotifier {
|
||||||
|
client: deps.client.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
// it's turned into vector, cause ont of the cases requires &[]
|
// it's turned into vector, cause ont of the cases requires &[]
|
||||||
let apis = apis.list_apis().into_iter().collect::<Vec<_>>();
|
let apis = apis.list_apis().into_iter().collect::<Vec<_>>();
|
||||||
for api in &apis {
|
for api in &apis {
|
||||||
@ -244,7 +251,7 @@ pub fn setup_rpc(mut handler: MetaIoHandler<Metadata>, deps: Arc<Dependencies>,
|
|||||||
add_signing_methods!(ParitySigning, handler, deps);
|
add_signing_methods!(ParitySigning, handler, deps);
|
||||||
},
|
},
|
||||||
Api::ParityAccounts => {
|
Api::ParityAccounts => {
|
||||||
handler.extend_with(ParityAccountsClient::new(&deps.secret_store, &deps.client).to_delegate());
|
handler.extend_with(ParityAccountsClient::new(&deps.secret_store).to_delegate());
|
||||||
},
|
},
|
||||||
Api::ParitySet => {
|
Api::ParitySet => {
|
||||||
handler.extend_with(ParitySetClient::new(
|
handler.extend_with(ParitySetClient::new(
|
||||||
|
@ -18,7 +18,7 @@ use std::sync::Arc;
|
|||||||
use std::net::{TcpListener};
|
use std::net::{TcpListener};
|
||||||
use ctrlc::CtrlC;
|
use ctrlc::CtrlC;
|
||||||
use fdlimit::raise_fd_limit;
|
use fdlimit::raise_fd_limit;
|
||||||
use ethcore_rpc::{NetworkSettings, is_major_importing};
|
use ethcore_rpc::{NetworkSettings, informant, is_major_importing};
|
||||||
use ethsync::NetworkConfiguration;
|
use ethsync::NetworkConfiguration;
|
||||||
use util::{Colour, version, RotatingLogger, Mutex, Condvar};
|
use util::{Colour, version, RotatingLogger, Mutex, Condvar};
|
||||||
use io::{MayPanic, ForwardPanic, PanicHandler};
|
use io::{MayPanic, ForwardPanic, PanicHandler};
|
||||||
@ -358,6 +358,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
service.add_notify(updater.clone());
|
service.add_notify(updater.clone());
|
||||||
|
|
||||||
// set up dependencies for rpc servers
|
// set up dependencies for rpc servers
|
||||||
|
let rpc_stats = Arc::new(informant::RpcStats::default());
|
||||||
let signer_path = cmd.signer_conf.signer_path.clone();
|
let signer_path = cmd.signer_conf.signer_path.clone();
|
||||||
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
|
let deps_for_rpc_apis = Arc::new(rpc_apis::Dependencies {
|
||||||
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
|
signer_service: Arc::new(rpc_apis::SignerService::new(move || {
|
||||||
@ -390,6 +391,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
panic_handler: panic_handler.clone(),
|
panic_handler: panic_handler.clone(),
|
||||||
apis: deps_for_rpc_apis.clone(),
|
apis: deps_for_rpc_apis.clone(),
|
||||||
remote: event_loop.raw_remote(),
|
remote: event_loop.raw_remote(),
|
||||||
|
stats: rpc_stats.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// start rpc servers
|
// start rpc servers
|
||||||
@ -405,6 +407,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
remote: event_loop.raw_remote(),
|
remote: event_loop.raw_remote(),
|
||||||
fetch: fetch.clone(),
|
fetch: fetch.clone(),
|
||||||
signer: deps_for_rpc_apis.signer_service.clone(),
|
signer: deps_for_rpc_apis.signer_service.clone(),
|
||||||
|
stats: rpc_stats.clone(),
|
||||||
};
|
};
|
||||||
let dapps_server = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?;
|
let dapps_server = dapps::new(cmd.dapps_conf.clone(), dapps_deps)?;
|
||||||
|
|
||||||
@ -413,6 +416,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
panic_handler: panic_handler.clone(),
|
panic_handler: panic_handler.clone(),
|
||||||
apis: deps_for_rpc_apis.clone(),
|
apis: deps_for_rpc_apis.clone(),
|
||||||
remote: event_loop.raw_remote(),
|
remote: event_loop.raw_remote(),
|
||||||
|
rpc_stats: rpc_stats.clone(),
|
||||||
};
|
};
|
||||||
let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?;
|
let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?;
|
||||||
|
|
||||||
@ -422,7 +426,8 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
|
|||||||
Some(sync_provider.clone()),
|
Some(sync_provider.clone()),
|
||||||
Some(manage_network.clone()),
|
Some(manage_network.clone()),
|
||||||
Some(snapshot_service.clone()),
|
Some(snapshot_service.clone()),
|
||||||
cmd.logger_config.color
|
Some(rpc_stats.clone()),
|
||||||
|
cmd.logger_config.color,
|
||||||
));
|
));
|
||||||
service.add_notify(informant.clone());
|
service.add_notify(informant.clone());
|
||||||
service.register_io_handler(informant.clone()).map_err(|_| "Unable to register informant handler".to_owned())?;
|
service.register_io_handler(informant.clone()).map_err(|_| "Unable to register informant handler".to_owned())?;
|
||||||
|
@ -15,18 +15,21 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ansi_term::Colour;
|
use std::sync::Arc;
|
||||||
use io::{ForwardPanic, PanicHandler};
|
|
||||||
use util::path::restrict_permissions_owner;
|
|
||||||
use rpc_apis;
|
|
||||||
use ethcore_signer as signer;
|
|
||||||
use dir::default_data_path;
|
|
||||||
use helpers::replace_home;
|
|
||||||
use jsonrpc_core::reactor::{RpcHandler, Remote};
|
|
||||||
pub use ethcore_signer::Server as SignerServer;
|
pub use ethcore_signer::Server as SignerServer;
|
||||||
|
|
||||||
|
use ansi_term::Colour;
|
||||||
|
use dir::default_data_path;
|
||||||
|
use ethcore_rpc::informant::RpcStats;
|
||||||
|
use ethcore_signer as signer;
|
||||||
|
use helpers::replace_home;
|
||||||
|
use io::{ForwardPanic, PanicHandler};
|
||||||
|
use jsonrpc_core::reactor::{RpcHandler, Remote};
|
||||||
|
use rpc_apis;
|
||||||
|
use util::path::restrict_permissions_owner;
|
||||||
|
|
||||||
const CODES_FILENAME: &'static str = "authcodes";
|
const CODES_FILENAME: &'static str = "authcodes";
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@ -55,6 +58,7 @@ pub struct Dependencies {
|
|||||||
pub panic_handler: Arc<PanicHandler>,
|
pub panic_handler: Arc<PanicHandler>,
|
||||||
pub apis: Arc<rpc_apis::Dependencies>,
|
pub apis: Arc<rpc_apis::Dependencies>,
|
||||||
pub remote: Remote,
|
pub remote: Remote,
|
||||||
|
pub rpc_stats: Arc<RpcStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewToken {
|
pub struct NewToken {
|
||||||
@ -126,7 +130,8 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result<SignerServer, Str
|
|||||||
info!("If you do not intend this, exit now.");
|
info!("If you do not intend this, exit now.");
|
||||||
}
|
}
|
||||||
let server = server.skip_origin_validation(conf.skip_origin_validation);
|
let server = server.skip_origin_validation(conf.skip_origin_validation);
|
||||||
let apis = rpc_apis::setup_rpc(Default::default(), deps.apis, rpc_apis::ApiSet::SafeContext);
|
let server = server.stats(deps.rpc_stats.clone());
|
||||||
|
let apis = rpc_apis::setup_rpc(deps.rpc_stats, deps.apis, rpc_apis::ApiSet::SafeContext);
|
||||||
let handler = RpcHandler::new(Arc::new(apis), deps.remote);
|
let handler = RpcHandler::new(Arc::new(apis), deps.remote);
|
||||||
server.start(addr, handler)
|
server.start(addr, handler)
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ serde_json = "0.8"
|
|||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
transient-hashmap = "0.1"
|
transient-hashmap = "0.1"
|
||||||
|
order-stat = "0.1"
|
||||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
|
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
|
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||||
jsonrpc-ipc-server = { git = "https://github.com/ethcore/jsonrpc.git" }
|
jsonrpc-ipc-server = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||||
|
@ -41,6 +41,7 @@ extern crate time;
|
|||||||
extern crate rlp;
|
extern crate rlp;
|
||||||
extern crate fetch;
|
extern crate fetch;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
extern crate order_stat;
|
||||||
extern crate parity_updater as updater;
|
extern crate parity_updater as updater;
|
||||||
extern crate parity_reactor;
|
extern crate parity_reactor;
|
||||||
|
|
||||||
@ -64,16 +65,16 @@ use jsonrpc_core::reactor::RpcHandler;
|
|||||||
pub use ipc::{Server as IpcServer, Error as IpcServerError};
|
pub use ipc::{Server as IpcServer, Error as IpcServerError};
|
||||||
pub use jsonrpc_http_server::{ServerBuilder, Server, RpcServerError};
|
pub use jsonrpc_http_server::{ServerBuilder, Server, RpcServerError};
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin};
|
pub use v1::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, Metadata, Origin, informant};
|
||||||
pub use v1::block_import::is_major_importing;
|
pub use v1::block_import::is_major_importing;
|
||||||
|
|
||||||
/// Start http server asynchronously and returns result with `Server` handle on success or an error.
|
/// Start http server asynchronously and returns result with `Server` handle on success or an error.
|
||||||
pub fn start_http<M: jsonrpc_core::Metadata>(
|
pub fn start_http<M: jsonrpc_core::Metadata, S: jsonrpc_core::Middleware<M>>(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
cors_domains: Option<Vec<String>>,
|
cors_domains: Option<Vec<String>>,
|
||||||
allowed_hosts: Option<Vec<String>>,
|
allowed_hosts: Option<Vec<String>>,
|
||||||
panic_handler: Arc<PanicHandler>,
|
panic_handler: Arc<PanicHandler>,
|
||||||
handler: RpcHandler<M>,
|
handler: RpcHandler<M, S>,
|
||||||
) -> Result<Server, RpcServerError> {
|
) -> Result<Server, RpcServerError> {
|
||||||
|
|
||||||
let cors_domains = cors_domains.map(|domains| {
|
let cors_domains = cors_domains.map(|domains| {
|
||||||
@ -96,7 +97,10 @@ pub fn start_http<M: jsonrpc_core::Metadata>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start ipc server asynchronously and returns result with `Server` handle on success or an error.
|
/// Start ipc server asynchronously and returns result with `Server` handle on success or an error.
|
||||||
pub fn start_ipc<M: jsonrpc_core::Metadata>(addr: &str, handler: RpcHandler<M>) -> Result<ipc::Server<M>, ipc::Error> {
|
pub fn start_ipc<M: jsonrpc_core::Metadata, S: jsonrpc_core::Middleware<M>>(
|
||||||
|
addr: &str,
|
||||||
|
handler: RpcHandler<M, S>,
|
||||||
|
) -> Result<ipc::Server<M, S>, ipc::Error> {
|
||||||
let server = ipc::Server::with_rpc_handler(addr, handler)?;
|
let server = ipc::Server::with_rpc_handler(addr, handler)?;
|
||||||
server.run_async()?;
|
server.run_async()?;
|
||||||
Ok(server)
|
Ok(server)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use rlp;
|
use rlp;
|
||||||
use util::{Address, H256, U256, Uint, Bytes};
|
use util::{Address, H520, H256, U256, Uint, Bytes};
|
||||||
use util::bytes::ToPretty;
|
use util::bytes::ToPretty;
|
||||||
use util::sha3::Hashable;
|
use util::sha3::Hashable;
|
||||||
|
|
||||||
@ -112,6 +112,14 @@ pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload:
|
|||||||
ConfirmationPayload::Signature(address, data) => {
|
ConfirmationPayload::Signature(address, data) => {
|
||||||
signature(accounts, address, data.sha3(), pass)
|
signature(accounts, address, data.sha3(), pass)
|
||||||
.map(|result| result
|
.map(|result| result
|
||||||
|
.map(|rsv| {
|
||||||
|
let mut vrs = [0u8; 65];
|
||||||
|
let rsv = rsv.as_ref();
|
||||||
|
vrs[0] = rsv[64] + 27;
|
||||||
|
vrs[1..33].copy_from_slice(&rsv[0..32]);
|
||||||
|
vrs[33..65].copy_from_slice(&rsv[32..64]);
|
||||||
|
H520(vrs)
|
||||||
|
})
|
||||||
.map(RpcH520::from)
|
.map(RpcH520::from)
|
||||||
.map(ConfirmationResponse::Signature)
|
.map(ConfirmationResponse::Signature)
|
||||||
)
|
)
|
||||||
|
301
rpc/src/v1/helpers/informant.rs
Normal file
301
rpc/src/v1/helpers/informant.rs
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! RPC Requests Statistics
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{self, AtomicUsize};
|
||||||
|
use std::time;
|
||||||
|
use futures::Future;
|
||||||
|
use jsonrpc_core as rpc;
|
||||||
|
use order_stat;
|
||||||
|
use util::RwLock;
|
||||||
|
|
||||||
|
const RATE_SECONDS: usize = 10;
|
||||||
|
const STATS_SAMPLES: usize = 60;
|
||||||
|
|
||||||
|
struct RateCalculator {
|
||||||
|
era: time::Instant,
|
||||||
|
samples: [u16; RATE_SECONDS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RateCalculator {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "{} req/s", self.rate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RateCalculator {
|
||||||
|
fn default() -> Self {
|
||||||
|
RateCalculator {
|
||||||
|
era: time::Instant::now(),
|
||||||
|
samples: [0; RATE_SECONDS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateCalculator {
|
||||||
|
fn elapsed(&self) -> u64 {
|
||||||
|
self.era.elapsed().as_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) -> u16 {
|
||||||
|
if self.elapsed() >= RATE_SECONDS as u64 {
|
||||||
|
self.era = time::Instant::now();
|
||||||
|
self.samples[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = self.elapsed() as usize % RATE_SECONDS;
|
||||||
|
let next = (pos + 1) % RATE_SECONDS;
|
||||||
|
self.samples[next] = 0;
|
||||||
|
self.samples[pos] = self.samples[pos].saturating_add(1);
|
||||||
|
self.samples[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_rate(&self) -> usize {
|
||||||
|
let now = match self.elapsed() {
|
||||||
|
i if i >= RATE_SECONDS as u64 => RATE_SECONDS,
|
||||||
|
i => i as usize + 1,
|
||||||
|
};
|
||||||
|
let sum: usize = self.samples[0..now].iter().map(|x| *x as usize).sum();
|
||||||
|
sum / now
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rate(&self) -> usize {
|
||||||
|
if self.elapsed() > RATE_SECONDS as u64 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.current_rate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatsCalculator<T = u32> {
|
||||||
|
filled: bool,
|
||||||
|
idx: usize,
|
||||||
|
samples: [T; STATS_SAMPLES],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Copy> Default for StatsCalculator<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
StatsCalculator {
|
||||||
|
filled: false,
|
||||||
|
idx: 0,
|
||||||
|
samples: [T::default(); STATS_SAMPLES],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: fmt::Display + Default + Copy + Ord> fmt::Debug for StatsCalculator<T> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "median: {} ms", self.approximated_median())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Copy + Ord> StatsCalculator<T> {
|
||||||
|
pub fn add(&mut self, sample: T) {
|
||||||
|
self.idx += 1;
|
||||||
|
if self.idx >= STATS_SAMPLES {
|
||||||
|
self.filled = true;
|
||||||
|
self.idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.samples[self.idx] = sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns aproximate of media
|
||||||
|
pub fn approximated_median(&self) -> T {
|
||||||
|
let mut copy = [T::default(); STATS_SAMPLES];
|
||||||
|
copy.copy_from_slice(&self.samples);
|
||||||
|
let bound = if self.filled { STATS_SAMPLES } else { self.idx + 1 };
|
||||||
|
|
||||||
|
let (_, &mut median) = order_stat::median_of_medians(&mut copy[0..bound]);
|
||||||
|
median
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RPC Statistics
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct RpcStats {
|
||||||
|
requests: RwLock<RateCalculator>,
|
||||||
|
roundtrips: RwLock<StatsCalculator<u32>>,
|
||||||
|
active_sessions: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcStats {
|
||||||
|
/// Count session opened
|
||||||
|
pub fn open_session(&self) {
|
||||||
|
self.active_sessions.fetch_add(1, atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count session closed.
|
||||||
|
/// Silently overflows if closing unopened session.
|
||||||
|
pub fn close_session(&self) {
|
||||||
|
self.active_sessions.fetch_sub(1, atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count request. Returns number of requests in current second.
|
||||||
|
pub fn count_request(&self) -> u16 {
|
||||||
|
self.requests.write().tick()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add roundtrip time (microseconds)
|
||||||
|
pub fn add_roundtrip(&self, microseconds: u32) {
|
||||||
|
self.roundtrips.write().add(microseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of open sessions
|
||||||
|
pub fn sessions(&self) -> usize {
|
||||||
|
self.active_sessions.load(atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns requests rate
|
||||||
|
pub fn requests_rate(&self) -> usize {
|
||||||
|
self.requests.read().rate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns approximated roundtrip in microseconds
|
||||||
|
pub fn approximated_roundtrip(&self) -> u32 {
|
||||||
|
self.roundtrips.read().approximated_median()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notifies about RPC activity.
|
||||||
|
pub trait ActivityNotifier: Send + Sync + 'static {
|
||||||
|
/// Activity on RPC interface
|
||||||
|
fn active(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stats-counting RPC middleware
|
||||||
|
pub struct Middleware<T: ActivityNotifier = ClientNotifier> {
|
||||||
|
stats: Arc<RpcStats>,
|
||||||
|
notifier: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ActivityNotifier> Middleware<T> {
|
||||||
|
/// Create new Middleware with stats counter and activity notifier.
|
||||||
|
pub fn new(stats: Arc<RpcStats>, notifier: T) -> Self {
|
||||||
|
Middleware {
|
||||||
|
stats: stats,
|
||||||
|
notifier: notifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_micro(dur: time::Duration) -> u32 {
|
||||||
|
(dur.as_secs() * 1_000_000) as u32 + dur.subsec_nanos() / 1_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: rpc::Metadata, T: ActivityNotifier> rpc::Middleware<M> for Middleware<T> {
|
||||||
|
fn on_request<F>(&self, request: rpc::Request, meta: M, process: F) -> rpc::FutureResponse where
|
||||||
|
F: FnOnce(rpc::Request, M) -> rpc::FutureResponse,
|
||||||
|
{
|
||||||
|
let start = time::Instant::now();
|
||||||
|
let response = process(request, meta);
|
||||||
|
|
||||||
|
self.notifier.active();
|
||||||
|
let stats = self.stats.clone();
|
||||||
|
stats.count_request();
|
||||||
|
response.map(move |res| {
|
||||||
|
stats.add_roundtrip(Self::as_micro(start.elapsed()));
|
||||||
|
res
|
||||||
|
}).boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client Notifier
|
||||||
|
pub struct ClientNotifier {
|
||||||
|
/// Client
|
||||||
|
pub client: Arc<::ethcore::client::Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivityNotifier for ClientNotifier {
|
||||||
|
fn active(&self) {
|
||||||
|
self.client.keep_alive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::{RateCalculator, StatsCalculator, RpcStats};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_calculate_rate() {
|
||||||
|
// given
|
||||||
|
let mut avg = RateCalculator::default();
|
||||||
|
|
||||||
|
// when
|
||||||
|
avg.tick();
|
||||||
|
avg.tick();
|
||||||
|
avg.tick();
|
||||||
|
let rate = avg.rate();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(rate, 3usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_approximate_median() {
|
||||||
|
// given
|
||||||
|
let mut stats = StatsCalculator::default();
|
||||||
|
stats.add(5);
|
||||||
|
stats.add(100);
|
||||||
|
stats.add(3);
|
||||||
|
stats.add(15);
|
||||||
|
stats.add(20);
|
||||||
|
stats.add(6);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let median = stats.approximated_median();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(median, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_count_rpc_stats() {
|
||||||
|
// given
|
||||||
|
let stats = RpcStats::default();
|
||||||
|
assert_eq!(stats.sessions(), 0);
|
||||||
|
assert_eq!(stats.requests_rate(), 0);
|
||||||
|
assert_eq!(stats.approximated_roundtrip(), 0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
stats.open_session();
|
||||||
|
stats.close_session();
|
||||||
|
stats.open_session();
|
||||||
|
stats.count_request();
|
||||||
|
stats.count_request();
|
||||||
|
stats.add_roundtrip(125);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(stats.sessions(), 1);
|
||||||
|
assert_eq!(stats.requests_rate(), 2);
|
||||||
|
assert_eq!(stats.approximated_roundtrip(), 125);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_be_sync_and_send() {
|
||||||
|
let stats = RpcStats::default();
|
||||||
|
is_sync(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_sync<F: Send + Sync>(x: F) {
|
||||||
|
drop(x)
|
||||||
|
}
|
||||||
|
}
|
@ -17,16 +17,18 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
|
||||||
pub mod dispatch;
|
|
||||||
pub mod block_import;
|
pub mod block_import;
|
||||||
|
pub mod dispatch;
|
||||||
|
pub mod informant;
|
||||||
|
|
||||||
|
mod network_settings;
|
||||||
mod poll_manager;
|
mod poll_manager;
|
||||||
mod poll_filter;
|
mod poll_filter;
|
||||||
mod requests;
|
mod requests;
|
||||||
mod signer;
|
mod signer;
|
||||||
mod signing_queue;
|
mod signing_queue;
|
||||||
mod network_settings;
|
|
||||||
|
|
||||||
|
pub use self::network_settings::NetworkSettings;
|
||||||
pub use self::poll_manager::PollManager;
|
pub use self::poll_manager::PollManager;
|
||||||
pub use self::poll_filter::{PollFilter, limit_logs};
|
pub use self::poll_filter::{PollFilter, limit_logs};
|
||||||
pub use self::requests::{
|
pub use self::requests::{
|
||||||
@ -36,4 +38,3 @@ pub use self::signing_queue::{
|
|||||||
ConfirmationsQueue, ConfirmationPromise, ConfirmationResult, SigningQueue, QueueEvent, DefaultAccount,
|
ConfirmationsQueue, ConfirmationPromise, ConfirmationResult, SigningQueue, QueueEvent, DefaultAccount,
|
||||||
};
|
};
|
||||||
pub use self::signer::SignerService;
|
pub use self::signer::SignerService;
|
||||||
pub use self::network_settings::NetworkSettings;
|
|
||||||
|
@ -258,20 +258,6 @@ fn check_known<C>(client: &C, number: BlockNumber) -> Result<(), Error> where C:
|
|||||||
|
|
||||||
const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6.
|
const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6.
|
||||||
|
|
||||||
impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
|
||||||
C: MiningBlockChainClient + 'static,
|
|
||||||
SN: SnapshotService + 'static,
|
|
||||||
S: SyncProvider + 'static,
|
|
||||||
M: MinerService + 'static,
|
|
||||||
EM: ExternalMinerService + 'static {
|
|
||||||
|
|
||||||
fn active(&self) -> Result<(), Error> {
|
|
||||||
// TODO: only call every 30s at most.
|
|
||||||
take_weak!(self.client).keep_alive();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
static SOLC: &'static str = "solc.exe";
|
static SOLC: &'static str = "solc.exe";
|
||||||
|
|
||||||
@ -288,8 +274,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
type Metadata = Metadata;
|
type Metadata = Metadata;
|
||||||
|
|
||||||
fn protocol_version(&self) -> Result<String, Error> {
|
fn protocol_version(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let version = take_weak!(self.sync).status().protocol_version.to_owned();
|
let version = take_weak!(self.sync).status().protocol_version.to_owned();
|
||||||
Ok(format!("{}", version))
|
Ok(format!("{}", version))
|
||||||
}
|
}
|
||||||
@ -297,7 +281,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
fn syncing(&self) -> Result<SyncStatus, Error> {
|
fn syncing(&self) -> Result<SyncStatus, Error> {
|
||||||
use ethcore::snapshot::RestorationStatus;
|
use ethcore::snapshot::RestorationStatus;
|
||||||
|
|
||||||
self.active()?;
|
|
||||||
let status = take_weak!(self.sync).status();
|
let status = take_weak!(self.sync).status();
|
||||||
let client = take_weak!(self.client);
|
let client = take_weak!(self.client);
|
||||||
let snapshot_status = take_weak!(self.snapshot).status();
|
let snapshot_status = take_weak!(self.snapshot).status();
|
||||||
@ -331,8 +314,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let dapp = meta.dapp_id.unwrap_or_default();
|
let dapp = meta.dapp_id.unwrap_or_default();
|
||||||
|
|
||||||
let author = move || {
|
let author = move || {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let mut miner = take_weak!(self.miner).author();
|
let mut miner = take_weak!(self.miner).author();
|
||||||
if miner == 0.into() {
|
if miner == 0.into() {
|
||||||
let accounts = self.dapp_accounts(dapp.into())?;
|
let accounts = self.dapp_accounts(dapp.into())?;
|
||||||
@ -348,20 +329,14 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_mining(&self) -> Result<bool, Error> {
|
fn is_mining(&self) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.miner).is_sealing())
|
Ok(take_weak!(self.miner).is_sealing())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hashrate(&self) -> Result<RpcU256, Error> {
|
fn hashrate(&self) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(RpcU256::from(self.external_miner.hashrate()))
|
Ok(RpcU256::from(self.external_miner.hashrate()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gas_price(&self) -> Result<RpcU256, Error> {
|
fn gas_price(&self) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
|
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
|
||||||
Ok(RpcU256::from(default_gas_price(&*client, &*miner)))
|
Ok(RpcU256::from(default_gas_price(&*client, &*miner)))
|
||||||
}
|
}
|
||||||
@ -370,8 +345,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let dapp = meta.dapp_id.unwrap_or_default();
|
let dapp = meta.dapp_id.unwrap_or_default();
|
||||||
|
|
||||||
let accounts = move || {
|
let accounts = move || {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let accounts = self.dapp_accounts(dapp.into())?;
|
let accounts = self.dapp_accounts(dapp.into())?;
|
||||||
Ok(accounts.into_iter().map(Into::into).collect())
|
Ok(accounts.into_iter().map(Into::into).collect())
|
||||||
};
|
};
|
||||||
@ -380,8 +353,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_number(&self) -> Result<RpcU256, Error> {
|
fn block_number(&self) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(RpcU256::from(take_weak!(self.client).chain_info().best_block_number))
|
Ok(RpcU256::from(take_weak!(self.client).chain_info().best_block_number))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +360,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let address = address.into();
|
let address = address.into();
|
||||||
|
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
match num.0.clone() {
|
match num.0.clone() {
|
||||||
BlockNumber::Pending => Ok(take_weak!(self.miner).balance(&*take_weak!(self.client), &address).into()),
|
BlockNumber::Pending => Ok(take_weak!(self.miner).balance(&*take_weak!(self.client), &address).into()),
|
||||||
id => {
|
id => {
|
||||||
@ -412,7 +382,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let position: U256 = RpcU256::into(pos);
|
let position: U256 = RpcU256::into(pos);
|
||||||
|
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
match num.0.clone() {
|
match num.0.clone() {
|
||||||
BlockNumber::Pending => Ok(take_weak!(self.miner).storage_at(&*take_weak!(self.client), &address, &H256::from(position)).into()),
|
BlockNumber::Pending => Ok(take_weak!(self.miner).storage_at(&*take_weak!(self.client), &address, &H256::from(position)).into()),
|
||||||
id => {
|
id => {
|
||||||
@ -433,7 +402,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
||||||
let address: Address = RpcH160::into(address);
|
let address: Address = RpcH160::into(address);
|
||||||
let inner = move || {
|
let inner = move || {
|
||||||
self.active()?;
|
|
||||||
match num.0.clone() {
|
match num.0.clone() {
|
||||||
BlockNumber::Pending => Ok(take_weak!(self.miner).nonce(&*take_weak!(self.client), &address).into()),
|
BlockNumber::Pending => Ok(take_weak!(self.miner).nonce(&*take_weak!(self.client), &address).into()),
|
||||||
id => {
|
id => {
|
||||||
@ -453,7 +421,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
Ok(take_weak!(self.client).block(BlockId::Hash(hash.into()))
|
Ok(take_weak!(self.client).block(BlockId::Hash(hash.into()))
|
||||||
.map(|block| block.transactions_count().into()))
|
.map(|block| block.transactions_count().into()))
|
||||||
};
|
};
|
||||||
@ -463,7 +430,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
match num {
|
match num {
|
||||||
BlockNumber::Pending => Ok(Some(
|
BlockNumber::Pending => Ok(Some(
|
||||||
take_weak!(self.miner).status().transactions_in_pending_block.into()
|
take_weak!(self.miner).status().transactions_in_pending_block.into()
|
||||||
@ -480,7 +446,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
Ok(take_weak!(self.client).block(BlockId::Hash(hash.into()))
|
Ok(take_weak!(self.client).block(BlockId::Hash(hash.into()))
|
||||||
.map(|block| block.uncles_count().into()))
|
.map(|block| block.uncles_count().into()))
|
||||||
};
|
};
|
||||||
@ -490,7 +455,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
match num {
|
match num {
|
||||||
BlockNumber::Pending => Ok(Some(0.into())),
|
BlockNumber::Pending => Ok(Some(0.into())),
|
||||||
_ => Ok(
|
_ => Ok(
|
||||||
@ -507,7 +471,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let address: Address = RpcH160::into(address);
|
let address: Address = RpcH160::into(address);
|
||||||
|
|
||||||
let inner = || {
|
let inner = || {
|
||||||
self.active()?;
|
|
||||||
match num.0.clone() {
|
match num.0.clone() {
|
||||||
BlockNumber::Pending => Ok(take_weak!(self.miner).code(&*take_weak!(self.client), &address).map_or_else(Bytes::default, Bytes::new)),
|
BlockNumber::Pending => Ok(take_weak!(self.miner).code(&*take_weak!(self.client), &address).map_or_else(Bytes::default, Bytes::new)),
|
||||||
id => {
|
id => {
|
||||||
@ -526,25 +489,14 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
||||||
let inner = || {
|
future::done(self.block(BlockId::Hash(hash.into()), include_txs)).boxed()
|
||||||
self.active()?;
|
|
||||||
self.block(BlockId::Hash(hash.into()), include_txs)
|
|
||||||
};
|
|
||||||
|
|
||||||
future::done(inner()).boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture<Option<RichBlock>, Error> {
|
||||||
let inner = || {
|
future::done(self.block(num.into(), include_txs)).boxed()
|
||||||
self.active()?;
|
|
||||||
self.block(num.into(), include_txs)
|
|
||||||
};
|
|
||||||
|
|
||||||
future::done(inner()).boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_by_hash(&self, hash: RpcH256) -> Result<Option<Transaction>, Error> {
|
fn transaction_by_hash(&self, hash: RpcH256) -> Result<Option<Transaction>, Error> {
|
||||||
self.active()?;
|
|
||||||
let hash: H256 = hash.into();
|
let hash: H256 = hash.into();
|
||||||
let miner = take_weak!(self.miner);
|
let miner = take_weak!(self.miner);
|
||||||
let client = take_weak!(self.client);
|
let client = take_weak!(self.client);
|
||||||
@ -552,20 +504,14 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result<Option<Transaction>, Error> {
|
fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result<Option<Transaction>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value()))
|
self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result<Option<Transaction>, Error> {
|
fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result<Option<Transaction>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.transaction(TransactionId::Location(num.into(), index.value()))
|
self.transaction(TransactionId::Location(num.into(), index.value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_receipt(&self, hash: RpcH256) -> Result<Option<Receipt>, Error> {
|
fn transaction_receipt(&self, hash: RpcH256) -> Result<Option<Receipt>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let miner = take_weak!(self.miner);
|
let miner = take_weak!(self.miner);
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
||||||
let hash: H256 = hash.into();
|
let hash: H256 = hash.into();
|
||||||
@ -580,20 +526,14 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result<Option<RichBlock>, Error> {
|
fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result<Option<RichBlock>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.uncle(UncleId { block: BlockId::Hash(hash.into()), position: index.value() })
|
self.uncle(UncleId { block: BlockId::Hash(hash.into()), position: index.value() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result<Option<RichBlock>, Error> {
|
fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result<Option<RichBlock>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.uncle(UncleId { block: num.into(), position: index.value() })
|
self.uncle(UncleId { block: num.into(), position: index.value() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compilers(&self) -> Result<Vec<String>, Error> {
|
fn compilers(&self) -> Result<Vec<String>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let mut compilers = vec![];
|
let mut compilers = vec![];
|
||||||
if Command::new(SOLC).output().is_ok() {
|
if Command::new(SOLC).output().is_ok() {
|
||||||
compilers.push("solidity".to_owned())
|
compilers.push("solidity".to_owned())
|
||||||
@ -622,7 +562,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> {
|
fn work(&self, no_new_work_timeout: Trailing<u64>) -> Result<Work, Error> {
|
||||||
self.active()?;
|
|
||||||
let no_new_work_timeout = no_new_work_timeout.0;
|
let no_new_work_timeout = no_new_work_timeout.0;
|
||||||
|
|
||||||
let client = take_weak!(self.client);
|
let client = take_weak!(self.client);
|
||||||
@ -674,8 +613,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> {
|
fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let nonce: H64 = nonce.into();
|
let nonce: H64 = nonce.into();
|
||||||
let pow_hash: H256 = pow_hash.into();
|
let pow_hash: H256 = pow_hash.into();
|
||||||
let mix_hash: H256 = mix_hash.into();
|
let mix_hash: H256 = mix_hash.into();
|
||||||
@ -688,14 +625,11 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn submit_hashrate(&self, rate: RpcU256, id: RpcH256) -> Result<bool, Error> {
|
fn submit_hashrate(&self, rate: RpcU256, id: RpcH256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
self.external_miner.submit_hashrate(rate.into(), id.into());
|
self.external_miner.submit_hashrate(rate.into(), id.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_raw_transaction(&self, raw: Bytes) -> Result<RpcH256, Error> {
|
fn send_raw_transaction(&self, raw: Bytes) -> Result<RpcH256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
UntrustedRlp::new(&raw.into_vec()).as_val()
|
UntrustedRlp::new(&raw.into_vec()).as_val()
|
||||||
.map_err(errors::from_rlp_error)
|
.map_err(errors::from_rlp_error)
|
||||||
.and_then(|tx| SignedTransaction::new(tx).map_err(errors::from_transaction_error))
|
.and_then(|tx| SignedTransaction::new(tx).map_err(errors::from_transaction_error))
|
||||||
@ -710,8 +644,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&self, request: CallRequest, num: Trailing<BlockNumber>) -> Result<Bytes, Error> {
|
fn call(&self, request: CallRequest, num: Trailing<BlockNumber>) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let request = CallRequest::into(request);
|
let request = CallRequest::into(request);
|
||||||
let signed = self.sign_call(request)?;
|
let signed = self.sign_call(request)?;
|
||||||
|
|
||||||
@ -726,8 +658,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_gas(&self, request: CallRequest, num: Trailing<BlockNumber>) -> Result<RpcU256, Error> {
|
fn estimate_gas(&self, request: CallRequest, num: Trailing<BlockNumber>) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let request = CallRequest::into(request);
|
let request = CallRequest::into(request);
|
||||||
let signed = self.sign_call(request)?;
|
let signed = self.sign_call(request)?;
|
||||||
take_weak!(self.client).estimate_gas(&signed, num.0.into())
|
take_weak!(self.client).estimate_gas(&signed, num.0.into())
|
||||||
@ -736,19 +666,14 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compile_lll(&self, _: String) -> Result<Bytes, Error> {
|
fn compile_lll(&self, _: String) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
rpc_unimplemented!()
|
rpc_unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_serpent(&self, _: String) -> Result<Bytes, Error> {
|
fn compile_serpent(&self, _: String) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
rpc_unimplemented!()
|
rpc_unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_solidity(&self, code: String) -> Result<Bytes, Error> {
|
fn compile_solidity(&self, code: String) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
let maybe_child = Command::new(SOLC)
|
let maybe_child = Command::new(SOLC)
|
||||||
.arg("--bin")
|
.arg("--bin")
|
||||||
.arg("--optimize")
|
.arg("--optimize")
|
||||||
|
@ -50,19 +50,12 @@ impl<C, M> EthFilterClient<C, M> where
|
|||||||
polls: Mutex::new(PollManager::new()),
|
polls: Mutex::new(PollManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active(&self) -> Result<(), Error> {
|
|
||||||
// TODO: only call every 30s at most.
|
|
||||||
take_weak!(self.client).keep_alive();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M> EthFilter for EthFilterClient<C, M>
|
impl<C, M> EthFilter for EthFilterClient<C, M>
|
||||||
where C: BlockChainClient + 'static, M: MinerService + 'static
|
where C: BlockChainClient + 'static, M: MinerService + 'static
|
||||||
{
|
{
|
||||||
fn new_filter(&self, filter: Filter) -> Result<RpcU256, Error> {
|
fn new_filter(&self, filter: Filter) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls.lock();
|
||||||
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
let block_number = take_weak!(self.client).chain_info().best_block_number;
|
||||||
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
|
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
|
||||||
@ -70,16 +63,12 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_block_filter(&self) -> Result<RpcU256, Error> {
|
fn new_block_filter(&self) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls.lock();
|
||||||
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
|
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
|
||||||
Ok(id.into())
|
Ok(id.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_pending_transaction_filter(&self) -> Result<RpcU256, Error> {
|
fn new_pending_transaction_filter(&self) -> Result<RpcU256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls.lock();
|
||||||
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
let best_block = take_weak!(self.client).chain_info().best_block_number;
|
||||||
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(best_block);
|
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(best_block);
|
||||||
@ -88,7 +77,6 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_changes(&self, index: Index) -> Result<FilterChanges, Error> {
|
fn filter_changes(&self, index: Index) -> Result<FilterChanges, Error> {
|
||||||
self.active()?;
|
|
||||||
let client = take_weak!(self.client);
|
let client = take_weak!(self.client);
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls.lock();
|
||||||
match polls.poll_mut(&index.value()) {
|
match polls.poll_mut(&index.value()) {
|
||||||
@ -180,8 +168,6 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_logs(&self, index: Index) -> Result<Vec<Log>, Error> {
|
fn filter_logs(&self, index: Index) -> Result<Vec<Log>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let mut polls = self.polls.lock();
|
let mut polls = self.polls.lock();
|
||||||
match polls.poll(&index.value()) {
|
match polls.poll(&index.value()) {
|
||||||
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
|
Some(&PollFilter::Logs(ref _block_number, ref _previous_log, ref filter)) => {
|
||||||
@ -207,8 +193,6 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn uninstall_filter(&self, index: Index) -> Result<bool, Error> {
|
fn uninstall_filter(&self, index: Index) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.polls.lock().remove_poll(&index.value());
|
self.polls.lock().remove_poll(&index.value());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
@ -101,12 +101,6 @@ impl<C, M, S: ?Sized, U> ParityClient<C, M, S, U> where
|
|||||||
dapps_port: dapps_port,
|
dapps_port: dapps_port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active(&self) -> Result<(), Error> {
|
|
||||||
// TODO: only call every 30s at most.
|
|
||||||
take_weak!(self.client).keep_alive();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||||
@ -118,8 +112,6 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
type Metadata = Metadata;
|
type Metadata = Metadata;
|
||||||
|
|
||||||
fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> {
|
fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let dapp = dapp.0;
|
let dapp = dapp.0;
|
||||||
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
@ -149,8 +141,6 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> {
|
fn default_account(&self, meta: Self::Metadata) -> BoxFuture<H160, Error> {
|
||||||
let dapp_id = meta.dapp_id.unwrap_or_default();
|
let dapp_id = meta.dapp_id.unwrap_or_default();
|
||||||
let default_account = move || {
|
let default_account = move || {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.accounts)
|
Ok(take_weak!(self.accounts)
|
||||||
.dapps_addresses(dapp_id.into())
|
.dapps_addresses(dapp_id.into())
|
||||||
.ok()
|
.ok()
|
||||||
@ -163,57 +153,39 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transactions_limit(&self) -> Result<usize, Error> {
|
fn transactions_limit(&self) -> Result<usize, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.miner).transactions_limit())
|
Ok(take_weak!(self.miner).transactions_limit())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min_gas_price(&self) -> Result<U256, Error> {
|
fn min_gas_price(&self) -> Result<U256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(U256::from(take_weak!(self.miner).minimal_gas_price()))
|
Ok(U256::from(take_weak!(self.miner).minimal_gas_price()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_data(&self) -> Result<Bytes, Error> {
|
fn extra_data(&self) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(Bytes::new(take_weak!(self.miner).extra_data()))
|
Ok(Bytes::new(take_weak!(self.miner).extra_data()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gas_floor_target(&self) -> Result<U256, Error> {
|
fn gas_floor_target(&self) -> Result<U256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(U256::from(take_weak!(self.miner).gas_floor_target()))
|
Ok(U256::from(take_weak!(self.miner).gas_floor_target()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gas_ceil_target(&self) -> Result<U256, Error> {
|
fn gas_ceil_target(&self) -> Result<U256, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(U256::from(take_weak!(self.miner).gas_ceil_target()))
|
Ok(U256::from(take_weak!(self.miner).gas_ceil_target()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dev_logs(&self) -> Result<Vec<String>, Error> {
|
fn dev_logs(&self) -> Result<Vec<String>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let logs = self.logger.logs();
|
let logs = self.logger.logs();
|
||||||
Ok(logs.as_slice().to_owned())
|
Ok(logs.as_slice().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dev_logs_levels(&self) -> Result<String, Error> {
|
fn dev_logs_levels(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(self.logger.levels().to_owned())
|
Ok(self.logger.levels().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_chain(&self) -> Result<String, Error> {
|
fn net_chain(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(self.settings.chain.clone())
|
Ok(self.settings.chain.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_peers(&self) -> Result<Peers, Error> {
|
fn net_peers(&self) -> Result<Peers, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let sync = take_weak!(self.sync);
|
let sync = take_weak!(self.sync);
|
||||||
let sync_status = sync.status();
|
let sync_status = sync.status();
|
||||||
let net_config = take_weak!(self.net).network_config();
|
let net_config = take_weak!(self.net).network_config();
|
||||||
@ -228,20 +200,14 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn net_port(&self) -> Result<u16, Error> {
|
fn net_port(&self) -> Result<u16, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(self.settings.network_port)
|
Ok(self.settings.network_port)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_name(&self) -> Result<String, Error> {
|
fn node_name(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(self.settings.name.clone())
|
Ok(self.settings.name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn registry_address(&self) -> Result<Option<H160>, Error> {
|
fn registry_address(&self) -> Result<Option<H160>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
take_weak!(self.client)
|
take_weak!(self.client)
|
||||||
.additional_params()
|
.additional_params()
|
||||||
@ -252,7 +218,6 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rpc_settings(&self) -> Result<RpcSettings, Error> {
|
fn rpc_settings(&self) -> Result<RpcSettings, Error> {
|
||||||
self.active()?;
|
|
||||||
Ok(RpcSettings {
|
Ok(RpcSettings {
|
||||||
enabled: self.settings.rpc_enabled,
|
enabled: self.settings.rpc_enabled,
|
||||||
interface: self.settings.rpc_interface.clone(),
|
interface: self.settings.rpc_interface.clone(),
|
||||||
@ -261,19 +226,14 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_extra_data(&self) -> Result<Bytes, Error> {
|
fn default_extra_data(&self) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(Bytes::new(version_data()))
|
Ok(Bytes::new(version_data()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gas_price_histogram(&self) -> Result<Histogram, Error> {
|
fn gas_price_histogram(&self) -> Result<Histogram, Error> {
|
||||||
self.active()?;
|
|
||||||
take_weak!(self.client).gas_price_histogram(100, 10).ok_or_else(errors::not_enough_data).map(Into::into)
|
take_weak!(self.client).gas_price_histogram(100, 10).ok_or_else(errors::not_enough_data).map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unsigned_transactions_count(&self) -> Result<usize, Error> {
|
fn unsigned_transactions_count(&self) -> Result<usize, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
match self.signer {
|
match self.signer {
|
||||||
None => Err(errors::signer_disabled()),
|
None => Err(errors::signer_disabled()),
|
||||||
Some(ref signer) => Ok(signer.len()),
|
Some(ref signer) => Ok(signer.len()),
|
||||||
@ -281,56 +241,40 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate_secret_phrase(&self) -> Result<String, Error> {
|
fn generate_secret_phrase(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(random_phrase(12))
|
Ok(random_phrase(12))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phrase_to_address(&self, phrase: String) -> Result<H160, Error> {
|
fn phrase_to_address(&self, phrase: String) -> Result<H160, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(Brain::new(phrase).generate().unwrap().address().into())
|
Ok(Brain::new(phrase).generate().unwrap().address().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_accounts(&self, count: u64, after: Option<H160>, block_number: Trailing<BlockNumber>) -> Result<Option<Vec<H160>>, Error> {
|
fn list_accounts(&self, count: u64, after: Option<H160>, block_number: Trailing<BlockNumber>) -> Result<Option<Vec<H160>>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.client)
|
Ok(take_weak!(self.client)
|
||||||
.list_accounts(block_number.0.into(), after.map(Into::into).as_ref(), count)
|
.list_accounts(block_number.0.into(), after.map(Into::into).as_ref(), count)
|
||||||
.map(|a| a.into_iter().map(Into::into).collect()))
|
.map(|a| a.into_iter().map(Into::into).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_storage_keys(&self, address: H160, count: u64, after: Option<H256>, block_number: Trailing<BlockNumber>) -> Result<Option<Vec<H256>>, Error> {
|
fn list_storage_keys(&self, address: H160, count: u64, after: Option<H256>, block_number: Trailing<BlockNumber>) -> Result<Option<Vec<H256>>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.client)
|
Ok(take_weak!(self.client)
|
||||||
.list_storage(block_number.0.into(), &address.into(), after.map(Into::into).as_ref(), count)
|
.list_storage(block_number.0.into(), &address.into(), after.map(Into::into).as_ref(), count)
|
||||||
.map(|a| a.into_iter().map(Into::into).collect()))
|
.map(|a| a.into_iter().map(Into::into).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result<Bytes, Error> {
|
fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result<Bytes, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
ecies::encrypt(&key.into(), &DEFAULT_MAC, &phrase.0)
|
ecies::encrypt(&key.into(), &DEFAULT_MAC, &phrase.0)
|
||||||
.map_err(errors::encryption_error)
|
.map_err(errors::encryption_error)
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
fn pending_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.miner).pending_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
Ok(take_weak!(self.miner).pending_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn future_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
fn future_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
Ok(take_weak!(self.miner).future_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
Ok(take_weak!(self.miner).future_transactions().into_iter().map(Into::into).collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error> {
|
fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let stats = take_weak!(self.sync).transactions_stats();
|
let stats = take_weak!(self.sync).transactions_stats();
|
||||||
Ok(stats.into_iter()
|
Ok(stats.into_iter()
|
||||||
.map(|(hash, stats)| (hash.into(), stats.into()))
|
.map(|(hash, stats)| (hash.into(), stats.into()))
|
||||||
@ -339,8 +283,6 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let transactions = take_weak!(self.miner).local_transactions();
|
let transactions = take_weak!(self.miner).local_transactions();
|
||||||
Ok(transactions
|
Ok(transactions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -350,8 +292,6 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signer_port(&self) -> Result<u16, Error> {
|
fn signer_port(&self) -> Result<u16, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.signer
|
self.signer
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|signer| signer.address())
|
.and_then(|signer| signer.address())
|
||||||
@ -360,21 +300,16 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dapps_port(&self) -> Result<u16, Error> {
|
fn dapps_port(&self) -> Result<u16, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.dapps_port
|
self.dapps_port
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::dapps_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dapps_interface(&self) -> Result<String, Error> {
|
fn dapps_interface(&self) -> Result<String, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
self.dapps_interface.clone()
|
self.dapps_interface.clone()
|
||||||
.ok_or_else(|| errors::dapps_disabled())
|
.ok_or_else(|| errors::dapps_disabled())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_nonce(&self, address: H160) -> Result<U256, Error> {
|
fn next_nonce(&self, address: H160) -> Result<U256, Error> {
|
||||||
self.active()?;
|
|
||||||
let address: Address = address.into();
|
let address: Address = address.into();
|
||||||
let miner = take_weak!(self.miner);
|
let miner = take_weak!(self.miner);
|
||||||
let client = take_weak!(self.client);
|
let client = take_weak!(self.client);
|
||||||
@ -400,26 +335,21 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn consensus_capability(&self) -> Result<ConsensusCapability, Error> {
|
fn consensus_capability(&self) -> Result<ConsensusCapability, Error> {
|
||||||
self.active()?;
|
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.capability().into())
|
Ok(updater.capability().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_info(&self) -> Result<VersionInfo, Error> {
|
fn version_info(&self) -> Result<VersionInfo, Error> {
|
||||||
self.active()?;
|
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.version_info().into())
|
Ok(updater.version_info().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn releases_info(&self) -> Result<Option<OperationsInfo>, Error> {
|
fn releases_info(&self) -> Result<Option<OperationsInfo>, Error> {
|
||||||
self.active()?;
|
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.info().map(Into::into))
|
Ok(updater.info().map(Into::into))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chain_status(&self) -> Result<ChainStatus, Error> {
|
fn chain_status(&self) -> Result<ChainStatus, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
let chain_info = take_weak!(self.client).chain_info();
|
let chain_info = take_weak!(self.client).chain_info();
|
||||||
|
|
||||||
let gap = chain_info.ancient_block_number.map(|x| U256::from(x + 1))
|
let gap = chain_info.ancient_block_number.map(|x| U256::from(x + 1))
|
||||||
|
@ -21,7 +21,6 @@ use util::{Address};
|
|||||||
|
|
||||||
use ethkey::{Brain, Generator, Secret};
|
use ethkey::{Brain, Generator, Secret};
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::AccountProvider;
|
||||||
use ethcore::client::MiningBlockChainClient;
|
|
||||||
|
|
||||||
use jsonrpc_core::Error;
|
use jsonrpc_core::Error;
|
||||||
use v1::helpers::errors;
|
use v1::helpers::errors;
|
||||||
@ -29,30 +28,21 @@ use v1::traits::ParityAccounts;
|
|||||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId};
|
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId};
|
||||||
|
|
||||||
/// Account management (personal) rpc implementation.
|
/// Account management (personal) rpc implementation.
|
||||||
pub struct ParityAccountsClient<C> where C: MiningBlockChainClient {
|
pub struct ParityAccountsClient {
|
||||||
accounts: Weak<AccountProvider>,
|
accounts: Weak<AccountProvider>,
|
||||||
client: Weak<C>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> ParityAccountsClient<C> where C: MiningBlockChainClient {
|
impl ParityAccountsClient {
|
||||||
/// Creates new PersonalClient
|
/// Creates new PersonalClient
|
||||||
pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>) -> Self {
|
pub fn new(store: &Arc<AccountProvider>) -> Self {
|
||||||
ParityAccountsClient {
|
ParityAccountsClient {
|
||||||
accounts: Arc::downgrade(store),
|
accounts: Arc::downgrade(store),
|
||||||
client: Arc::downgrade(client),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active(&self) -> Result<(), Error> {
|
|
||||||
// TODO: only call every 30s at most.
|
|
||||||
take_weak!(self.client).keep_alive();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlockChainClient {
|
impl ParityAccounts for ParityAccountsClient {
|
||||||
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, Error> {
|
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
||||||
let other = store.addresses_info();
|
let other = store.addresses_info();
|
||||||
@ -75,7 +65,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> {
|
fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
let brain = Brain::new(phrase).generate().unwrap();
|
let brain = Brain::new(phrase).generate().unwrap();
|
||||||
@ -85,7 +74,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_account_from_wallet(&self, json: String, pass: String) -> Result<RpcH160, Error> {
|
fn new_account_from_wallet(&self, json: String, pass: String) -> Result<RpcH160, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
store.import_presale(json.as_bytes(), &pass)
|
store.import_presale(json.as_bytes(), &pass)
|
||||||
@ -95,7 +83,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
|
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
let secret = Secret::from_slice(&secret.0)
|
let secret = Secret::from_slice(&secret.0)
|
||||||
@ -106,7 +93,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_password(&self, account: RpcH160, password: String) -> Result<bool, Error> {
|
fn test_password(&self, account: RpcH160, password: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let account: Address = account.into();
|
let account: Address = account.into();
|
||||||
|
|
||||||
take_weak!(self.accounts)
|
take_weak!(self.accounts)
|
||||||
@ -115,7 +101,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn change_password(&self, account: RpcH160, password: String, new_password: String) -> Result<bool, Error> {
|
fn change_password(&self, account: RpcH160, password: String, new_password: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let account: Address = account.into();
|
let account: Address = account.into();
|
||||||
take_weak!(self.accounts)
|
take_weak!(self.accounts)
|
||||||
.change_password(&account, password, new_password)
|
.change_password(&account, password, new_password)
|
||||||
@ -124,7 +109,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn kill_account(&self, account: RpcH160, password: String) -> Result<bool, Error> {
|
fn kill_account(&self, account: RpcH160, password: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let account: Address = account.into();
|
let account: Address = account.into();
|
||||||
take_weak!(self.accounts)
|
take_weak!(self.accounts)
|
||||||
.kill_account(&account, &password)
|
.kill_account(&account, &password)
|
||||||
@ -133,7 +117,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_address(&self, addr: RpcH160) -> Result<bool, Error> {
|
fn remove_address(&self, addr: RpcH160) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let addr: Address = addr.into();
|
let addr: Address = addr.into();
|
||||||
|
|
||||||
@ -142,7 +125,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_account_name(&self, addr: RpcH160, name: String) -> Result<bool, Error> {
|
fn set_account_name(&self, addr: RpcH160, name: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let addr: Address = addr.into();
|
let addr: Address = addr.into();
|
||||||
|
|
||||||
@ -152,7 +134,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_account_meta(&self, addr: RpcH160, meta: String) -> Result<bool, Error> {
|
fn set_account_meta(&self, addr: RpcH160, meta: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
let addr: Address = addr.into();
|
let addr: Address = addr.into();
|
||||||
|
|
||||||
@ -216,7 +197,6 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn geth_accounts(&self) -> Result<Vec<RpcH160>, Error> {
|
fn geth_accounts(&self) -> Result<Vec<RpcH160>, Error> {
|
||||||
self.active()?;
|
|
||||||
let store = take_weak!(self.accounts);
|
let store = take_weak!(self.accounts);
|
||||||
|
|
||||||
Ok(into_vec(store.list_geth_accounts(false)))
|
Ok(into_vec(store.list_geth_accounts(false)))
|
||||||
|
@ -23,7 +23,7 @@ use ethcore::client::MiningBlockChainClient;
|
|||||||
use ethcore::mode::Mode;
|
use ethcore::mode::Mode;
|
||||||
use ethsync::ManageNetwork;
|
use ethsync::ManageNetwork;
|
||||||
use fetch::{self, Fetch};
|
use fetch::{self, Fetch};
|
||||||
use futures::{self, BoxFuture, Future};
|
use futures::{BoxFuture, Future};
|
||||||
use util::sha3;
|
use util::sha3;
|
||||||
use updater::{Service as UpdateService};
|
use updater::{Service as UpdateService};
|
||||||
|
|
||||||
@ -62,12 +62,6 @@ impl<C, M, U, F> ParitySetClient<C, M, U, F> where
|
|||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active(&self) -> Result<(), Error> {
|
|
||||||
// TODO: only call every 30s at most.
|
|
||||||
take_weak!(self.client).keep_alive();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
||||||
@ -78,63 +72,46 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
{
|
{
|
||||||
|
|
||||||
fn set_min_gas_price(&self, gas_price: U256) -> Result<bool, Error> {
|
fn set_min_gas_price(&self, gas_price: U256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_minimal_gas_price(gas_price.into());
|
take_weak!(self.miner).set_minimal_gas_price(gas_price.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gas_floor_target(&self, target: U256) -> Result<bool, Error> {
|
fn set_gas_floor_target(&self, target: U256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_gas_floor_target(target.into());
|
take_weak!(self.miner).set_gas_floor_target(target.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gas_ceil_target(&self, target: U256) -> Result<bool, Error> {
|
fn set_gas_ceil_target(&self, target: U256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_gas_ceil_target(target.into());
|
take_weak!(self.miner).set_gas_ceil_target(target.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_extra_data(&self, extra_data: Bytes) -> Result<bool, Error> {
|
fn set_extra_data(&self, extra_data: Bytes) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_extra_data(extra_data.into_vec());
|
take_weak!(self.miner).set_extra_data(extra_data.into_vec());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_author(&self, author: H160) -> Result<bool, Error> {
|
fn set_author(&self, author: H160) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_author(author.into());
|
take_weak!(self.miner).set_author(author.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_engine_signer(&self, address: H160, password: String) -> Result<bool, Error> {
|
fn set_engine_signer(&self, address: H160, password: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
take_weak!(self.miner).set_engine_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)?;
|
take_weak!(self.miner).set_engine_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_transactions_limit(&self, limit: usize) -> Result<bool, Error> {
|
fn set_transactions_limit(&self, limit: usize) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_transactions_limit(limit);
|
take_weak!(self.miner).set_transactions_limit(limit);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tx_gas_limit(&self, limit: U256) -> Result<bool, Error> {
|
fn set_tx_gas_limit(&self, limit: U256) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.miner).set_tx_gas_limit(limit.into());
|
take_weak!(self.miner).set_tx_gas_limit(limit.into());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_reserved_peer(&self, peer: String) -> Result<bool, Error> {
|
fn add_reserved_peer(&self, peer: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
match take_weak!(self.net).add_reserved_peer(peer) {
|
match take_weak!(self.net).add_reserved_peer(peer) {
|
||||||
Ok(()) => Ok(true),
|
Ok(()) => Ok(true),
|
||||||
Err(e) => Err(errors::invalid_params("Peer address", e)),
|
Err(e) => Err(errors::invalid_params("Peer address", e)),
|
||||||
@ -142,8 +119,6 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_reserved_peer(&self, peer: String) -> Result<bool, Error> {
|
fn remove_reserved_peer(&self, peer: String) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
match take_weak!(self.net).remove_reserved_peer(peer) {
|
match take_weak!(self.net).remove_reserved_peer(peer) {
|
||||||
Ok(()) => Ok(true),
|
Ok(()) => Ok(true),
|
||||||
Err(e) => Err(errors::invalid_params("Peer address", e)),
|
Err(e) => Err(errors::invalid_params("Peer address", e)),
|
||||||
@ -151,15 +126,11 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drop_non_reserved_peers(&self) -> Result<bool, Error> {
|
fn drop_non_reserved_peers(&self) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.net).deny_unreserved_peers();
|
take_weak!(self.net).deny_unreserved_peers();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_non_reserved_peers(&self) -> Result<bool, Error> {
|
fn accept_non_reserved_peers(&self) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
|
|
||||||
take_weak!(self.net).accept_unreserved_peers();
|
take_weak!(self.net).accept_unreserved_peers();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@ -186,10 +157,6 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hash_content(&self, url: String) -> BoxFuture<H256, Error> {
|
fn hash_content(&self, url: String) -> BoxFuture<H256, Error> {
|
||||||
if let Err(e) = self.active() {
|
|
||||||
return futures::failed(e).boxed();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fetch.process(self.fetch.fetch(&url).then(move |result| {
|
self.fetch.process(self.fetch.fetch(&url).then(move |result| {
|
||||||
result
|
result
|
||||||
.map_err(errors::from_fetch_error)
|
.map_err(errors::from_fetch_error)
|
||||||
@ -201,13 +168,11 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
fn upgrade_ready(&self) -> Result<Option<ReleaseInfo>, Error> {
|
||||||
self.active()?;
|
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.upgrade_ready().map(Into::into))
|
Ok(updater.upgrade_ready().map(Into::into))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_upgrade(&self) -> Result<bool, Error> {
|
fn execute_upgrade(&self) -> Result<bool, Error> {
|
||||||
self.active()?;
|
|
||||||
let updater = take_weak!(self.updater);
|
let updater = take_weak!(self.updater);
|
||||||
Ok(updater.execute_upgrade())
|
Ok(updater.execute_upgrade())
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user