From 793579885a113dd08dbb56de2c0db28401e86f0e Mon Sep 17 00:00:00 2001 From: Gheorghe Pinzaru Date: Thu, 30 Jul 2020 12:39:55 +0300 Subject: [PATCH] Add universal QR scanner via common router Rename events Add router to handle all links Use router in add new chat Unify universal link and universal qr with router Add icon for universal scanner Update tests Now routing is tested in routing PR lint Cleanup QA fixes Scan own profile Handle more EIP Fix wallet scanner Fix stack for view profile in UL Signed-off-by: Gheorghe Pinzaru --- nix/deps/clojure/deps.json | 246 ++++++++++-------- nix/deps/clojure/deps.list | 24 +- shadow-cljs.edn | 2 + src/status_im/events.cljs | 20 -- src/status_im/qr_scanner/core.cljs | 74 +++++- src/status_im/router/core.cljs | 180 +++++++++++++ src/status_im/router/core_test.cljs | 52 ++++ .../ui/screens/add_new/new_chat/events.cljs | 44 ++-- .../ui/screens/add_new/new_chat/views.cljs | 5 +- .../edit_bootnode/views.cljs | 3 +- .../ui/screens/home/sheet/views.cljs | 15 ++ .../edit_mailserver/views.cljs | 3 +- .../ui/screens/profile/contact/views.cljs | 1 + .../ui/screens/wallet/accounts/views.cljs | 20 +- .../ui/screens/wallet/add_new/views.cljs | 24 +- .../ui/screens/wallet/send/sheets.cljs | 22 +- src/status_im/utils/universal_links/core.cljs | 125 ++++----- .../utils/universal_links/core_test.cljs | 51 +--- .../wallet/choose_recipient/core.cljs | 63 ++--- src/status_im/wallet/core.cljs | 14 +- translations/en.json | 2 + 21 files changed, 598 insertions(+), 392 deletions(-) create mode 100644 src/status_im/router/core.cljs create mode 100644 src/status_im/router/core_test.cljs diff --git a/nix/deps/clojure/deps.json b/nix/deps/clojure/deps.json index 8a769e3c016..249a8011c8d 100644 --- a/nix/deps/clojure/deps.json +++ b/nix/deps/clojure/deps.json @@ -12,6 +12,19 @@ } }, + { + "path": "bidi/bidi/2.1.6/bidi-2.1.6", + "host": "https://repo.clojars.org", + "pom": { + "sha1": "179b6a4d499f6830d8bf8ae030d82d8a49f61924", + "sha256": "1g3pzsal938f9s5xvfbkjplmprry33j0nc9x106z41jzzgqry37h" + }, + "jar": { + "sha1": "e17fa1c05ff99e99543c6d5328e293e933e15e06", + "sha256": "1gld043c5qz7v9bp5s61vf1s5f8f1pbda9nzwqhy893dpm8xv0qb" + } + }, + { "path": "binaryage/env-config/0.2.2/env-config-0.2.2", "host": "https://repo.clojars.org", @@ -78,15 +91,15 @@ }, { - "path": "cljsjs/react/16.13.0-0/react-16.13.0-0", + "path": "cljsjs/react-dom-server/16.13.0-0/react-dom-server-16.13.0-0", "host": "https://repo.clojars.org", "pom": { - "sha1": "66011033045a6aaa778c01ed308675fa008817f0", - "sha256": "1j17bw4vmi26782v84znxnrx1qpi0wm6agbpczds0gb5f9nr2myv" + "sha1": "320f6487531caf52b15ef10d43129b80c40f7c35", + "sha256": "1l9s4akd1s06xcx0r38h6xf82bcr0ypmaw6xnvdrmll19a3g9ilc" }, "jar": { - "sha1": "b72f3f9a69ec4039ac57597859ea4b5d1cfcd113", - "sha256": "1dp4pi9jphncifa56kqj6sri8vwl62ia1xy3ii70fba1mdjij63s" + "sha1": "4d400ae4f66687286170eb691aa8e642fe87aa47", + "sha256": "0fzwd6hfb2rx85pr6fsr2r4x76a90wgnjgnv23jzfn1vw6znch07" } }, @@ -104,15 +117,15 @@ }, { - "path": "cljsjs/react-dom-server/16.13.0-0/react-dom-server-16.13.0-0", + "path": "cljsjs/react/16.13.0-0/react-16.13.0-0", "host": "https://repo.clojars.org", "pom": { - "sha1": "320f6487531caf52b15ef10d43129b80c40f7c35", - "sha256": "1l9s4akd1s06xcx0r38h6xf82bcr0ypmaw6xnvdrmll19a3g9ilc" + "sha1": "66011033045a6aaa778c01ed308675fa008817f0", + "sha256": "1j17bw4vmi26782v84znxnrx1qpi0wm6agbpczds0gb5f9nr2myv" }, "jar": { - "sha1": "4d400ae4f66687286170eb691aa8e642fe87aa47", - "sha256": "0fzwd6hfb2rx85pr6fsr2r4x76a90wgnjgnv23jzfn1vw6znch07" + "sha1": "b72f3f9a69ec4039ac57597859ea4b5d1cfcd113", + "sha256": "1dp4pi9jphncifa56kqj6sri8vwl62ia1xy3ii70fba1mdjij63s" } }, @@ -246,19 +259,6 @@ } }, - { - "path": "com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1", - "host": "https://repo1.maven.org/maven2", - "pom": { - "sha1": "5e902aae26ac5c36f6420f689f43333129dd69e2", - "sha256": "10vzlnl8vbjv2jqf818wdb7kgy9c6qjka7fjmmi3vdpg1mcn6pv6" - }, - "jar": { - "sha1": "c9ad4a0850ab676c5c64461a05ca524cdfff59f1", - "sha256": "170rflxnqnah0265ik2aylmxkshyqbf2zas9bp2l32xqj9l6jsaf" - } - }, - { "path": "com/google/elemental2/elemental2-core/1.0.0-RC1/elemental2-core-1.0.0-RC1", "host": "https://repo1.maven.org/maven2", @@ -390,54 +390,15 @@ }, { - "path": "commons-codec/commons-codec/1.10/commons-codec-1.10", - "host": "https://repo1.maven.org/maven2", - "pom": { - "sha1": "44b9477418d2942d45550f7e7c66c16262062d0e", - "sha256": "1yscxabk7i59vgfjg7c1y3prj39h1d8prnwgxbisc4ni29qdpf5x" - }, - "jar": { - "sha1": "4b95f4897fa13f2cd904aee711aeafc0c5295cd8", - "sha256": "0scm6321zz76dc3bs8sy2qyami755lz4lq5455gl67bi9slxyha2" - } - }, - - { - "path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4", - "host": "https://repo1.maven.org/maven2", - "pom": { - "sha1": "65112009d674333c1acfafb4e198ff250d710764", - "sha256": "007nyd66fqp3fbrmnsbfp1fpkhmr2lk33qmkp3salqld3xd7qlc8" - }, - "jar": { - "sha1": "f95188e3d372e20e7328706c37ef366e5d7859b0", - "sha256": "1xyyl54sfxsdcwxdyq6b0azmr31b4dwqns850jjkw9a9dwrh5v54" - } - }, - - { - "path": "commons-io/commons-io/2.6/commons-io-2.6", + "path": "com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1", "host": "https://repo1.maven.org/maven2", "pom": { - "sha1": "5060835593e5b6ed18c82fc2e782f0a3c30a00b1", - "sha256": "0q4a6fp6xkyd86ikymkyv2plhf9vj8aqvggxg9d1yad2jcw8c8qc" - }, - "jar": { - "sha1": "815893df5f31da2ece4040fe0a12fd44b577afaf", - "sha256": "04v5fg53jl9gbn6pyz3l7kbpxv0xjzyasnw6yd1a3hhacq2d6xzq" - } - }, - - { - "path": "compojure/compojure/1.5.2/compojure-1.5.2", - "host": "https://repo.clojars.org", - "pom": { - "sha1": "9e8da477b6682094d56802cb155291a2acb829bd", - "sha256": "036z64iprypccz03iq7lqxvw99xjh4xlsfmfwbs37pmhfnfmbdnx" + "sha1": "5e902aae26ac5c36f6420f689f43333129dd69e2", + "sha256": "10vzlnl8vbjv2jqf818wdb7kgy9c6qjka7fjmmi3vdpg1mcn6pv6" }, "jar": { - "sha1": "0b5258d0616ffc5f64c2b6d95f09de56d24df439", - "sha256": "1s2k05lwnlm9a66mxnsss437i9gp70dny8y2rlfkl090s6mdqsaf" + "sha1": "c9ad4a0850ab676c5c64461a05ca524cdfff59f1", + "sha256": "170rflxnqnah0265ik2aylmxkshyqbf2zas9bp2l32xqj9l6jsaf" } }, @@ -519,6 +480,58 @@ } }, + { + "path": "commons-codec/commons-codec/1.10/commons-codec-1.10", + "host": "https://repo1.maven.org/maven2", + "pom": { + "sha1": "44b9477418d2942d45550f7e7c66c16262062d0e", + "sha256": "1yscxabk7i59vgfjg7c1y3prj39h1d8prnwgxbisc4ni29qdpf5x" + }, + "jar": { + "sha1": "4b95f4897fa13f2cd904aee711aeafc0c5295cd8", + "sha256": "0scm6321zz76dc3bs8sy2qyami755lz4lq5455gl67bi9slxyha2" + } + }, + + { + "path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4", + "host": "https://repo1.maven.org/maven2", + "pom": { + "sha1": "65112009d674333c1acfafb4e198ff250d710764", + "sha256": "007nyd66fqp3fbrmnsbfp1fpkhmr2lk33qmkp3salqld3xd7qlc8" + }, + "jar": { + "sha1": "f95188e3d372e20e7328706c37ef366e5d7859b0", + "sha256": "1xyyl54sfxsdcwxdyq6b0azmr31b4dwqns850jjkw9a9dwrh5v54" + } + }, + + { + "path": "commons-io/commons-io/2.6/commons-io-2.6", + "host": "https://repo1.maven.org/maven2", + "pom": { + "sha1": "5060835593e5b6ed18c82fc2e782f0a3c30a00b1", + "sha256": "0q4a6fp6xkyd86ikymkyv2plhf9vj8aqvggxg9d1yad2jcw8c8qc" + }, + "jar": { + "sha1": "815893df5f31da2ece4040fe0a12fd44b577afaf", + "sha256": "04v5fg53jl9gbn6pyz3l7kbpxv0xjzyasnw6yd1a3hhacq2d6xzq" + } + }, + + { + "path": "compojure/compojure/1.5.2/compojure-1.5.2", + "host": "https://repo.clojars.org", + "pom": { + "sha1": "9e8da477b6682094d56802cb155291a2acb829bd", + "sha256": "036z64iprypccz03iq7lqxvw99xjh4xlsfmfwbs37pmhfnfmbdnx" + }, + "jar": { + "sha1": "0b5258d0616ffc5f64c2b6d95f09de56d24df439", + "sha256": "1s2k05lwnlm9a66mxnsss437i9gp70dny8y2rlfkl090s6mdqsaf" + } + }, + { "path": "crypto-equality/crypto-equality/1.0.0/crypto-equality-1.0.0", "host": "https://repo.clojars.org", @@ -923,28 +936,28 @@ }, { - "path": "org/clojure/google-closure-library/0.0-20191016-6ae1f72f/google-closure-library-0.0-20191016-6ae1f72f", + "path": "org/clojure/google-closure-library-third-party/0.0-20191016-6ae1f72f/google-closure-library-third-party-0.0-20191016-6ae1f72f", "host": "https://repo1.maven.org/maven2", "pom": { - "sha1": "dea94d117aa88d75e281f877914454f79df06bf2", - "sha256": "1nysls9ywj3b3zvlfwy68kayx47r4bacidmq1vx8w4h043l9kxrr" + "sha1": "1aa0b32fd70e6644dcb06b88b55630d6bbb6f188", + "sha256": "1wq6gvhlvy3mdhj1cb0w62kclqd7x42dqd0275l40rlla5q5h1z1" }, "jar": { - "sha1": "6025affb7181cd40418600864f58eed1ea80055d", - "sha256": "0w6waii2qps7vp3nzbj5nai809xdq26ss0cabl4wz1s8fc6js6qw" + "sha1": "4fc15daa4fd1b150e8c6d32755796634446dd86a", + "sha256": "1ykw162gng0lcbxn2f387vv1l4d7hfwyayhxgzqqgi9jx211fzw5" } }, { - "path": "org/clojure/google-closure-library-third-party/0.0-20191016-6ae1f72f/google-closure-library-third-party-0.0-20191016-6ae1f72f", + "path": "org/clojure/google-closure-library/0.0-20191016-6ae1f72f/google-closure-library-0.0-20191016-6ae1f72f", "host": "https://repo1.maven.org/maven2", "pom": { - "sha1": "1aa0b32fd70e6644dcb06b88b55630d6bbb6f188", - "sha256": "1wq6gvhlvy3mdhj1cb0w62kclqd7x42dqd0275l40rlla5q5h1z1" + "sha1": "dea94d117aa88d75e281f877914454f79df06bf2", + "sha256": "1nysls9ywj3b3zvlfwy68kayx47r4bacidmq1vx8w4h043l9kxrr" }, "jar": { - "sha1": "4fc15daa4fd1b150e8c6d32755796634446dd86a", - "sha256": "1ykw162gng0lcbxn2f387vv1l4d7hfwyayhxgzqqgi9jx211fzw5" + "sha1": "6025affb7181cd40418600864f58eed1ea80055d", + "sha256": "0w6waii2qps7vp3nzbj5nai809xdq26ss0cabl4wz1s8fc6js6qw" } }, @@ -975,28 +988,28 @@ }, { - "path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0", + "path": "org/clojure/tools.analyzer.jvm/1.0.0/tools.analyzer.jvm-1.0.0", "host": "https://repo1.maven.org/maven2", "pom": { - "sha1": "fa72b6392b31762b55e0dc3de1c220ecdc6bb8a7", - "sha256": "1yg46a9zbga7rsrhwdv877hh60hcbyaykn37r3i50icxmgqcylq4" + "sha1": "3aa3ef5c9ee2bdfb92016608964f607a1dbbd0c9", + "sha256": "0mrf62bd0mvf4dikc78rch1iksbl855khiafxa92hij3b2mbfcrw" }, "jar": { - "sha1": "d2fb65426c7998647fb1fb3db1fb5b222ab91df6", - "sha256": "0mj1xi3v03fzcw1smpayvbvbgly2p3ysbgfz7jgfdhmxi4gl6gs6" + "sha1": "04989a29272fe6b3a917788d3fe48a59b2495225", + "sha256": "13hyvq34p642alb14qi8bvvriggsfw9dgds95prpgqgrj3ndqx9b" } }, { - "path": "org/clojure/tools.analyzer.jvm/1.0.0/tools.analyzer.jvm-1.0.0", + "path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0", "host": "https://repo1.maven.org/maven2", "pom": { - "sha1": "3aa3ef5c9ee2bdfb92016608964f607a1dbbd0c9", - "sha256": "0mrf62bd0mvf4dikc78rch1iksbl855khiafxa92hij3b2mbfcrw" + "sha1": "fa72b6392b31762b55e0dc3de1c220ecdc6bb8a7", + "sha256": "1yg46a9zbga7rsrhwdv877hh60hcbyaykn37r3i50icxmgqcylq4" }, "jar": { - "sha1": "04989a29272fe6b3a917788d3fe48a59b2495225", - "sha256": "13hyvq34p642alb14qi8bvvriggsfw9dgds95prpgqgrj3ndqx9b" + "sha1": "d2fb65426c7998647fb1fb3db1fb5b222ab91df6", + "sha256": "0mj1xi3v03fzcw1smpayvbvbgly2p3ysbgfz7jgfdhmxi4gl6gs6" } }, @@ -1196,28 +1209,28 @@ }, { - "path": "quoin/quoin/0.1.2/quoin-0.1.2", + "path": "prismatic/schema/1.1.7/schema-1.1.7", "host": "https://repo.clojars.org", "pom": { - "sha1": "cdabd1b76f3a86c59260cd25be534a0a5b67c302", - "sha256": "0wv7m5l9hafc0ysxg7g7fqmdq94dxmv13cj37il69wvagqskarhc" + "sha1": "d5bb7edbd1830e6400705c2b5797ced1737fb208", + "sha256": "0bmnmx53kjlcxx477wm7xvcnaf2vi95rr164xi49611kfbhh2dsd" }, "jar": { - "sha1": "0dbbb28df3e337233f934468915eb327ff488172", - "sha256": "0b5rc1cmbgg8qpl80jyvh0ldgs7dv86c0qxixazdnz05limmnpb5" + "sha1": "289b571f1cbab59aef436fc0af56e7efc5322685", + "sha256": "01zc13qhkyghhr4s1if9agbgz38gvrg8x62gn1g8fjll9nq7aca9" } }, { - "path": "reagent/reagent/0.10.0/reagent-0.10.0", + "path": "quoin/quoin/0.1.2/quoin-0.1.2", "host": "https://repo.clojars.org", "pom": { - "sha1": "0ced0db04eaf77ec3dfa7a3f56cc022fe2814d8b", - "sha256": "15cqnfwkzxia6bdjq1kism72jf66zdpldk0925313z41ynkqfr8r" + "sha1": "cdabd1b76f3a86c59260cd25be534a0a5b67c302", + "sha256": "0wv7m5l9hafc0ysxg7g7fqmdq94dxmv13cj37il69wvagqskarhc" }, "jar": { - "sha1": "1a88fae22cd0f7f66bb181aa520da264a82f6942", - "sha256": "0vdvm75rpcq9kx2sp0jd2lsa70fv2xymy1g55ss972s85p5j50bm" + "sha1": "0dbbb28df3e337233f934468915eb327ff488172", + "sha256": "0b5rc1cmbgg8qpl80jyvh0ldgs7dv86c0qxixazdnz05limmnpb5" } }, @@ -1234,19 +1247,6 @@ } }, - { - "path": "refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0", - "host": "https://repo.clojars.org", - "pom": { - "sha1": "0bce30b420249ba7e4b90cbb3e046b4bb5416389", - "sha256": "0zmg5qc8d55pry7832isiwd2q237znfjqjpxchd2hvlpalh5qnva" - }, - "jar": { - "sha1": "6bc3441afc94f7ca024e41a864ca75e05df7e207", - "sha256": "0w8hax99y98l53mixxzx2ja0vcnhjv8dnsaz1zj3vqk775ns5w6i" - } - }, - { "path": "re-frame/re-frame/0.12.0/re-frame-0.12.0", "host": "https://repo.clojars.org", @@ -1286,6 +1286,32 @@ } }, + { + "path": "reagent/reagent/0.10.0/reagent-0.10.0", + "host": "https://repo.clojars.org", + "pom": { + "sha1": "0ced0db04eaf77ec3dfa7a3f56cc022fe2814d8b", + "sha256": "15cqnfwkzxia6bdjq1kism72jf66zdpldk0925313z41ynkqfr8r" + }, + "jar": { + "sha1": "1a88fae22cd0f7f66bb181aa520da264a82f6942", + "sha256": "0vdvm75rpcq9kx2sp0jd2lsa70fv2xymy1g55ss972s85p5j50bm" + } + }, + + { + "path": "refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0", + "host": "https://repo.clojars.org", + "pom": { + "sha1": "0bce30b420249ba7e4b90cbb3e046b4bb5416389", + "sha256": "0zmg5qc8d55pry7832isiwd2q237znfjqjpxchd2hvlpalh5qnva" + }, + "jar": { + "sha1": "6bc3441afc94f7ca024e41a864ca75e05df7e207", + "sha256": "0w8hax99y98l53mixxzx2ja0vcnhjv8dnsaz1zj3vqk775ns5w6i" + } + }, + { "path": "ring-cors/ring-cors/0.1.8/ring-cors-0.1.8", "host": "https://repo.clojars.org", diff --git a/nix/deps/clojure/deps.list b/nix/deps/clojure/deps.list index 872e2803dc9..b53486be6b5 100644 --- a/nix/deps/clojure/deps.list +++ b/nix/deps/clojure/deps.list @@ -1,12 +1,13 @@ args4j/args4j/2.0.26/args4j-2.0.26.jar +bidi/bidi/2.1.6/bidi-2.1.6.jar binaryage/env-config/0.2.2/env-config-0.2.2.jar binaryage/oops/0.7.0/oops-0.7.0.jar cider/cider-nrepl/0.24.0/cider-nrepl-0.24.0.jar cider/piggieback/0.4.1/piggieback-0.4.1.jar cljs-bean/cljs-bean/1.3.0/cljs-bean-1.3.0.jar -cljsjs/react/16.13.0-0/react-16.13.0-0.jar -cljsjs/react-dom/16.13.0-0/react-dom-16.13.0-0.jar cljsjs/react-dom-server/16.13.0-0/react-dom-server-16.13.0-0.jar +cljsjs/react-dom/16.13.0-0/react-dom-16.13.0-0.jar +cljsjs/react/16.13.0-0/react-16.13.0-0.jar clout/clout/2.1.2/clout-2.1.2.jar com/andrewmcveigh/cljs-time/0.5.2/cljs-time-0.5.2.jar com/bhauman/cljs-test-display/0.1.1/cljs-test-display-0.1.1.jar @@ -17,7 +18,6 @@ com/cognitect/transit-js/0.8.846/transit-js-0.8.846.jar com/fasterxml/jackson/core/jackson-core/2.8.7/jackson-core-2.8.7.jar com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar com/google/code/gson/gson/2.7/gson-2.7.jar -com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar com/google/elemental2/elemental2-core/1.0.0-RC1/elemental2-core-1.0.0-RC1.jar com/google/errorprone/error_prone_annotations/2.3.1/error_prone_annotations-2.3.1.jar com/google/guava/guava/25.1-jre/guava-25.1-jre.jar @@ -28,16 +28,17 @@ com/google/jsinterop/base/1.0.0/base-1.0.0.jar com/google/jsinterop/jsinterop-annotations/1.0.2/jsinterop-annotations-1.0.2.jar com/google/protobuf/protobuf-java/3.11.1/protobuf-java-3.11.1.jar com/google/re2j/re2j/1.3/re2j-1.3.jar -commons-codec/commons-codec/1.10/commons-codec-1.10.jar -commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar -commons-io/commons-io/2.6/commons-io-2.6.jar -compojure/compojure/1.5.2/compojure-1.5.2.jar +com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar com/taoensso/encore/2.105.0/encore-2.105.0.jar com/taoensso/timbre/4.10.0/timbre-4.10.0.jar com/taoensso/truss/1.5.0/truss-1.5.0.jar com/taoensso/tufte/2.1.0/tufte-2.1.0.jar com/wsscode/pathom/2.2.31/pathom-2.2.31.jar com/wsscode/spec-inspec/1.0.0-alpha2/spec-inspec-1.0.0-alpha2.jar +commons-codec/commons-codec/1.10/commons-codec-1.10.jar +commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar +commons-io/commons-io/2.6/commons-io-2.6.jar +compojure/compojure/1.5.2/compojure-1.5.2.jar crypto-equality/crypto-equality/1.0.0/crypto-equality-1.0.0.jar crypto-random/crypto-random/1.2.0/crypto-random-1.2.0.jar day8/re-frame/test/0.1.5/test-0.1.5.jar @@ -69,12 +70,12 @@ org/clojure/core.rrb-vector/0.1.1/core.rrb-vector-0.1.1.jar org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar org/clojure/data.json/1.0.0/data.json-1.0.0.jar org/clojure/data.priority-map/0.0.7/data.priority-map-0.0.7.jar -org/clojure/google-closure-library/0.0-20191016-6ae1f72f/google-closure-library-0.0-20191016-6ae1f72f.jar org/clojure/google-closure-library-third-party/0.0-20191016-6ae1f72f/google-closure-library-third-party-0.0-20191016-6ae1f72f.jar +org/clojure/google-closure-library/0.0-20191016-6ae1f72f/google-closure-library-0.0-20191016-6ae1f72f.jar org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar org/clojure/test.check/1.0.0/test.check-1.0.0.jar -org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar org/clojure/tools.analyzer.jvm/1.0.0/tools.analyzer.jvm-1.0.0.jar +org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.jar org/clojure/tools.logging/0.4.1/tools.logging-0.4.1.jar org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.jar @@ -90,13 +91,14 @@ org/msgpack/msgpack/0.6.12/msgpack-0.6.12.jar org/ow2/asm/asm/5.2/asm-5.2.jar org/wildfly/client/wildfly-client-config/1.0.1.Final/wildfly-client-config-1.0.1.Final.jar org/wildfly/common/wildfly-common/1.5.2.Final/wildfly-common-1.5.2.Final.jar +prismatic/schema/1.1.7/schema-1.1.7.jar quoin/quoin/0.1.2/quoin-0.1.2.jar -reagent/reagent/0.10.0/reagent-0.10.0.jar re-com/re-com/2.8.0/re-com-2.8.0.jar -refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0.jar re-frame/re-frame/0.12.0/re-frame-0.12.0.jar re-frisk-remote/re-frisk-remote/1.3.1/re-frisk-remote-1.3.1.jar re-frisk/sente/1.15.0/sente-1.15.0.jar +reagent/reagent/0.10.0/reagent-0.10.0.jar +refactor-nrepl/refactor-nrepl/2.5.0/refactor-nrepl-2.5.0.jar ring-cors/ring-cors/0.1.8/ring-cors-0.1.8.jar ring/ring-codec/1.1.2/ring-codec-1.1.2.jar ring/ring-core/1.8.1/ring-core-1.8.1.jar diff --git a/shadow-cljs.edn b/shadow-cljs.edn index e6538004eda..312e6d39a5a 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -17,6 +17,8 @@ [cider/cider-nrepl "0.24.0"] [cider/piggieback "0.4.1"] [re-frisk-remote "1.3.1"] + ;; routing + [bidi "2.1.6"] ;; test dependencies [day8.re-frame/test "0.1.5"] [com.taoensso/tufte "2.1.0"]] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 3fe77000323..f6858a51f3d 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -29,7 +29,6 @@ [status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.pairing.core :as pairing] [status-im.privacy-policy.core :as privacy-policy] - [status-im.qr-scanner.core :as qr-scanner] [status-im.signals.core :as signals] [status-im.stickers.core :as stickers] [status-im.transport.core :as transport] @@ -344,25 +343,6 @@ (fn [cofx [_ qr-code-data _]] (browser/handle-canceled-qr-code cofx (:data qr-code-data)))) -;; qr-scanner module - -(handlers/register-handler-fx - :qr-scanner.ui/scan-qr-code-pressed - (fn [cofx [_ opts]] - (qr-scanner/scan-qr-code cofx opts))) - -(handlers/register-handler-fx - :qr-scanner.callback/scan-qr-code-success - (fn [cofx [_ opts data]] - (qr-scanner/set-qr-code cofx opts data))) - -(handlers/register-handler-fx - :qr-scanner.callback/scan-qr-code-cancel - (fn [cofx [_ opts]] - (fx/merge cofx - (qr-scanner/set-qr-code-cancel opts) - (navigation/navigate-back)))) - ;; privacy-policy module (handlers/register-handler-fx diff --git a/src/status_im/qr_scanner/core.cljs b/src/status_im/qr_scanner/core.cljs index 014bd6f7512..1c71f51ce21 100644 --- a/src/status_im/qr_scanner/core.cljs +++ b/src/status_im/qr_scanner/core.cljs @@ -1,10 +1,16 @@ (ns status-im.qr-scanner.core (:require [re-frame.core :as re-frame] [status-im.i18n :as i18n] + [status-im.chat.models :as chat] + [status-im.router.core :as router] + [status-im.navigation :as navigation] [status-im.utils.utils :as utils] + [status-im.ethereum.core :as ethereum] + [status-im.ui.screens.add-new.new-chat.db :as new-chat.db] [status-im.utils.fx :as fx])) (fx/defn scan-qr-code + {:events [::scan-code]} [_ opts] {:request-permissions-fx {:permissions [:camera] @@ -16,11 +22,73 @@ 50))}}) (fx/defn set-qr-code + {:events [:qr-scanner.callback/scan-qr-code-success]} [{:keys [db]} opts data] (when-let [handler (:handler opts)] {:dispatch [handler data opts]})) (fx/defn set-qr-code-cancel - [_ opts] - (when-let [handler (:cancel-handler opts)] - {:dispatch [handler opts]})) + {:events [:qr-scanner.callback/scan-qr-code-cancel]} + [cofx opts] + (fx/merge cofx + (navigation/navigate-back) + (when-let [handler (:cancel-handler opts)] + (fn [] {:dispatch [handler opts]})))) + +(fx/defn handle-browse [cofx {:keys [url]}] + (fx/merge cofx + {:browser/show-browser-selection url} + (navigation/navigate-back))) + +(fx/defn handle-private-chat [{:keys [db] :as cofx} {:keys [chat-id]}] + (if-not (new-chat.db/own-public-key? db chat-id) + (chat/start-chat cofx chat-id) + {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) + :content (i18n/label :t/can-not-add-yourself)}})) + +(fx/defn handle-public-chat [cofx {:keys [topic]}] + (when (seq topic) + (chat/start-public-chat cofx topic {}))) + +(fx/defn handle-view-profile + [{:keys [db] :as cofx} {:keys [public-key]}] + (let [own (new-chat.db/own-public-key? db public-key)] + (cond + (and public-key own) + (navigation/navigate-to-cofx cofx :tabs {:screen :profile-stack + :params {:screen :my-profile}}) + + (and public-key (not own)) + (navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] public-key) + :tabs + {:screen :chat-stack + :params {:screen :profile}}) + + :else + {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) + :content (i18n/label :t/ens-name-not-found) + :on-dismiss #(re-frame/dispatch [:navigate-to :home])}}))) + +(fx/defn handle-eip681 [cofx data] + (fx/merge cofx + {:dispatch [:wallet/parse-eip681-uri-and-resolve-ens data]} + (navigation/navigate-to-cofx :tabs {:screen :wallet}))) + +(fx/defn match-scan + {:events [::match-scanned-value]} + [cofx {:keys [type] :as data}] + (case type + :public-chat (handle-public-chat cofx data) + :private-chat (handle-private-chat cofx data) + :contact (handle-view-profile cofx data) + :browser (handle-browse cofx data) + :eip681 (handle-eip681 cofx data) + {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) + :on-dismiss #(re-frame/dispatch [:navigate-to :home])}})) + +(fx/defn on-scan + {:events [::on-scan-success]} + [{:keys [db]} uri] + {::router/handle-uri {:chain (ethereum/chain-keyword db) + :uri uri + :cb #(re-frame/dispatch [::match-scanned-value %])}}) diff --git a/src/status_im/router/core.cljs b/src/status_im/router/core.cljs new file mode 100644 index 00000000000..a95da9e526d --- /dev/null +++ b/src/status_im/router/core.cljs @@ -0,0 +1,180 @@ +(ns status-im.router.core + (:require [re-frame.core :as re-frame] + [clojure.string :as string] + [bidi.bidi :as bidi] + [taoensso.timbre :as log] + [status-im.ui.screens.add-new.new-public-chat.db :as public-chat.db] + [status-im.utils.security :as security] + [status-im.ethereum.eip681 :as eip681] + [status-im.ethereum.ens :as ens] + [status-im.ethereum.resolver :as resolver] + [status-im.ethereum.stateofus :as stateofus] + [cljs.spec.alpha :as spec] + [status-im.ethereum.core :as ethereum])) + +(def ethereum-scheme "ethereum:") + +(def uri-schemes ["status-im://" "status-im:"]) + +(def web-prefixes ["https://" "http://" "https://www." "http://wwww."]) + +(def web2-domain "join.status.im") + +(def web-urls (map #(str % web2-domain "/") web-prefixes)) + +(def handled-schemes (set (into uri-schemes web-urls))) + +(def browser-extractor {[#"(.*)" :domain] {"" :browser + "/" :browser}}) + +(def eip-extractor {#{[:prefix "-" :address] + [:address]} + {#{["@" :chain-id] ""} + {#{["/" :function] ""} + :ethereum}}}) + +(def routes ["" {handled-schemes {["" :chat-id] :public-chat + "chat" {["/public/" :chat-id] :public-chat} + "b/" browser-extractor + "browser/" browser-extractor + ["p/" :chat-id] :private-chat + ["u/" :user-id] :user + ["user/" :user-id] :user + ["referral/" :referrer] :referrals} + ethereum-scheme eip-extractor}]) + +(defn match-uri [uri] + (assoc (bidi/match-route routes uri) :uri uri)) + +(defn- ens-name-parse [contact-identity] + (when (string? contact-identity) + (string/lower-case + (if (ens/is-valid-eth-name? contact-identity) + contact-identity + (stateofus/subdomain contact-identity))))) + +(defn resolve-public-key + [{:keys [chain contact-identity cb]}] + (let [registry (get ens/ens-registries chain) + ens-name (ens-name-parse contact-identity)] + (resolver/pubkey registry ens-name cb))) + +(defn match-contact-async + [chain {:keys [user-id]} callback] + (let [public-key? (and (string? user-id) + (string/starts-with? user-id "0x")) + valid-key (and (spec/valid? :global/public-key user-id) + (not= user-id ens/default-key))] + (cond + (and public-key? valid-key) + (callback {:type :contact + :public-key user-id}) + + (and (not public-key?) (string? user-id)) + (let [registry (get ens/ens-registries chain) + ens-name (ens-name-parse user-id) + on-success #(match-contact-async chain {:user-id %} callback)] + (resolver/pubkey registry ens-name on-success)) + + :else + (callback {:type :contact + :error :not-found})))) + +(defn match-public-chat [{:keys [chat-id]}] + (if (public-chat.db/valid-topic? chat-id) + {:type :public-chat + :topic chat-id} + {:type :public-chat + :error :invalid-topic})) + +(defn match-private-chat-async [chain {:keys [chat-id]} cb] + (match-contact-async chain + {:user-id chat-id} + (fn [{:keys [public-key]}] + (if public-key + (cb {:type :private-chat + :chat-id public-key}) + (cb {:type :private-chat + :error :invalid-chat-id}))))) + +(defn match-browser [uri {:keys [domain]}] + ;; NOTE: We rebuild domain from original URI and matched domain + (let [domain (->> (string/split uri domain) + second + (str domain))] + (if (security/safe-link? domain) + {:type :browser + :url domain} + {:type :browser + :error :unsafe-link}))) + +;; NOTE(Ferossgp): Better to handle eip681 also with router instead of regexp. +(defn match-eip681 [uri] + (if-let [message (eip681/parse-uri uri)] + (let [{:keys [paths ens-names]} + (reduce (fn [acc path] + (let [address (get-in message path)] + (if (ens/is-valid-eth-name? address) + (-> acc + (update :paths conj path) + (update :ens-names conj address)) + acc))) + {:paths [] :ens-names []} + [[:address] [:function-arguments :address]])] + (if (empty? ens-names) + ;; if there are no ens-names, we dispatch request-uri-parsed immediately + {:type :eip681 + :message message + :uri uri} + {:type :eip681 + :uri uri + :message message + :paths paths + :ens-names ens-names})) + {:type :eip681 + :uri uri + :error :cannot-parse})) + +(defn address->eip681 [address] + (match-eip681 (str ethereum-scheme address))) + +(defn match-referral [{:keys [referrer]}] + {:type :referrals + :referrer referrer}) + +(defn handle-uri [chain uri cb] + (let [{:keys [handler route-params]} (match-uri uri)] + (log/info "[router] uri " uri " matched " handler " with " route-params) + (cond + (= handler :public-chat) + (cb (match-public-chat route-params)) + + (= handler :browser) + (cb (match-browser uri route-params)) + + (= handler :ethereum) + (cb (match-eip681 uri)) + + (= handler :user) + (match-contact-async chain route-params cb) + + (= handler :private-chat) + (match-private-chat-async chain route-params cb) + + (spec/valid? :global/public-key uri) + (match-contact-async chain {:user-id uri} cb) + + (= handler :referrals) + (cb (match-referral route-params)) + + (ethereum/address? uri) + (cb (address->eip681 uri)) + + :else + (cb {:type :undefined + :data uri})))) + +(re-frame/reg-fx + ::handle-uri + (fn [{:keys [chain uri cb]}] + (handle-uri chain uri cb))) diff --git a/src/status_im/router/core_test.cljs b/src/status_im/router/core_test.cljs new file mode 100644 index 00000000000..55033bfb863 --- /dev/null +++ b/src/status_im/router/core_test.cljs @@ -0,0 +1,52 @@ +(ns status-im.router.core-test + (:require [status-im.router.core :as router] + [cljs.test :refer [deftest are] :include-macros true])) + +(def public-key "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073") + +(deftest parse-uris + (are [uri expected] (= (router/match-uri uri) {:handler (first expected) + :route-params (second expected) + :uri uri}) + + "status-im://status" [:public-chat {:chat-id "status"}] + + "status-im://u/statuse2e" [:user {:user-id "statuse2e"}] + + (str "status-im://user/" public-key) [:user {:user-id public-key}] + + "status-im://b/www.cryptokitties.co" [:browser {:domain "www.cryptokitties.c"}] + + "https://join.status.im/status" [:public-chat {:chat-id "status"}] + + "https://join.status.im/u/statuse2e" [:user {:user-id "statuse2e"}] + + (str "https://join.status.im/user/" public-key) [:user {:user-id public-key}] + + ;; Last char removed by: https://github.com/juxt/bidi/issues/104 + "https://join.status.im/b/www.cryptokitties.co" [:browser {:domain "www.cryptokitties.c"}] + + "https://join.status.im/b/https://www.google.com/" [:browser {:domain "https://www.google.co"}] + + "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" [:ethereum {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}] + + "ethereum:pay-0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" [:ethereum {:prefix "pay" + :address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}] + "ethereum:foo-0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" [:ethereum {:prefix "foo" + :address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}] + + ;; FIXME: Should handle only first line + "ethereum:foo-state-of-us.eth" [:ethereum {:prefix "foo-state-of" + :address "us.eth"}] + + "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@42" [:ethereum {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" + :chain-id "42"}] + + "ethereum:0x0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x12345&uint256=1" [:ethereum {:address "0x0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" + :function "transfer"}] + + "ethereum:0x0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7?value=2.014e18&gas=10&gasLimit=21000&gasPrice=50" [:ethereum {:address "0x0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}] + + "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7@1/transfer?uint256=1" [:ethereum {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" + :chain-id "1" + :function "transfer"}])) diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 614c75e9b79..5c71722b58d 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -2,7 +2,6 @@ (:require [clojure.string :as string] [re-frame.core :as re-frame] [status-im.ethereum.core :as ethereum] - [status-im.utils.universal-links.core :as ul] [status-im.ethereum.ens :as ens] [status-im.ethereum.resolver :as resolver] [status-im.ui.screens.add-new.new-chat.db :as db] @@ -14,6 +13,7 @@ [status-im.chat.models :as chat] [status-im.i18n :as i18n] [status-im.contact.core :as contact] + [status-im.router.core :as router] [status-im.navigation :as navigation])) (defn- ens-name-parse [contact-identity] @@ -79,30 +79,30 @@ :yourself (i18n/label :t/can-not-add-yourself))) -(fx/defn qr-code-scanned - {:events [:contact/qr-code-scanned]} - [{:keys [db] :as cofx} contact-identity {:keys [new-contact?] :as opts}] - (let [public-key? (and (string? contact-identity) - (string/starts-with? contact-identity "0x")) - validation-result (db/validate-pub-key db contact-identity)] - (cond - (and public-key? (not (some? validation-result))) +(fx/defn qr-code-handled + {:events [::qr-code-handled]} + [{:keys [db] :as cofx} {:keys [type public-key chat-id data]} {:keys [new-contact?] :as opts}] + (let [public-key? (and (string? data) + (string/starts-with? data "0x")) + chat-key (cond + (= type :private-chat) chat-id + (= type :contact) public-key + (and (= type :undefined) + public-key?) data) + validation-result (db/validate-pub-key db chat-key)] + (if-not validation-result (if new-contact? (fx/merge cofx - (contact/add-contact contact-identity) + (contact/add-contact chat-key) (navigation/navigate-to-cofx :contacts-list {})) - (chat/start-chat cofx contact-identity)) - - (and (string? contact-identity) (ul/match-url contact-identity ul/profile-regex)) - (qr-code-scanned cofx (ul/match-url contact-identity ul/profile-regex) opts) - - (and (not public-key?) (string? contact-identity)) - (let [chain (ethereum/chain-keyword db)] - {:resolve-public-key {:chain chain - :contact-identity contact-identity - :cb #(re-frame/dispatch [:contact/qr-code-scanned % opts])}}) - - :else + (chat/start-chat cofx chat-key)) {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) :content (get-validation-label validation-result) :on-dismiss #(re-frame/dispatch [:navigate-to :home])}}))) + +(fx/defn qr-code-scanned + {:events [:contact/qr-code-scanned]} + [{:keys [db]} data opts] + {::router/handle-uri {:chain (ethereum/chain-keyword db) + :uri data + :cb #(re-frame/dispatch [::qr-code-handled % opts])}}) diff --git a/src/status_im/ui/screens/add_new/new_chat/views.cljs b/src/status_im/ui/screens/add_new/new_chat/views.cljs index fee8d40f00e..1185140972d 100644 --- a/src/status_im/ui/screens/add_new/new_chat/views.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/views.cljs @@ -7,6 +7,7 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [quo.core :as quo] [status-im.utils.gfycat.core :as gfycat] + [status-im.qr-scanner.core :as qr-scanner] [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.components.topbar :as topbar] @@ -67,7 +68,7 @@ :modal? true :accessories [{:icon :qr :accessibility-label :scan-contact-code-button - :handler #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed + :handler #(re-frame/dispatch [::qr-scanner/scan-code {:title (i18n/label :t/new-contact) :handler :contact/qr-code-scanned}])}]}] [react/view {:flex-direction :row @@ -115,7 +116,7 @@ :modal? true :accessories [{:icon :qr :accessibility-label :scan-contact-code-button - :handler #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed + :handler #(re-frame/dispatch [::qr-scanner/scan-code {:title (i18n/label :t/new-contact) :handler :contact/qr-code-scanned :new-contact? true}])}]}] diff --git a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs index 3f7e2a6f8b4..d623bcff7c6 100644 --- a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs +++ b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs @@ -5,6 +5,7 @@ [status-im.ui.components.react :as react] [status-im.ui.components.toolbar :as toolbar] [quo.core :as quo] + [status-im.qr-scanner.core :as qr-scanner] [status-im.ui.components.topbar :as topbar] [status-im.ui.screens.bootnodes-settings.edit-bootnode.styles :as @@ -55,7 +56,7 @@ {:format (i18n/label :t/bootnode-format)})) :bottom-value 0 :after {:icon :main-icons/qr - :on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed + :on-press #(re-frame/dispatch [::qr-scanner/scan-code {:title (i18n/label :t/add-bootnode) :handler :bootnodes.callback/qr-code-scanned}])}})]] (when id diff --git a/src/status_im/ui/screens/home/sheet/views.cljs b/src/status_im/ui/screens/home/sheet/views.cljs index 1f0dc187cb1..23b7458d046 100644 --- a/src/status_im/ui/screens/home/sheet/views.cljs +++ b/src/status_im/ui/screens/home/sheet/views.cljs @@ -1,6 +1,7 @@ (ns status-im.ui.screens.home.sheet.views (:require [re-frame.core :as re-frame] [status-im.ui.components.react :as react] + [status-im.qr-scanner.core :as qr-scanner] [quo.core :as quo] [status-im.i18n :as i18n] [status-im.utils.config :as config] @@ -12,6 +13,20 @@ (defn add-new-view [] [react/view + [react/view {:style {:flex-direction :row + :padding-left 16 + :padding-right 8 + :justify-content :space-between + :align-items :center}} + [quo/text {:size :large + :weight :bold} + (i18n/label :t/open-home)] + [quo/button {:type :icon + :theme :icon + :on-press #(hide-sheet-and-dispatch + [::qr-scanner/scan-code + {:handler ::qr-scanner/on-scan-success}])} + :main-icons/qr]] [quo/list-item {:theme :accent :title (i18n/label :t/start-new-chat) diff --git a/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/views.cljs b/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/views.cljs index d5c1821746e..e8bb011eee8 100644 --- a/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/views.cljs +++ b/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/views.cljs @@ -6,6 +6,7 @@ [status-im.ui.components.toolbar :as toolbar] [status-im.ui.screens.offline-messaging-settings.edit-mailserver.styles :as styles] [clojure.string :as string] + [status-im.qr-scanner.core :as qr-scanner] [quo.core :as quo] [status-im.ui.components.topbar :as topbar])) @@ -61,7 +62,7 @@ (i18n/label :t/invalid-format {:format (i18n/label :t/mailserver-format)})) :after {:icon :main-icons/qr - :on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed + :on-press #(re-frame/dispatch [::qr-scanner/scan-code {:title (i18n/label :t/add-mailserver) :handler :mailserver.callback/qr-code-scanned}])}}]] (when (and id diff --git a/src/status_im/ui/screens/profile/contact/views.cljs b/src/status_im/ui/screens/profile/contact/views.cljs index a0fe9e0ebd7..dc8b5ca61b1 100644 --- a/src/status_im/ui/screens/profile/contact/views.cljs +++ b/src/status_im/ui/screens/profile/contact/views.cljs @@ -113,6 +113,7 @@ [react/view {:padding-top 12} (for [{:keys [label subtext accessibility-label icon action disabled?]} (actions contact)] + ^{:key label} (when label [quo/list-item {:theme :accent :title label diff --git a/src/status_im/ui/screens/wallet/accounts/views.cljs b/src/status_im/ui/screens/wallet/accounts/views.cljs index 4aedabc29b2..4fda567394b 100644 --- a/src/status_im/ui/screens/wallet/accounts/views.cljs +++ b/src/status_im/ui/screens/wallet/accounts/views.cljs @@ -10,7 +10,7 @@ [status-im.ui.components.react :as react] [status-im.ui.screens.wallet.accounts.sheets :as sheets] [status-im.ui.screens.wallet.accounts.styles :as styles] - [status-im.utils.utils :as utils.utils] + [status-im.qr-scanner.core :as qr-scanner] [status-im.wallet.utils :as wallet.utils] [status-im.keycard.login :as keycard.login]) (:require-macros [status-im.utils.views :as views])) @@ -91,20 +91,6 @@ :key-fn :name :render-fn (render-asset (:code currency))}])) -(defn- request-camera-permissions [] - (let [options {:handler :wallet.send/qr-scanner-result}] - (re-frame/dispatch - [:request-permissions - {:permissions [:camera] - :on-allowed - #(re-frame/dispatch [:wallet.send/qr-scanner-allowed options]) - :on-denied - #(utils.utils/set-timeout - (fn [] - (utils.utils/show-popup (i18n/label :t/error) - (i18n/label :t/camera-access-error))) - 50)}]))) - (views/defview send-button [] (views/letsubs [account [:multiaccount/default-account]] [react/view styles/send-button-container @@ -187,7 +173,9 @@ [quo/animated-header {:extended-header total-value :use-insets true - :right-accessories [{:on-press #(request-camera-permissions) + :right-accessories [{:on-press #(re-frame/dispatch + [::qr-scanner/scan-code + {:handler :wallet.send/qr-scanner-result}]) :icon :main-icons/qr :accessibility-label :accounts-qr-code} {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet diff --git a/src/status_im/ui/screens/wallet/add_new/views.cljs b/src/status_im/ui/screens/wallet/add_new/views.cljs index b55e1c3757a..566e8b32a64 100644 --- a/src/status_im/ui/screens/wallet/add_new/views.cljs +++ b/src/status_im/ui/screens/wallet/add_new/views.cljs @@ -10,7 +10,6 @@ [status-im.multiaccounts.db :as multiaccounts.db] [status-im.ui.components.toolbar :as toolbar] [status-im.ui.components.topbar :as topbar] - [status-im.utils.utils :as utils.utils] [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.screens.wallet.account-settings.views :as account-settings] [status-im.ethereum.core :as ethereum] @@ -19,32 +18,19 @@ [quo.core :as quo] [status-im.ui.components.bottom-panel.views :as bottom-panel])) -(defn- request-camera-permissions [] - (let [options {:handler :wallet.add-new/qr-scanner-result}] - (re-frame/dispatch - [:request-permissions - {:permissions [:camera] - :on-allowed - #(re-frame/dispatch [:wallet.add-new/qr-scanner-allowed options]) - :on-denied - #(utils.utils/set-timeout - (fn [] - (utils.utils/show-popup (i18n/label :t/error) - (i18n/label :t/camera-access-error))) - 50)}]))) - (defn add-account-topbar [type] (let [title (case type :generate :t/generate-an-account - :watch :t/add-watch-account - :seed :t/add-seed-account - :key :t/add-private-key-account + :watch :t/add-watch-account + :seed :t/add-seed-account + :key :t/add-private-key-account "")] [topbar/topbar (merge {:title title} (when (= type :watch) {:accessories [{:icon :qr - :handler #(request-camera-permissions)}]}))])) + :handler #(re-frame/dispatch [:wallet.add-new/qr-scanner + {:handler :wallet.add-new/qr-scanner-result}])}]}))])) (defn common-settings [account] [react/view {:margin-horizontal 16} diff --git a/src/status_im/ui/screens/wallet/send/sheets.cljs b/src/status_im/ui/screens/wallet/send/sheets.cljs index b284b5e4874..c97a006cf2c 100644 --- a/src/status_im/ui/screens/wallet/send/sheets.cljs +++ b/src/status_im/ui/screens/wallet/send/sheets.cljs @@ -6,8 +6,7 @@ [quo.core :as quo] [status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.ui.components.list.views :as list] - [status-im.ui.screens.wallet.accounts.views :as wallet.accounts] - [status-im.utils.utils :as utils.utils])) + [status-im.ui.screens.wallet.accounts.views :as wallet.accounts])) (views/defview assets [address] (views/letsubs [{:keys [tokens]} [:wallet/visible-assets-with-values address] @@ -34,21 +33,6 @@ :key-fn :address :render-fn (render-account field event)}])) -(defn- request-camera-permissions [] - (let [options {:handler :wallet.send/qr-scanner-result - :cancel-handler :wallet.send/qr-scanner-cancel - :modal-opened? true}] - (re-frame/dispatch - [:request-permissions - {:permissions [:camera] - :on-allowed #(re-frame/dispatch [:wallet.send/qr-scanner-allowed options]) - :on-denied - #(utils.utils/set-timeout - (fn [] - (utils.utils/show-popup (i18n/label :t/error) - (i18n/label :t/camera-access-error))) - 50)}]))) - (defn show-accounts-list [] (re-frame/dispatch [:bottom-sheet/hide]) (js/setTimeout #(re-frame/dispatch [:bottom-sheet/show-sheet @@ -66,7 +50,9 @@ :icon :main-icons/qr :theme :accent :accessibility-label :chose-recipient-scan-qr - :on-press request-camera-permissions} + :on-press #(re-frame/dispatch [:wallet.send/qr-scanner {:handler :wallet.send/qr-scanner-result + :cancel-handler :wallet.send/qr-scanner-cancel + :modal-opened? true}])} {:title (i18n/label :t/recipient-code) :icon :main-icons/address :theme :accent diff --git a/src/status_im/utils/universal_links/core.cljs b/src/status_im/utils/universal_links/core.cljs index c33d9e445f3..6bfdd42188b 100644 --- a/src/status_im/utils/universal_links/core.cljs +++ b/src/status_im/utils/universal_links/core.cljs @@ -1,14 +1,12 @@ (ns status-im.utils.universal-links.core - (:require [cljs.spec.alpha :as spec] - [clojure.string :as string] - [goog.string :as gstring] + (:require [goog.string :as gstring] [re-frame.core :as re-frame] [status-im.multiaccounts.model :as multiaccounts.model] [status-im.chat.models :as chat] [status-im.constants :as constants] - [status-im.ethereum.ens :as ens] + [status-im.router.core :as router] + [status-im.i18n :as i18n] [status-im.ethereum.core :as ethereum] - [status-im.utils.security :as security] [status-im.ui.components.react :as react] [status-im.ui.screens.add-new.new-chat.db :as new-chat.db] [status-im.navigation :as navigation] @@ -20,12 +18,6 @@ ;; TODO(yenda) investigate why `handle-universal-link` event is ;; dispatched 7 times for the same link -(def private-chat-regex #".*/chat/private/(.*)$") -(def public-chat-regex #"(?:https?://join\.)?status[.-]im(?::/)?/(?:chat/public/([a-z0-9\-]+)$|([a-z0-9\-]+))$") -(def profile-regex #"(?:https?://join\.)?status[.-]im(?::/)?/(?:u/(0x.*)$|u/(.*)$|user/(.*))$") -(def browse-regex #"(?:https?://join\.)?status[.-]im(?::/)?/(?:b/(.*)$|browse/(.*))$") -(def referral-link-regex #"(?:https?://join\.)?status[.-]im(?::/)?/(?:referral/(.*))$") - ;; domains should be without the trailing slash (def domains {:external "https://join.status.im" :internal "status-im:/"}) @@ -40,17 +32,6 @@ (get domains domain-type) param)) -(defn match-url [url regex] - (some->> url - (re-matches regex) - rest - reverse - (remove nil?) - first)) - -(defn is-request-url? [url] - (string/starts-with? url "ethereum:")) - (defn universal-link? [url] (boolean (re-matches constants/regx-universal-link url))) @@ -59,52 +40,45 @@ (boolean (re-matches constants/regx-deep-link url))) -(fx/defn handle-browse [cofx url] +(fx/defn handle-browse [cofx {:keys [url]}] (log/info "universal-links: handling browse" url) - (when (security/safe-link? url) - {:browser/show-browser-selection url})) + {:browser/show-browser-selection url}) -(fx/defn handle-private-chat [cofx chat-id] +(fx/defn handle-private-chat [{:keys [db] :as cofx} {:keys [chat-id]}] (log/info "universal-links: handling private chat" chat-id) - (chat/start-chat cofx chat-id)) + (when chat-id + (if-not (new-chat.db/own-public-key? db chat-id) + (chat/start-chat cofx chat-id) + {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) + :content (i18n/label :t/can-not-add-yourself)}}))) -(fx/defn handle-public-chat [cofx public-chat] - (log/info "universal-links: handling public chat" public-chat) - (chat/start-public-chat cofx public-chat {})) +(fx/defn handle-public-chat [cofx {:keys [topic]}] + (log/info "universal-links: handling public chat" topic) + (when (seq topic) + (chat/start-public-chat cofx topic {}))) (fx/defn handle-view-profile - [{:keys [db] :as cofx} {:keys [public-key ens-name]}] - (log/info "universal-links: handling view profile" (or ens-name public-key)) + [{:keys [db] :as cofx} {:keys [public-key]}] + (log/info "universal-links: handling view profile" public-key) (cond (and public-key (new-chat.db/own-public-key? db public-key)) - (navigation/navigate-to-cofx cofx :my-profile nil) + (navigation/navigate-to-cofx cofx :tabs {:screen :profile-stack}) public-key - (navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] public-key) :profile nil) - - ens-name - (let [chain (ethereum/chain-keyword db)] - {:resolve-public-key {:chain chain - :contact-identity ens-name - :cb (fn [pub-key] - (cond - (and pub-key (new-chat.db/own-public-key? db pub-key)) - (re-frame/dispatch [:navigate-to :my-profile]) - - pub-key - (re-frame/dispatch [:chat.ui/show-profile pub-key]) - - :else - (log/info "universal-link: no pub-key for ens-name " ens-name)))}}))) + (navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] public-key) + :tabs + {:screen :chat-stack + :params {:screen :profile}}))) -(fx/defn handle-referrer-url [_ referrer] - {::acquisition/check-referrer referrer}) - -(fx/defn handle-eip681 [cofx url] +(fx/defn handle-eip681 [cofx data] (fx/merge cofx - (choose-recipient/parse-eip681-uri-and-resolve-ens url) + (choose-recipient/parse-eip681-uri-and-resolve-ens data) (navigation/navigate-to-cofx :wallet nil))) +(fx/defn handle-referrer-url [_ {:keys [referrer]}] + ;; TODO: Use only for testing + {::acquisition/check-referrer referrer}) + (defn handle-not-found [full-url] (log/info "universal-links: no handler for " full-url)) @@ -115,35 +89,24 @@ (re-frame/dispatch [:handle-universal-link url]) (log/debug "universal-links: no url"))) +(fx/defn on-handle + {:events [::match-value]} + [cofx url {:keys [type] :as data}] + (case type + :public-chat (handle-public-chat cofx data) + :private-chat (handle-private-chat cofx data) + :contact (handle-view-profile cofx data) + :browser (handle-browse cofx data) + :eip681 (handle-eip681 cofx data) + :referrals (handle-referrer-url cofx data) + (handle-not-found url))) + (fx/defn route-url "Match a url against a list of routes and handle accordingly" - [cofx url] - (cond - - (match-url url referral-link-regex) - (handle-referrer-url cofx (match-url url referral-link-regex)) - - (match-url url private-chat-regex) - (handle-private-chat cofx (match-url url private-chat-regex)) - - (spec/valid? :global/public-key (match-url url profile-regex)) - (handle-view-profile cofx {:public-key (match-url url profile-regex)}) - - (or (ens/valid-eth-name-prefix? (match-url url profile-regex)) - (ens/is-valid-eth-name? (match-url url profile-regex))) - (handle-view-profile cofx {:ens-name (match-url url profile-regex)}) - - (match-url url browse-regex) - (handle-browse cofx (match-url url browse-regex)) - - (is-request-url? url) - (handle-eip681 cofx url) - - ;; This needs to stay last, as it's a bit of a catch-all regex - (match-url url public-chat-regex) - (handle-public-chat cofx (match-url url public-chat-regex)) - - :else (handle-not-found url))) + [{:keys [db]} url] + {::router/handle-uri {:chain (ethereum/chain-keyword db) + :uri url + :cb #(re-frame/dispatch [::match-value url %])}}) (fx/defn store-url-for-later "Store the url in the db to be processed on login" diff --git a/src/status_im/utils/universal_links/core_test.cljs b/src/status_im/utils/universal_links/core_test.cljs index ea11fffb269..04207667a62 100644 --- a/src/status_im/utils/universal_links/core_test.cljs +++ b/src/status_im/utils/universal_links/core_test.cljs @@ -3,6 +3,7 @@ [status-im.utils.gfycat.core :as gfycat] [status-im.utils.identicon :as identicon] [re-frame.core :as re-frame] + [status-im.router.core :as router] [status-im.utils.universal-links.core :as links])) (deftest handle-url-test @@ -18,54 +19,10 @@ (testing "it clears the url" (is (nil? (get-in (links/handle-url {:db db} "some-url") [:db :universal-links/url])))) - (testing "a public chat link" - (testing "it joins the chat, short version" - (is (get-in (links/handle-url {:db db} "status-im://status") - [:db :chats "status"]))) - (testing "it joins the chat, short version, https" - (is (get-in (links/handle-url {:db db} "https://join.status.im/status") - [:db :chats "status"]))) - (testing "it joins the chat" - (is (get-in (links/handle-url {:db db} "status-im://chat/public/status") - [:db :chats "status"])))) - (testing "a browse dapp link" - (testing "it open the dapps short version" - (is - (= "www.cryptokitties.co" - (:browser/show-browser-selection (links/handle-url {:db db} "status-im://b/www.cryptokitties.co"))))) - (testing "it open the dapps short version, https" - (is - (= "www.cryptokitties.co" - (:browser/show-browser-selection (links/handle-url {:db db} "https://join.status.im/b/www.cryptokitties.co"))))) - (testing "it open the dapps" - (is - (= "www.cryptokitties.co" - (:browser/show-browser-selection (links/handle-url {:db db} "status-im://browse/www.cryptokitties.co")))))) - (testing "a user profile link" - (testing "it loads the profile, short version" - (let [actual (links/handle-url {:db db} "status-im://u/0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073")] - (is (= "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073" (get-in actual [:db :contacts/identity]))))) - (testing "it loads the profile, short version https" - (let [actual (links/handle-url {:db db} "https://join.status.im/u/0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073")] - (is (= "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073" (get-in actual [:db :contacts/identity]))))) - (testing "it loads the profile" - (let [actual (links/handle-url {:db db} "status-im://user/0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073")] - (is (= "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073" (get-in actual [:db :contacts/identity])))))) - (testing "Handle a custom string as a an profile link with ens-name" - (is (= (get-in (links/handle-url {:db db} "status-im://u/CONTACTCODE") - [:resolve-public-key :contact-identity]) - "CONTACTCODE"))) - (testing "Handle a custom string as a an profile link with ens-name, http" + (testing "Handle a custom string" (is (= (get-in (links/handle-url {:db db} "https://join.status.im/u/statuse2e") - [:resolve-public-key :contact-identity]) - "statuse2e"))) - (testing "Handle a custom string as a an profile link with ens-name" - (is (= (get-in (links/handle-url {:db db} "status-im://user/CONTACTCODE") - [:resolve-public-key :contact-identity]) - "CONTACTCODE"))) - (testing "a not found url" - (testing "it does nothing" - (is (nil? (links/handle-url {:db db} "status-im://blah/not-existing"))))))))) + [::router/handle-uri :uri]) + "https://join.status.im/u/statuse2e"))))))) (deftest url-event-listener (testing "the url is not nil" diff --git a/src/status_im/wallet/choose_recipient/core.cljs b/src/status_im/wallet/choose_recipient/core.cljs index 7c1ffb81bc3..771f775bdba 100644 --- a/src/status_im/wallet/choose_recipient/core.cljs +++ b/src/status_im/wallet/choose_recipient/core.cljs @@ -8,10 +8,14 @@ [status-im.i18n :as i18n] [status-im.utils.money :as money] [status-im.utils.fx :as fx] + [status-im.router.core :as router] + [status-im.qr-scanner.core :as qr-scaner] + [status-im.ui.components.bottom-sheet.core :as bottom-sheet] [status-im.navigation :as navigation] [clojure.string :as string] [status-im.ethereum.stateofus :as stateofus])) +;; FIXME(Ferossgp): Should be part of QR scanner not wallet (fx/defn toggle-flashlight {:events [:wallet/toggle-flashlight]} [{:keys [db]}] @@ -107,43 +111,42 @@ {:data uri :chain current-chain-id})})))) {:ui/show-error (i18n/label :t/wallet-invalid-address {:data uri})}))) +(fx/defn qr-scanner-allowed + {:events [:wallet.send/qr-scanner]} + [{:keys [db] :as cofx} options] + (fx/merge cofx + (when (:modal-opened? options) + {:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)}) + (bottom-sheet/hide-bottom-sheet) + (qr-scaner/scan-qr-code options))) + (fx/defn qr-scanner-cancel {:events [:wallet.send/qr-scanner-cancel]} [{db :db} _] {:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)}) (fx/defn parse-eip681-uri-and-resolve-ens - [{db :db :as cofx} uri] - (if-let [message (eip681/parse-uri uri)] + {:events [:wallet/parse-eip681-uri-and-resolve-ens]} + [{db :db :as cofx} {:keys [message uri paths ens-names error]}] + (if-not error ;; first we get a vector of ens-names to resolve and a vector of paths of ;; these names - (let [{:keys [paths ens-names]} - (reduce (fn [acc path] - (let [address (get-in message path)] - (if (ens/is-valid-eth-name? address) - (-> acc - (update :paths conj path) - (update :ens-names conj address)) - acc))) - {:paths [] :ens-names []} - [[:address] [:function-arguments :address]])] - (println "message" message) - (if (empty? ens-names) - ;; if there are no ens-names, we dispatch request-uri-parsed immediately - (request-uri-parsed cofx message uri) - {::resolve-addresses - {:registry (get ens/ens-registries (ethereum/chain-keyword db)) - :ens-names ens-names - :callback - (fn [addresses] - (re-frame/dispatch - [:wallet/request-uri-parsed - ;; we replace ens-names at their path in the message by their - ;; actual address - (reduce (fn [message [path address]] - (assoc-in message path address)) - message - (map vector paths addresses)) uri]))}})) + (if (empty? ens-names) + ;; if there are no ens-names, we dispatch request-uri-parsed immediately + (request-uri-parsed cofx message uri) + {::resolve-addresses + {:registry (get ens/ens-registries (ethereum/chain-keyword db)) + :ens-names ens-names + :callback + (fn [addresses] + (re-frame/dispatch + [:wallet/request-uri-parsed + ;; we replace ens-names at their path in the message by their + ;; actual address + (reduce (fn [message [path address]] + (assoc-in message path address)) + message + (map vector paths addresses)) uri]))}}) {:ui/show-error (i18n/label :t/wallet-invalid-address {:data uri})})) (fx/defn qr-scanner-result @@ -151,7 +154,7 @@ [cofx data _] (fx/merge cofx (navigation/navigate-back) - (parse-eip681-uri-and-resolve-ens data) + (parse-eip681-uri-and-resolve-ens (router/match-eip681 data)) (fn [{:keys [db]}] (when (get-in db [:wallet/prepare-transaction :modal-opened?]) {:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)})))) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 29b6d4798fc..bf517934104 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :as re-frame] [status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.constants :as constants] + [status-im.qr-scanner.core :as qr-scaner] [status-im.waku.core :as waku] [status-im.ethereum.core :as ethereum] [status-im.ethereum.eip55 :as eip55] @@ -548,21 +549,12 @@ :amount amount :from-command? true})}) -(fx/defn qr-scanner-allowed - {:events [:wallet.send/qr-scanner-allowed]} - [{:keys [db] :as cofx} options] - (fx/merge cofx - (when (:modal-opened? options) - {:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)}) - (bottom-sheet/hide-bottom-sheet) - (navigation/navigate-to-cofx :qr-scanner options))) - (fx/defn view-only-qr-scanner-allowed - {:events [:wallet.add-new/qr-scanner-allowed]} + {:events [:wallet.add-new/qr-scanner]} [{:keys [db] :as cofx} options] (fx/merge cofx {:db (update-in db [:add-account] dissoc :address)} - (navigation/navigate-to-cofx :qr-scanner options))) + (qr-scaner/scan-qr-code options))) (fx/defn wallet-send-set-symbol {:events [:wallet.send/set-symbol]} diff --git a/translations/en.json b/translations/en.json index 6b4af570962..302dbb8610c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -413,6 +413,7 @@ "ens-username-connected-with-different-key": "Continuing will require a transaction to connect the username with your current chat key.", "ens-username-owned-continue": "Continuing will connect this username with your chat key.", "ens-username-taken": "Username already taken :(", + "ens-name-not-found": "Cannot resolve ENS name", "enter-12-words": "Enter the 12 words of your seed phrase, separated by single spaces", "enter-a-private-key": "Enter a private key", "enter-a-seed-phrase": "Enter a seed phrase", @@ -785,6 +786,7 @@ "okay": "Okay", "on": "On", "open": "Open", + "open-home": "Open...", "open-dapp": "Open ÐApp", "open-dapp-store": "Discover ÐApps", "open-nfc-settings": "Open NFC settings",