Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
Julien Eluard 2018-10-15 14:55:01 +02:00
parent a6de279660
commit 5eeda7aa0b
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
13 changed files with 235 additions and 63 deletions

View File

@ -88,9 +88,9 @@
(dissoc :font) (dissoc :font)
(assoc style-key (merge style font))))) (assoc style-key (merge style font)))))
(defn transform-to-uppercase [{:keys [uppercase? force-uppercase?] :as opts} ts] (defn transform-to-uppercase [{:keys [uppercase? force-uppercase?]} ts]
(if (or force-uppercase? (and uppercase? platform/android?)) (if (or force-uppercase? (and uppercase? platform/android?))
(vec (map string/upper-case ts)) (vec (map #(when % (string/upper-case %)) ts))
ts)) ts))
(defn text (defn text

View File

@ -130,24 +130,27 @@
(def command-hook (def command-hook
"Hook for extensions" "Hook for extensions"
{:properties {:properties
{:scope #{:personal-chats :public-chats} {:description? :string
:scope #{:personal-chats :public-chats}
:short-preview :view :short-preview :view
:preview :view :preview :view
:on-send? :event
:on-receive? :event
:parameters [{:id :keyword :parameters [{:id :keyword
:type {:one-of #{:text :phone :password :number}} :type {:one-of #{:text :phone :password :number}}
:placeholder :string :placeholder :string
:suggestions :view}]} :suggestions :view}]}
:hook :hook
(reify hooks/Hook (reify hooks/Hook
(hook-in [_ id {:keys [description scope parameters preview short-preview]} cofx] (hook-in [_ id {:keys [description scope parameters preview short-preview on-send on-receive]} cofx]
(let [new-command (reify protocol/Command (let [new-command (reify protocol/Command
(id [_] (name id)) (id [_] (name id))
(scope [_] scope) (scope [_] scope)
(description [_] description) (description [_] description)
(parameters [_] parameters) (parameters [_] parameters)
(validate [_ _ _]) (validate [_ _ _])
(on-send [_ _ _]) (on-send [_ _ _] {:dispatch on-send})
(on-receive [_ _ _]) (on-receive [_ _ _] {:dispatch on-receive})
(short-preview [_ props] (short-preview props)) (short-preview [_ props] (short-preview props))
(preview [_ props] (preview props)))] (preview [_ props] (preview props)))]
(load-commands cofx [new-command]))) (load-commands cofx [new-command])))

View File

@ -131,7 +131,7 @@
(defn choose-nft-token-suggestion [selected-event-creator] (defn choose-nft-token-suggestion [selected-event-creator]
[choose-nft-token selected-event-creator]) [choose-nft-token selected-event-creator])
(defview nft-token [{:keys [name image_url] :as token}] (defview nft-token [{{:keys [name image_url]} :token}]
[react/view {:flex-direction :column [react/view {:flex-direction :column
:align-items :center} :align-items :center}
[svgimage/svgimage {:style {:width 100 [svgimage/svgimage {:style {:width 100
@ -200,6 +200,9 @@
tx-exists? :status-pending tx-exists? :status-pending
:else :status-tx-not-found))]]]])) :else :status-tx-not-found))]]]]))
(defn transaction-status [{:keys [tx-hash outgoing]}]
[send-status tx-hash outgoing])
(defview send-preview (defview send-preview
[{:keys [content timestamp-str outgoing group-chat]}] [{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]] (letsubs [network [:network-name]]

View File

@ -33,7 +33,7 @@
"Function which can provide any extra effects to be produced in addition to "Function which can provide any extra effects to be produced in addition to
normal message effects which happen whenever message is sent") normal message effects which happen whenever message is sent")
(on-receive [this command-message cofx] (on-receive [this command-message cofx]
"Function which can provide any extre effects to be produced in addition to "Function which can provide any extra effects to be produced in addition to
normal message effects which happen when particular command message is received") normal message effects which happen when particular command message is received")
(short-preview [this command-message] (short-preview [this command-message]
"Function rendering the short-preview of the command message, used when "Function rendering the short-preview of the command message, used when

View File

@ -286,7 +286,7 @@
:chat-parameter-box :chat-parameter-box
:<- [:get-current-chat] :<- [:get-current-chat]
:<- [:selected-chat-command] :<- [:selected-chat-command]
(fn [[current-chat {:keys [current-param-position params]}]] (fn [[_ {:keys [current-param-position params]}]]
(when (and params current-param-position) (when (and params current-param-position)
(get-in params [current-param-position :suggestions])))) (get-in params [current-param-position :suggestions]))))

View File

@ -1,36 +1,162 @@
(ns status-im.extensions.core (ns status-im.extensions.core
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame]
[pluto.reader :as reader] [pluto.reader :as reader]
[pluto.registry :as registry]
[pluto.storages :as storages] [pluto.storages :as storages]
[status-im.chat.commands.core :as commands] [status-im.chat.commands.core :as commands]
[status-im.chat.commands.impl.transactions :as transactions] [status-im.chat.commands.impl.transactions :as transactions]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.button.view :as button]
[status-im.utils.handlers :as handlers]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx])) [status-im.utils.fx :as fx]))
(def components (re-frame/reg-fx
{'view {:value react/view} ::alert
'text {:value react/text} (fn [value] (js/alert value)))
'nft-token {:value transactions/nft-token}
'send-status {:value transactions/send-status}
'asset-selector {:value transactions/choose-nft-asset-suggestion}
'token-selector {:value transactions/choose-nft-token-suggestion}})
(def app-hooks #{commands/command-hook}) (re-frame/reg-event-fx
:alert
(fn [_ [_ {:keys [value]}]]
{::alert value}))
(re-frame/reg-fx
::log
(fn [value] (js/console.log value)))
(re-frame/reg-event-fx
:log
(fn [_ [_ {:keys [value]}]]
{::log value}))
(re-frame/reg-sub
:store/get
(fn [db [_ {:keys [key]}]]
(get-in db [:extensions-store :collectible key])))
(handlers/register-handler-fx
:store/put
(fn [{:keys [db]} [_ {:keys [key value]}]]
{:db (assoc-in db [:extensions-store :collectible key] value)}))
(defn- append [acc k v]
(let [o (get acc k)]
(assoc acc k (conj (if (vector? o) o (vector o)) v))))
(handlers/register-handler-fx
:store/append
(fn [{:keys [db]} [_ {:keys [key value]}]]
{:db (update-in db [:extensions-store :collectible] append key value)}))
(handlers/register-handler-fx
:store/clear
(fn [{:keys [db]} [_ {:keys [key]}]]
{:db (update-in db [:extensions-store :collectible] dissoc key)}))
(re-frame/reg-event-fx
:http/get
(fn [_ [_ {:keys [url on-success on-failure timeout]}]]
{:http-get (merge {:url url
:success-event-creator (fn [o] (into on-success (vector o)))}
(when on-failure
{:failure-event-creator (fn [o] (into on-failure (vector o)))})
(when timeout
{:timeout-ms timeout}))}))
(defn button [{:keys [on-click]} label]
[button/secondary-button {:on-press #(re-frame/dispatch on-click)} label])
(defn input [{:keys [on-change placeholder]}]
[react/text-input {:on-change-text #(re-frame/dispatch on-change) :placeholder placeholder}])
(def capacities (def capacities
(reduce (fn [capacities hook] {:components {'view {:value react/view}
(assoc-in capacities [:hooks :commands] hook)) 'text {:value react/text}
{:components components 'input {:value input :properties {:on-change :event :placeholder :string}}
:queries {'get-collectible-token {:value :get-collectible-token}} 'button {:value button :properties {:on-click :event}}
:events {}} 'nft-token-viewer {:value transactions/nft-token :properties {:token :string}}
app-hooks)) 'transaction-status {:value transactions/transaction-status :properties {:outgoing :string :tx-hash :string}}
'asset-selector {:value transactions/choose-nft-asset-suggestion}
'token-selector {:value transactions/choose-nft-token-suggestion}}
:queries {'store/get {:value :store/get :arguments {:key :string}}
'get-collectible-token {:value :get-collectible-token :arguments {:token :string :symbol :string}}}
:events {'alert
{:permissions [:read]
:value :alert
:arguments {:value :string}}
'log
{:permissions [:read]
:value :log
:arguments {:value :string}}
'store/put
{:permissions [:read]
:value :store/put
:arguments {:key :string :value :string}}
'store/append
{:permissions [:read]
:value :store/append
:arguments {:key :string :value :string}}
'store/clear
{:permissions [:read]
:value :store/put
:arguments {:key :string}}
'http/get
{:permissions [:read]
:value :http/get
:arguments {:url :string
:timeout? :string
:on-success :event
:on-failure? :event}}
'browser/open {:value :browser/open :arguments {:url :string}}
'chat/open {:value :chat/open :arguments {:url :string}}
'ethereum/sign
{:arguments
{:account :string
:message :string
:on-result :event}}
'ethereum/send-raw-transaction
{:arguments {:data :string}}
'ethereum/send-transaction
{:arguments
{:from :string
:to :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:nonce? :string}}
'ethereum/new-contract
{:arguments
{:from :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:nonce? :string}}
'ethereum/call
{:arguments
{:from? :string
:to :string
:gas? :string
:gas-price? :string
:value? :string
:data? :string
:block :string}}
'ethereum/logs
{:arguments
{:from? :string
:to :string
:address :string
:topics :string
:blockhash :string}}}
:hooks {:commands commands/command-hook}})
(defn read-extension [o] (defn read-extension [{:keys [value]}]
(-> o :value first :content reader/read)) (when (seq value)
(let [{:keys [content]} (first value)]
(reader/read content))))
(defn parse [{:keys [data] :as m}] (defn parse [{:keys [data]}]
(try (try
(let [{:keys [errors] :as extension-data} (reader/parse {:capacities capacities} data)] (let [{:keys [errors] :as extension-data} (reader/parse {:capacities capacities} data)]
(when errors (when errors
@ -38,9 +164,16 @@
extension-data) extension-data)
(catch :default e (println "EXC" e)))) (catch :default e (println "EXC" e))))
(def uri-prefix "https://get.status.im/extension/")
(defn valid-uri? [s]
(boolean
(when s
(re-matches (re-pattern (str "^" uri-prefix "\\w+@\\w+")) (string/trim s)))))
(defn url->uri [s] (defn url->uri [s]
(when s (when s
(string/replace s "https://get.status.im/extension/" ""))) (string/replace s uri-prefix "")))
(defn load-from [url f] (defn load-from [url f]
(when-let [uri (url->uri url)] (when-let [uri (url->uri url)]

View File

@ -1,3 +0,0 @@
(ns status-im.ui.components.text
(:require [status-im.ui.components.react :as react]
[status-im.utils.platform :as platform]))

View File

@ -160,6 +160,7 @@
(spec/def ::extension-url (spec/nilable string?)) (spec/def ::extension-url (spec/nilable string?))
(spec/def ::staged-extension (spec/nilable any?)) (spec/def ::staged-extension (spec/nilable any?))
(spec/def ::extensions-store (spec/nilable any?))
;;;;NODE ;;;;NODE
@ -235,6 +236,7 @@
:desktop/desktop :desktop/desktop
:dimensions/window :dimensions/window
:dapps/permissions] :dapps/permissions]
:opt-un :opt-un
[::current-public-key [::current-public-key
::modal ::modal
@ -308,4 +310,5 @@
::collectibles ::collectibles
::extension-url ::extension-url
::staged-extension ::staged-extension
::extensions-store
:registry/registry])) :registry/registry]))

View File

@ -38,7 +38,7 @@
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %)) (let [on-success #(re-frame/dispatch (success-event-creator %))
on-error #(re-frame/dispatch (failure-event-creator %)) on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
opts {:valid-response? response-validator opts {:valid-response? response-validator
:timeout-ms timeout-ms}] :timeout-ms timeout-ms}]
(http/get url on-success on-error opts))) (http/get url on-success on-error opts)))

View File

@ -2,8 +2,11 @@
(:require-macros [status-im.utils.views :as views]) (:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[clojure.string :as string] [clojure.string :as string]
[status-im.extensions.core :as extensions]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
@ -13,49 +16,66 @@
[status-im.ui.components.text-input.view :as text-input] [status-im.ui.components.text-input.view :as text-input]
[status-im.ui.screens.extensions.add.styles :as styles])) [status-im.ui.screens.extensions.add.styles :as styles]))
(defn cartouche [{:keys [header]} content] (defn cartouche [{:keys [header]} content]
[react/view {:style styles/cartouche-container} [react/view {:style styles/cartouche-container}
[react/text {:style styles/cartouche-header} [react/text {:style styles/cartouche-header}
header] header]
[react/view {:style styles/cartouche-content-wrapper} [react/view {:style styles/cartouche-content-wrapper}
[react/view {:flex 1} [react/view {:flex 1}
[react/text {:style styles/text} content]]])
content]]]])
(defn hooks [{:keys [hooks]}] (defn hooks [{:keys [hooks]}]
(mapcat (fn [[hook-id values]] (mapcat (fn [[hook-id values]]
(map (fn [[id]] (map (fn [[id]]
(symbol "hook" (str (name hook-id) "." (name id)))) (str (name hook-id) "." (name id)))
values)) values))
hooks)) hooks))
(views/defview show-extension [] (views/defview show-extension []
(views/letsubs [{:keys [data errors]} [:get-staged-extension]] (views/letsubs [{:keys [data errors]} [:get-staged-extension]]
[react/view styles/screen (if data
[status-bar/status-bar] [react/view styles/screen
[react/keyboard-avoiding-view components.styles/flex [status-bar/status-bar]
[toolbar/simple-toolbar (i18n/label :t/extension)] [react/keyboard-avoiding-view components.styles/flex
[react/scroll-view {:keyboard-should-persist-taps :handled} [toolbar/simple-toolbar (i18n/label :t/extension)]
[react/view styles/wrapper [react/scroll-view {:keyboard-should-persist-taps :handled}
[cartouche {:header (i18n/label :t/identifier)} [react/view styles/wrapper
(str (get-in data ['meta :name]))] [cartouche {:header (i18n/label :t/identifier)}
[cartouche {:header (i18n/label :t/name)} [react/text {:style styles/text}
(str (get-in data ['meta :name]))] (str (get-in data ['meta :name]))]]
[cartouche {:header (i18n/label :t/description)} [cartouche {:header (i18n/label :t/name)}
(str (get-in data ['meta :description]))] [react/text {:style styles/text}
[cartouche {:header (i18n/label :t/hooks)} (str (get-in data ['meta :name]))]]
(string/join " " (hooks data))] [cartouche {:header (i18n/label :t/description)}
[cartouche {:header (i18n/label :t/permissions)} [react/text {:style styles/text}
(i18n/label :t/none)] (str (get-in data ['meta :description]))]]
[cartouche {:header (i18n/label :t/errors)} [cartouche {:header (i18n/label :t/hooks)}
(i18n/label :t/none)]]] (into [react/view] (for [hook (hooks data)]
[react/view styles/bottom-container [react/text {:style styles/text}
[react/view components.styles/flex] (str hook)]))]
[components.common/bottom-button [cartouche {:header (i18n/label :t/permissions)}
{:forward? true [react/text {:style styles/text}
:label (i18n/label :t/install) (i18n/label :t/none)]]
:disabled? (not (empty? errors)) [cartouche {:header (i18n/label :t/errors)}
:on-press #(re-frame/dispatch [:extension/install data])}]]]])) (if errors
(into [react/view] (for [error errors]
[react/text {:style styles/text}
(str (name (:pluto.reader.errors/type error)) " " (str (:pluto.reader.errors/value error)))]))
[react/text {:style styles/text}
(i18n/label :t/none)])]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/install)
:disabled? (not (empty? errors))
:on-press #(re-frame/dispatch [:extension/install data])}]]]]
[react/view styles/screen
[status-bar/status-bar]
[react/view {:flex 1}
[toolbar/simple-toolbar (i18n/label :t/extension)]
[react/view {:style {:flex 1 :justify-content :center :align-items :center}}
[react/text (i18n/label :t/invalid-extension)]]]])))
(def qr-code (def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed [react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
@ -86,5 +106,5 @@
[components.common/bottom-button [components.common/bottom-button
{:forward? true {:forward? true
:label (i18n/label :t/find) :label (i18n/label :t/find)
:disabled? (string/blank? extension-url) :disabled? (not (extensions/valid-uri? extension-url))
:on-press #(re-frame/dispatch [:extension/show extension-url])}]]]])) :on-press #(re-frame/dispatch [:extension/show (string/trim extension-url)])}]]]]))

View File

@ -0,0 +1,10 @@
(ns status-im.test.extensions.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.extensions.core :as extensions]))
(deftest valid-uri?
(is (= false (extensions/valid-uri? nil)))
(is (= false (extensions/valid-uri? "http://get.status.im/extension/ipfs")))
(is (= false (extensions/valid-uri? " http://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv ")))
(is (= true (extensions/valid-uri? " https://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv ")))
(is (= true (extensions/valid-uri? "https://get.status.im/extension/ipfs@QmWKqSanV4M4zrd55QMkvDMZEQyuHzCMHpX1Fs3dZeSExv"))))

View File

@ -3,6 +3,7 @@
[status-im.test.contacts.subs] [status-im.test.contacts.subs]
[status-im.test.data-store.chats] [status-im.test.data-store.chats]
[status-im.test.data-store.realm.core] [status-im.test.data-store.realm.core]
[status-im.test.extensions.core]
[status-im.test.browser.core] [status-im.test.browser.core]
[status-im.test.browser.permissions] [status-im.test.browser.permissions]
[status-im.test.wallet.subs] [status-im.test.wallet.subs]
@ -72,6 +73,7 @@
'status-im.test.init.core 'status-im.test.init.core
'status-im.test.data-store.chats 'status-im.test.data-store.chats
'status-im.test.data-store.realm.core 'status-im.test.data-store.realm.core
'status-im.test.extensions.core
'status-im.test.mailserver.core 'status-im.test.mailserver.core
'status-im.test.group-chats.core 'status-im.test.group-chats.core
'status-im.test.node.core 'status-im.test.node.core

View File

@ -385,6 +385,7 @@
"enter-dapp-url": "Enter a ÐApp URL", "enter-dapp-url": "Enter a ÐApp URL",
"wallet-transaction-total-fee": "Total Fee", "wallet-transaction-total-fee": "Total Fee",
"extension": "Extension", "extension": "Extension",
"invalid-extension": "Invalid extension URI",
"datetime-day": { "datetime-day": {
"one": "day", "one": "day",
"other": "days" "other": "days"