commit
677379f655
10
project.clj
10
project.clj
|
@ -3,8 +3,8 @@
|
||||||
:url "http://example.com/FIXME"
|
:url "http://example.com/FIXME"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
:dependencies [[org.clojure/clojure "1.9.0-alpha7"]
|
:dependencies [[org.clojure/clojure "1.9.0-alpha11"]
|
||||||
[org.clojure/clojurescript "1.9.76"]
|
[org.clojure/clojurescript "1.9.227"]
|
||||||
[reagent "0.5.1" :exclusions [cljsjs/react]]
|
[reagent "0.5.1" :exclusions [cljsjs/react]]
|
||||||
[re-frame "0.7.0"]
|
[re-frame "0.7.0"]
|
||||||
[prismatic/schema "1.0.4"]
|
[prismatic/schema "1.0.4"]
|
||||||
|
@ -13,7 +13,11 @@
|
||||||
[natal-shell "0.3.0"]
|
[natal-shell "0.3.0"]
|
||||||
[com.andrewmcveigh/cljs-time "0.4.0"]
|
[com.andrewmcveigh/cljs-time "0.4.0"]
|
||||||
[tailrecursion/cljs-priority-map "1.2.0"]
|
[tailrecursion/cljs-priority-map "1.2.0"]
|
||||||
[cljsjs/web3 "0.16.0-0"]]
|
[cljsjs/web3 "0.16.0-0"]
|
||||||
|
[com.taoensso/timbre "4.7.4"]
|
||||||
|
[org.clojure/test.check "0.9.0"]
|
||||||
|
[cljsjs/chance "0.7.3-0"]
|
||||||
|
[cljsjs/eccjs "0.3.1-0"]]
|
||||||
:plugins [[lein-cljsbuild "1.1.1"]
|
:plugins [[lein-cljsbuild "1.1.1"]
|
||||||
[lein-figwheel "0.5.0-2"]
|
[lein-figwheel "0.5.0-2"]
|
||||||
[lein-voom "0.1.0-20160311_203101-g259fbfc"]]
|
[lein-voom "0.1.0-20160311_203101-g259fbfc"]]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
(:require [status-im.models.accounts :as accounts-model]
|
(:require [status-im.models.accounts :as accounts-model]
|
||||||
[re-frame.core :refer [register-handler after dispatch dispatch-sync debug]]
|
[re-frame.core :refer [register-handler after dispatch dispatch-sync debug]]
|
||||||
[status-im.utils.logging :as log]
|
[status-im.utils.logging :as log]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.components.status :as status]
|
[status-im.components.status :as status]
|
||||||
[status-im.utils.types :refer [json->clj]]
|
[status-im.utils.types :refer [json->clj]]
|
||||||
[status-im.persistence.simple-kv-store :as kv]
|
[status-im.persistence.simple-kv-store :as kv]
|
||||||
|
@ -33,14 +33,17 @@
|
||||||
(storage/put kv/kv-store :password password))
|
(storage/put kv/kv-store :password password))
|
||||||
|
|
||||||
(defn account-created [result password]
|
(defn account-created [result password]
|
||||||
(let [data (json->clj result)
|
(let [data (json->clj result)
|
||||||
public-key (:pubkey data)
|
public-key (:pubkey data)
|
||||||
address (:address data)
|
address (:address data)
|
||||||
mnemonic (:mnemonic data)
|
mnemonic (:mnemonic data)
|
||||||
account {:public-key public-key
|
{:keys [public private]} (protocol/new-keypair!)
|
||||||
:address address
|
account {:public-key public-key
|
||||||
:name address
|
:address address
|
||||||
:photo-path (identicon public-key)}]
|
:name address
|
||||||
|
:updates-public-key public
|
||||||
|
:updates-private-key private
|
||||||
|
:photo-path (identicon public-key)}]
|
||||||
(log/debug "account-created")
|
(log/debug "account-created")
|
||||||
(when (not (str/blank? public-key))
|
(when (not (str/blank? public-key))
|
||||||
(do
|
(do
|
||||||
|
@ -61,15 +64,25 @@
|
||||||
(accounts-model/save-accounts [(get accounts current-account-id)] true))
|
(accounts-model/save-accounts [(get accounts current-account-id)] true))
|
||||||
|
|
||||||
(defn send-account-update
|
(defn send-account-update
|
||||||
[{:keys [current-account-id accounts]} _]
|
[{:keys [current-account-id current-public-key web3 accounts]} _]
|
||||||
(api/send-account-update (get accounts current-account-id)))
|
(let [{:keys [name photo-path status]} (get accounts current-account-id)
|
||||||
|
{:keys [updates-public-key updates-private-key]} (accounts current-account-id)]
|
||||||
|
(protocol/broadcats-profile!
|
||||||
|
{:web3 web3
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)
|
||||||
|
:keypair {:public updates-public-key
|
||||||
|
:private updates-private-key}
|
||||||
|
:payload {:profile {:name name
|
||||||
|
:status status
|
||||||
|
:profile-image photo-path}}}})))
|
||||||
|
|
||||||
(register-handler
|
(register-handler
|
||||||
:account-update
|
:account-update
|
||||||
(-> (fn [{:keys [current-account-id accounts] :as db} [_ data]]
|
(-> (fn [{:keys [current-account-id accounts] :as db} [_ data]]
|
||||||
(let [data (assoc data :last-updated (time/now-ms))
|
(let [data (assoc data :last-updated (time/now-ms))
|
||||||
account (-> (get accounts current-account-id)
|
account (-> (get accounts current-account-id)
|
||||||
(merge data))]
|
(merge data))]
|
||||||
(assoc-in db [:accounts current-account-id] account)))
|
(assoc-in db [:accounts current-account-id] account)))
|
||||||
((after save-account-to-realm!))
|
((after save-account-to-realm!))
|
||||||
((after send-account-update))))
|
((after send-account-update))))
|
||||||
|
@ -79,7 +92,7 @@
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [{:keys [current-account-id accounts]} _]
|
(fn [{:keys [current-account-id accounts]} _]
|
||||||
(let [{:keys [last-updated]} (get accounts current-account-id)
|
(let [{:keys [last-updated]} (get accounts current-account-id)
|
||||||
now (time/now-ms)
|
now (time/now-ms)
|
||||||
needs-update? (> (- now last-updated) time/week)]
|
needs-update? (> (- now last-updated) time/week)]
|
||||||
(log/info "Need to send account-update: " needs-update?)
|
(log/info "Need to send account-update: " needs-update?)
|
||||||
(when needs-update?
|
(when needs-update?
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
[status-im.components.styles :refer [default-chat-color]]
|
[status-im.components.styles :refer [default-chat-color]]
|
||||||
[status-im.chat.suggestions :as suggestions]
|
[status-im.chat.suggestions :as suggestions]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.protocol.api :as api]
|
||||||
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.models.chats :as chats]
|
[status-im.models.chats :as chats]
|
||||||
[status-im.models.messages :as messages]
|
[status-im.models.messages :as messages]
|
||||||
[status-im.models.pending-messages :as pending-messages]
|
[status-im.models.pending-messages :as pending-messages]
|
||||||
|
@ -174,7 +175,7 @@
|
||||||
|
|
||||||
(defn init-console-chat
|
(defn init-console-chat
|
||||||
[{:keys [chats] :as db} existing-account?]
|
[{:keys [chats] :as db} existing-account?]
|
||||||
(let [chat-id "console"
|
(let [chat-id "console"
|
||||||
new-chat sign-up-service/console-chat]
|
new-chat sign-up-service/console-chat]
|
||||||
(if (chats chat-id)
|
(if (chats chat-id)
|
||||||
db
|
db
|
||||||
|
@ -264,11 +265,6 @@
|
||||||
(after #(dispatch [:load-unviewed-messages!]))
|
(after #(dispatch [:load-unviewed-messages!]))
|
||||||
((enrich initialize-chats) load-chats!))
|
((enrich initialize-chats) load-chats!))
|
||||||
|
|
||||||
(register-handler :initialize-pending-messages
|
|
||||||
(u/side-effect!
|
|
||||||
(fn [_ _]
|
|
||||||
(api/init-pending-messages (pending-messages/get-pending-messages)))))
|
|
||||||
|
|
||||||
(defmethod nav/preload-data! :chat
|
(defmethod nav/preload-data! :chat
|
||||||
[{:keys [current-chat-id] :as db} [_ _ id]]
|
[{:keys [current-chat-id] :as db} [_ _ id]]
|
||||||
(let [chat-id (or id current-chat-id)
|
(let [chat-id (or id current-chat-id)
|
||||||
|
@ -324,9 +320,9 @@
|
||||||
(dispatch [::start-chat! contact-id options navigation-type])))))
|
(dispatch [::start-chat! contact-id options navigation-type])))))
|
||||||
|
|
||||||
(register-handler :add-chat
|
(register-handler :add-chat
|
||||||
(-> prepare-chat
|
(-> prepare-chat
|
||||||
((enrich add-chat))
|
((enrich add-chat))
|
||||||
((after save-new-chat!))))
|
((after save-new-chat!))))
|
||||||
|
|
||||||
(register-handler :switch-command-suggestions!
|
(register-handler :switch-command-suggestions!
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
|
@ -335,12 +331,8 @@
|
||||||
(dispatch [:set-chat-input-text text])))))
|
(dispatch [:set-chat-input-text text])))))
|
||||||
|
|
||||||
(defn remove-chat
|
(defn remove-chat
|
||||||
[{:keys [current-chat-id] :as db} _]
|
[db [_ chat-id]]
|
||||||
(update db :chats dissoc current-chat-id))
|
(update db :chats dissoc chat-id))
|
||||||
|
|
||||||
(defn notify-about-leaving!
|
|
||||||
[{:keys [current-chat-id]} _]
|
|
||||||
(api/leave-group-chat current-chat-id))
|
|
||||||
|
|
||||||
; todo do we really need this message?
|
; todo do we really need this message?
|
||||||
(defn leaving-message!
|
(defn leaving-message!
|
||||||
|
@ -353,27 +345,47 @@
|
||||||
:content-type text-content-type}))
|
:content-type text-content-type}))
|
||||||
|
|
||||||
(defn delete-messages!
|
(defn delete-messages!
|
||||||
[{:keys [current-chat-id]} _]
|
[{:keys [current-chat-id]} [_ chat-id]]
|
||||||
(r/write :account
|
(let [id (or chat-id current-chat-id)]
|
||||||
(fn []
|
(r/write :account
|
||||||
(r/delete :account (r/get-by-field :account :message :chat-id current-chat-id)))))
|
(fn []
|
||||||
|
(r/delete :account
|
||||||
|
(r/get-by-field :account :message :chat-id id))))))
|
||||||
|
|
||||||
(defn delete-chat!
|
(defn delete-chat!
|
||||||
[{:keys [current-chat-id]} _]
|
[_ [_ chat-id]]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn [] :account
|
(fn [] :account
|
||||||
(->> (r/get-by-field :account :chat :chat-id current-chat-id)
|
(when-let [chat (->> (r/get-by-field :account :chat :chat-id chat-id)
|
||||||
(r/single)
|
(r/single))]
|
||||||
(r/delete :account)))))
|
(doto chat
|
||||||
|
(aset "is-active" false)
|
||||||
|
(aset "removed-at" (.getTime (js/Date.))))))))
|
||||||
|
|
||||||
|
(defn remove-pending-messages!
|
||||||
|
[_ [_ chat-id]]
|
||||||
|
(pending-messages/remove-all-by-chat chat-id))
|
||||||
|
|
||||||
(register-handler :leave-group-chat
|
(register-handler :leave-group-chat
|
||||||
;; todo oreder of operations tbd
|
;; todo oreder of operations tbd
|
||||||
(after (fn [_ _] (dispatch [:navigation-replace :chat-list])))
|
(after (fn [_ _] (dispatch [:navigation-replace :chat-list])))
|
||||||
|
(u/side-effect!
|
||||||
|
(fn [{:keys [web3 current-chat-id chats current-public-key]} _]
|
||||||
|
(let [{:keys [public-key private-key]} (chats current-chat-id)]
|
||||||
|
(protocol/leave-group-chat!
|
||||||
|
{:web3 web3
|
||||||
|
:group-id current-chat-id
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)}}))
|
||||||
|
(dispatch [::remove-chat current-chat-id]))))
|
||||||
|
|
||||||
|
(register-handler ::remove-chat
|
||||||
(-> remove-chat
|
(-> remove-chat
|
||||||
;; todo uncomment
|
|
||||||
;((after notify-about-leaving!))
|
|
||||||
;((after leaving-message!))
|
;((after leaving-message!))
|
||||||
((after delete-messages!))
|
((after delete-messages!))
|
||||||
|
((after remove-pending-messages!))
|
||||||
((after delete-chat!))))
|
((after delete-chat!))))
|
||||||
|
|
||||||
(defn edit-mode-handler [mode]
|
(defn edit-mode-handler [mode]
|
||||||
|
@ -405,13 +417,18 @@
|
||||||
(assoc db :layout-height h)))
|
(assoc db :layout-height h)))
|
||||||
|
|
||||||
(register-handler :send-seen!
|
(register-handler :send-seen!
|
||||||
(after (fn [_ [_ chat-id message-id]]
|
(after (fn [_ [_ options]]
|
||||||
(dispatch [:message-seen {:message-id message-id
|
(dispatch [:message-seen options])))
|
||||||
:chat-id chat-id}])))
|
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ chat-id message-id]]
|
(fn [{:keys [web3 current-public-key chats]}
|
||||||
|
[_ {:keys [from chat-id message-id]}]]
|
||||||
(when-not (console? chat-id)
|
(when-not (console? chat-id)
|
||||||
(api/send-seen chat-id message-id)))))
|
(let [{:keys [group-chat]} (chats chat-id)]
|
||||||
|
(protocol/send-seen! {:web3 web3
|
||||||
|
:message {:from current-public-key
|
||||||
|
:to from
|
||||||
|
:group-id (when group-chat chat-id)
|
||||||
|
:message-id message-id}}))))))
|
||||||
|
|
||||||
(register-handler :set-web-view-url
|
(register-handler :set-web-view-url
|
||||||
(fn [{:keys [current-chat-id] :as db} [_ url]]
|
(fn [{:keys [current-chat-id] :as db} [_ url]]
|
||||||
|
|
|
@ -27,23 +27,35 @@
|
||||||
(defn receive-message
|
(defn receive-message
|
||||||
[db [_ {:keys [from group-id chat-id message-id] :as message}]]
|
[db [_ {:keys [from group-id chat-id message-id] :as message}]]
|
||||||
(let [same-message (messages/get-message message-id)
|
(let [same-message (messages/get-message message-id)
|
||||||
current-identity (get-current-identity db)]
|
current-identity (get-current-identity db)
|
||||||
(when-not (or same-message (= from current-identity))
|
chat-id' (or group-id chat-id from)
|
||||||
|
exists? (c/chat-exists? chat-id')
|
||||||
|
active? (c/is-active? chat-id')]
|
||||||
|
(when (and (not same-message)
|
||||||
|
(not= from current-identity)
|
||||||
|
(or (not exists?) active?))
|
||||||
(let [group-chat? (not (nil? group-id))
|
(let [group-chat? (not (nil? group-id))
|
||||||
chat-id' (or chat-id from)
|
|
||||||
previous-message (messages/get-last-message chat-id')
|
previous-message (messages/get-last-message chat-id')
|
||||||
message' (assoc (->> message
|
message' (assoc (->> message
|
||||||
(cu/check-author-direction previous-message)
|
(cu/check-author-direction previous-message)
|
||||||
(check-preview))
|
(check-preview))
|
||||||
:chat-id chat-id')]
|
:chat-id chat-id')]
|
||||||
(store-message message')
|
(store-message message')
|
||||||
(when-not (c/chat-exists? chat-id')
|
(when-not exists?
|
||||||
(dispatch [:add-chat chat-id' (when group-chat? {:group-chat true})]))
|
(dispatch [:add-chat chat-id' (when group-chat? {:group-chat true})]))
|
||||||
(dispatch [::add-message message'])
|
(dispatch [::add-message message'])
|
||||||
(when (= (:content-type message') content-type-command-request)
|
(when (= (:content-type message') content-type-command-request)
|
||||||
(dispatch [:add-request chat-id' message']))
|
(dispatch [:add-request chat-id' message']))
|
||||||
(dispatch [:add-unviewed-message chat-id' message-id])))))
|
(dispatch [:add-unviewed-message chat-id' message-id])))))
|
||||||
|
|
||||||
|
(register-handler :received-protocol-message!
|
||||||
|
(u/side-effect!
|
||||||
|
(fn [_ [_ {:keys [from to payload]}]]
|
||||||
|
(dispatch [:received-message (merge payload
|
||||||
|
{:from from
|
||||||
|
:to to
|
||||||
|
:chat-id from})]))))
|
||||||
|
|
||||||
(register-handler :received-message
|
(register-handler :received-message
|
||||||
(u/side-effect! receive-message))
|
(u/side-effect! receive-message))
|
||||||
|
|
||||||
|
|
|
@ -5,34 +5,15 @@
|
||||||
[status-im.components.status :as status]
|
[status-im.components.status :as status]
|
||||||
[status-im.utils.random :as random]
|
[status-im.utils.random :as random]
|
||||||
[status-im.utils.datetime :as time]
|
[status-im.utils.datetime :as time]
|
||||||
[re-frame.core :refer [enrich after debug dispatch path]]
|
[re-frame.core :refer [enrich after dispatch path]]
|
||||||
[status-im.chat.suggestions :as suggestions]
|
|
||||||
[status-im.models.commands :as commands]
|
|
||||||
[status-im.chat.utils :as cu]
|
[status-im.chat.utils :as cu]
|
||||||
[status-im.constants :refer [text-content-type
|
[status-im.constants :refer [text-content-type
|
||||||
content-type-command
|
content-type-command
|
||||||
content-type-command-request
|
content-type-command-request
|
||||||
default-number-of-messages]]
|
default-number-of-messages]]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.utils.datetime :as datetime]
|
||||||
[status-im.utils.logging :as log]))
|
[status-im.protocol.core :as protocol]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
(defn prepare-message
|
|
||||||
[{:keys [identity current-chat-id] :as db} _]
|
|
||||||
(let [text (get-in db [:chats current-chat-id :input-text])
|
|
||||||
[command] (suggestions/check-suggestion db (str text " "))
|
|
||||||
message (cu/check-author-direction
|
|
||||||
db current-chat-id
|
|
||||||
{:message-id (random/id)
|
|
||||||
:chat-id current-chat-id
|
|
||||||
:content text
|
|
||||||
:to current-chat-id
|
|
||||||
:from identity
|
|
||||||
:content-type text-content-type
|
|
||||||
:outgoing true
|
|
||||||
:timestamp (time/now-ms)})]
|
|
||||||
(if command
|
|
||||||
(commands/set-command-input db :commands command)
|
|
||||||
(assoc db :new-message (when-not (s/blank? text) message)))))
|
|
||||||
|
|
||||||
(defn prepare-command
|
(defn prepare-command
|
||||||
[identity chat-id {:keys [preview preview-string content command to-message]}]
|
[identity chat-id {:keys [preview preview-string content command to-message]}]
|
||||||
|
@ -52,13 +33,13 @@
|
||||||
|
|
||||||
(register-handler :send-chat-message
|
(register-handler :send-chat-message
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [{:keys [current-chat-id identity current-account-id] :as db}]
|
(fn [{:keys [current-chat-id current-public-key current-account-id] :as db}]
|
||||||
(let [staged-commands (vals (get-in db [:chats current-chat-id :staged-commands]))
|
(let [staged-commands (vals (get-in db [:chats current-chat-id :staged-commands]))
|
||||||
text (get-in db [:chats current-chat-id :input-text])
|
text (get-in db [:chats current-chat-id :input-text])
|
||||||
data {:commands staged-commands
|
data {:commands staged-commands
|
||||||
:message text
|
:message text
|
||||||
:chat-id current-chat-id
|
:chat-id current-chat-id
|
||||||
:identity identity
|
:identity current-public-key
|
||||||
:address current-account-id}]
|
:address current-account-id}]
|
||||||
(dispatch [:clear-input current-chat-id])
|
(dispatch [:clear-input current-chat-id])
|
||||||
(cond
|
(cond
|
||||||
|
@ -88,9 +69,9 @@
|
||||||
|
|
||||||
(register-handler :prepare-command!
|
(register-handler :prepare-command!
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [db [_ {:keys [chat-id command identity] :as params}]]
|
(fn [{:keys [current-public-key] :as db} [_ {:keys [chat-id command] :as params}]]
|
||||||
(let [command' (->> command
|
(let [command' (->> command
|
||||||
(prepare-command identity chat-id)
|
(prepare-command current-public-key chat-id)
|
||||||
(cu/check-author-direction db chat-id))]
|
(cu/check-author-direction db chat-id))]
|
||||||
(dispatch [::clear-command chat-id (:id command)])
|
(dispatch [::clear-command chat-id (:id command)])
|
||||||
(dispatch [::send-command! (assoc params :command command')])))))
|
(dispatch [::send-command! (assoc params :command command')])))))
|
||||||
|
@ -115,7 +96,7 @@
|
||||||
|
|
||||||
(register-handler ::save-command!
|
(register-handler ::save-command!
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ {:keys [command chat-id]}]]
|
(fn [{:keys [current-public-key]} [_ {:keys [command chat-id]}]]
|
||||||
(messages/save-message
|
(messages/save-message
|
||||||
chat-id
|
chat-id
|
||||||
(dissoc command :rendered-preview :to-message :has-handler)))))
|
(dissoc command :rendered-preview :to-message :has-handler)))))
|
||||||
|
@ -177,22 +158,41 @@
|
||||||
|
|
||||||
(register-handler ::send-message!
|
(register-handler ::send-message!
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ {{:keys [message-type]
|
(fn [{:keys [web3 chats]} [_ {{:keys [message-type]
|
||||||
:as message} :message
|
:as message} :message
|
||||||
chat-id :chat-id}]]
|
chat-id :chat-id}]]
|
||||||
(when (and message (cu/not-console? chat-id))
|
(when (and message (cu/not-console? chat-id))
|
||||||
(if (= message-type :group-user-message)
|
(let [message' (select-keys message [:from :message-id])
|
||||||
(api/send-group-user-message message)
|
payload (select-keys message [:timestamp :content :content-type])
|
||||||
(api/send-user-message message))))))
|
options {:web3 web3
|
||||||
|
:message (assoc message' :payload payload)}]
|
||||||
|
(if (= message-type :group-user-message)
|
||||||
|
(let [{:keys [public-key private-key]} (chats chat-id)]
|
||||||
|
(protocol/send-group-message! (assoc options
|
||||||
|
:group-id chat-id
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key})))
|
||||||
|
(protocol/send-message! (assoc-in options
|
||||||
|
[:message :to] (:to message)))))))))
|
||||||
|
|
||||||
(register-handler ::send-command-protocol!
|
(register-handler ::send-command-protocol!
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [db [_ {:keys [chat-id command]}]]
|
(fn [{:keys [web3 current-public-key chats] :as db} [_ {:keys [chat-id command]}]]
|
||||||
(let [{:keys [content]} command]
|
(let [{:keys [content message-id]} command]
|
||||||
(when (cu/not-console? chat-id)
|
(when (cu/not-console? chat-id)
|
||||||
(let [{:keys [group-chat]} (get-in db [:chats chat-id])
|
(let [{:keys [public-key private-key]} (chats chat-id)
|
||||||
message {:content content
|
{:keys [group-chat]} (get-in db [:chats chat-id])
|
||||||
:content-type content-type-command}]
|
payload {:content content
|
||||||
|
:content-type content-type-command
|
||||||
|
:timestamp (datetime/now-ms)}
|
||||||
|
options {:web3 web3
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id message-id
|
||||||
|
:payload payload}}]
|
||||||
(if group-chat
|
(if group-chat
|
||||||
(api/send-group-user-message (assoc message :group-id chat-id))
|
(protocol/send-group-message! (assoc options
|
||||||
(api/send-user-message (assoc message :to chat-id)))))))))
|
:group-id chat-id
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}))
|
||||||
|
(protocol/send-message! (assoc-in options
|
||||||
|
[:message :to] chat-id)))))))))
|
||||||
|
|
|
@ -262,7 +262,7 @@
|
||||||
children)])}))
|
children)])}))
|
||||||
(into [view] children)))
|
(into [view] children)))
|
||||||
|
|
||||||
(defn chat-message [{:keys [outgoing message-id chat-id user-statuses]}]
|
(defn chat-message [{:keys [outgoing message-id chat-id user-statuses from]}]
|
||||||
(let [my-identity (api/my-identity)
|
(let [my-identity (api/my-identity)
|
||||||
status (subscribe [:get-in [:message-user-statuses message-id my-identity]])]
|
status (subscribe [:get-in [:message-user-statuses message-id my-identity]])]
|
||||||
(r/create-class
|
(r/create-class
|
||||||
|
@ -271,7 +271,9 @@
|
||||||
(when (and (not outgoing)
|
(when (and (not outgoing)
|
||||||
(not= :seen (keyword @status))
|
(not= :seen (keyword @status))
|
||||||
(not= :seen (keyword (get-in user-statuses [my-identity :status]))))
|
(not= :seen (keyword (get-in user-statuses [my-identity :status]))))
|
||||||
(dispatch [:send-seen! chat-id message-id])))
|
(dispatch [:send-seen! {:chat-id chat-id
|
||||||
|
:from from
|
||||||
|
:message-id message-id}])))
|
||||||
:reagent-render
|
:reagent-render
|
||||||
(fn [{:keys [outgoing timestamp new-day group-chat] :as message}]
|
(fn [{:keys [outgoing timestamp new-day group-chat] :as message}]
|
||||||
[message-container message
|
[message-container message
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
[status-im.models.contacts :as contacts]
|
[status-im.models.contacts :as contacts]
|
||||||
[status-im.utils.crypt :refer [encrypt]]
|
[status-im.utils.crypt :refer [encrypt]]
|
||||||
[clojure.string :as s]
|
[clojure.string :as s]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.utils.utils :refer [http-post]]
|
[status-im.utils.utils :refer [http-post]]
|
||||||
[status-im.utils.phone-number :refer [format-phone-number]]
|
[status-im.utils.phone-number :refer [format-phone-number]]
|
||||||
[status-im.utils.handlers :as u]
|
[status-im.utils.handlers :as u]
|
||||||
[status-im.utils.utils :refer [require]]
|
[status-im.utils.utils :refer [require]]
|
||||||
[status-im.utils.logging :as log]
|
[status-im.navigation.handlers :as nav]
|
||||||
[status-im.navigation.handlers :as nav]))
|
[status-im.utils.random :as random]))
|
||||||
|
|
||||||
|
|
||||||
(defmethod nav/preload-data! :group-contacts
|
(defmethod nav/preload-data! :group-contacts
|
||||||
|
@ -32,15 +32,30 @@
|
||||||
(contacts/save-contacts [contact]))
|
(contacts/save-contacts [contact]))
|
||||||
|
|
||||||
(defn watch-contact
|
(defn watch-contact
|
||||||
[_ [_ {:keys [whisper-identity]}]]
|
[{:keys [web3]} [_ {:keys [whisper-identity public-key private-key]}]]
|
||||||
(api/watch-user whisper-identity))
|
(when (and public-key private-key)
|
||||||
|
(protocol/watch-user! {:web3 web3
|
||||||
|
:identity whisper-identity
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}
|
||||||
|
:callback #(dispatch [:incoming-message %1 %2])})))
|
||||||
|
|
||||||
(register-handler :watch-contact (u/side-effect! watch-contact))
|
(register-handler :watch-contact (u/side-effect! watch-contact))
|
||||||
|
|
||||||
(defn send-contact-request
|
(defn send-contact-request
|
||||||
[{:keys [current-account-id accounts]} [_ contact]]
|
[{:keys [current-public-key web3 current-account-id accounts]} [_ contact]]
|
||||||
(let [account (get accounts current-account-id)]
|
(let [{:keys [whisper-identity]} contact
|
||||||
(api/send-contact-request contact account)))
|
{:keys [name photo-path updates-public-key updates-private-key]} (get accounts current-account-id)]
|
||||||
|
(protocol/contact-request!
|
||||||
|
{:web3 web3
|
||||||
|
:message {:from current-public-key
|
||||||
|
:to whisper-identity
|
||||||
|
:message-id (random/id)
|
||||||
|
:payload {:contact {:name name
|
||||||
|
:profile-image photo-path
|
||||||
|
:address current-account-id}
|
||||||
|
:keypair {:public updates-public-key
|
||||||
|
:private updates-private-key}}}})))
|
||||||
|
|
||||||
(register-handler :update-contact!
|
(register-handler :update-contact!
|
||||||
(-> (fn [db [_ {:keys [whisper-identity] :as contact}]]
|
(-> (fn [db [_ {:keys [whisper-identity] :as contact}]]
|
||||||
|
@ -107,7 +122,7 @@
|
||||||
|
|
||||||
(defn request-stored-contacts [contacts]
|
(defn request-stored-contacts [contacts]
|
||||||
(let [contacts-by-hash (get-contacts-by-hash contacts)
|
(let [contacts-by-hash (get-contacts-by-hash contacts)
|
||||||
data (or (keys contacts-by-hash) ())]
|
data (or (keys contacts-by-hash) ())]
|
||||||
(http-post "get-contacts" {:phone-number-hashes data}
|
(http-post "get-contacts" {:phone-number-hashes data}
|
||||||
(fn [{:keys [contacts]}]
|
(fn [{:keys [contacts]}]
|
||||||
(let [contacts' (add-identity contacts-by-hash contacts)]
|
(let [contacts' (add-identity contacts-by-hash contacts)]
|
||||||
|
@ -131,7 +146,7 @@
|
||||||
|
|
||||||
(defn add-new-contacts
|
(defn add-new-contacts
|
||||||
[{:keys [contacts] :as db} [_ new-contacts]]
|
[{:keys [contacts] :as db} [_ new-contacts]]
|
||||||
(let [identities (set (map :whisper-identity contacts))
|
(let [identities (set (map :whisper-identity contacts))
|
||||||
new-contacts' (->> new-contacts
|
new-contacts' (->> new-contacts
|
||||||
(map #(update-pending-status contacts %))
|
(map #(update-pending-status contacts %))
|
||||||
(remove #(identities (:whisper-identity %)))
|
(remove #(identities (:whisper-identity %)))
|
||||||
|
@ -159,8 +174,7 @@
|
||||||
(register-handler ::prepare-contact
|
(register-handler ::prepare-contact
|
||||||
(-> add-new-contact
|
(-> add-new-contact
|
||||||
((after save-contact))
|
((after save-contact))
|
||||||
((after send-contact-request))
|
((after send-contact-request))))
|
||||||
((after watch-contact))))
|
|
||||||
|
|
||||||
(register-handler ::update-pending-contact
|
(register-handler ::update-pending-contact
|
||||||
(-> add-new-contact
|
(-> add-new-contact
|
||||||
|
@ -168,9 +182,21 @@
|
||||||
|
|
||||||
(register-handler :add-pending-contact
|
(register-handler :add-pending-contact
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ {:keys [whisper-identity] :as contact}]]
|
(fn [{:keys [current-public-key web3 current-account-id accounts]}
|
||||||
(let [contact (assoc contact :pending false)]
|
[_ {:keys [whisper-identity] :as contact}]]
|
||||||
(api/send-discovery-keypair whisper-identity)
|
(let [contact (assoc contact :pending false)
|
||||||
|
{:keys [name photo-path updates-public-key updates-private-key]}
|
||||||
|
(accounts current-account-id)]
|
||||||
|
(protocol/contact-request!
|
||||||
|
{:web3 web3
|
||||||
|
:message {:from current-public-key
|
||||||
|
:to whisper-identity
|
||||||
|
:message-id (random/id)
|
||||||
|
:payload {:contact {:name name
|
||||||
|
:profile-image photo-path
|
||||||
|
:address current-account-id}
|
||||||
|
:keypair {:public updates-public-key
|
||||||
|
:private updates-private-key}}}})
|
||||||
(dispatch [::update-pending-contact contact])))))
|
(dispatch [::update-pending-contact contact])))))
|
||||||
|
|
||||||
(defn set-contact-identity-from-qr
|
(defn set-contact-identity-from-qr
|
||||||
|
@ -181,34 +207,28 @@
|
||||||
|
|
||||||
(register-handler :contact-update-received
|
(register-handler :contact-update-received
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
;; TODO: security issue: we should use `from` instead of `public-key` here, but for testing it is much easier to use `public-key`
|
(fn [{:keys [chats] :as db} [_ {:keys [from payload]}]]
|
||||||
(fn [db [_ from {{:keys [public-key last-updated name] :as account} :account}]]
|
(let [{:keys [content timestamp]} payload
|
||||||
(let [prev-last-updated (get-in db [:contacts public-key :last-updated])]
|
{:keys [status name profile-image]} (:profile content)
|
||||||
(if (<= prev-last-updated last-updated)
|
prev-last-updated (get-in db [:contacts from :last-updated])]
|
||||||
(let [contact (-> (assoc account :whisper-identity public-key)
|
(if (<= prev-last-updated timestamp)
|
||||||
(dissoc :public-key))]
|
(let [contact {:whisper-identity from
|
||||||
|
:name name
|
||||||
|
:photo-path profile-image
|
||||||
|
:status status
|
||||||
|
:last-updated timestamp}]
|
||||||
(dispatch [:update-contact! contact])
|
(dispatch [:update-contact! contact])
|
||||||
(dispatch [:update-chat! {:chat-id public-key
|
(when (chats from)
|
||||||
:name name}])))))))
|
(dispatch [:update-chat! {:chat-id from
|
||||||
|
:name name}]))))))))
|
||||||
|
|
||||||
(register-handler :contact-online-received
|
(register-handler :contact-online-received
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [db [_ from {last-online :at :as payload}]]
|
(fn [db [_ {:keys [from]
|
||||||
|
{:keys [timestamp]} :payload}]]
|
||||||
(let [prev-last-online (get-in db [:contacts from :last-online])]
|
(let [prev-last-online (get-in db [:contacts from :last-online])]
|
||||||
(when (< prev-last-online last-online)
|
(when (< prev-last-online timestamp)
|
||||||
(api/resend-pending-messages from)
|
(protocol/reset-pending-messages! from)
|
||||||
(dispatch [:update-contact! {:whisper-identity from
|
(dispatch [:update-contact! {:whisper-identity from
|
||||||
:last-online last-online}]))))))
|
:last-online timestamp}]))))))
|
||||||
|
|
||||||
(register-handler :contact-request-received
|
|
||||||
(u/side-effect!
|
|
||||||
(fn [_ [_ {:keys [contact from]}]]
|
|
||||||
(let [contact (assoc contact :whisper-identity from
|
|
||||||
:pending true)]
|
|
||||||
(dispatch [:add-contacts [contact]])))))
|
|
||||||
|
|
||||||
(register-handler :contact-keypair-received
|
|
||||||
(u/side-effect!
|
|
||||||
(fn [_ [_ from]]
|
|
||||||
(api/stop-watching-user from)
|
|
||||||
(api/watch-user from))))
|
|
|
@ -11,6 +11,7 @@
|
||||||
;; initial state of app-db
|
;; initial state of app-db
|
||||||
(def app-db {:identity-password "replace-me-with-user-entered-password"
|
(def app-db {:identity-password "replace-me-with-user-entered-password"
|
||||||
:identity "me"
|
:identity "me"
|
||||||
|
:current-public-key "me"
|
||||||
|
|
||||||
:accounts {}
|
:accounts {}
|
||||||
:current-account-id nil
|
:current-account-id nil
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
[status-im.utils.utils :refer [first-index]]
|
[status-im.utils.utils :refer [first-index]]
|
||||||
[status-im.utils.handlers :refer [register-handler]]
|
[status-im.utils.handlers :refer [register-handler]]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.protocol.api :as api]
|
||||||
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.navigation.handlers :as nav]
|
[status-im.navigation.handlers :as nav]
|
||||||
[status-im.discovery.model :as discoveries]
|
[status-im.discovery.model :as discoveries]
|
||||||
[status-im.utils.handlers :as u]
|
[status-im.utils.handlers :as u]
|
||||||
[status-im.utils.datetime :as time]))
|
[status-im.utils.datetime :as time]
|
||||||
|
[status-im.utils.random :as random]))
|
||||||
|
|
||||||
(register-handler :init-discoveries
|
(register-handler :init-discoveries
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
|
@ -14,8 +16,8 @@
|
||||||
(assoc :discoveries []))))
|
(assoc :discoveries []))))
|
||||||
|
|
||||||
(defn calculate-priority [{:keys [chats contacts]} from payload]
|
(defn calculate-priority [{:keys [chats contacts]} from payload]
|
||||||
(let [contact (get contacts from)
|
(let [contact (get contacts from)
|
||||||
chat (get chats from)
|
chat (get chats from)
|
||||||
seen-online-recently? (< (- (time/now-ms) (get contact :last-online))
|
seen-online-recently? (< (- (time/now-ms) (get contact :last-online))
|
||||||
time/hour)]
|
time/hour)]
|
||||||
(+ (time/now-ms) ;; message is newer => priority is higher
|
(+ (time/now-ms) ;; message is newer => priority is higher
|
||||||
|
@ -35,23 +37,37 @@
|
||||||
|
|
||||||
(register-handler :discovery-response-received
|
(register-handler :discovery-response-received
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [db [_ from payload]]
|
(fn [{:keys [current-public-key] :as db} [_ {:keys [from payload]}]]
|
||||||
(let [{:keys [message-id name photo-path status hashtags]} payload
|
(when-not (= current-public-key from)
|
||||||
discovery {:message-id message-id
|
(let [{:keys [discovery-id profile hashtags]} payload
|
||||||
:name name
|
{:keys [name profile-image status]} profile
|
||||||
:photo-path photo-path
|
discovery {:message-id discovery-id
|
||||||
:status status
|
:name name
|
||||||
:whisper-id from
|
:photo-path profile-image
|
||||||
:tags (map #(hash-map :name %) hashtags)
|
:status status
|
||||||
:last-updated (js/Date.)
|
:whisper-id from
|
||||||
:priority (calculate-priority db from payload)}]
|
:tags (map #(hash-map :name %) hashtags)
|
||||||
(dispatch [:add-discovery discovery])))))
|
:last-updated (js/Date.)
|
||||||
|
:priority (calculate-priority db from payload)}]
|
||||||
|
(dispatch [:add-discovery discovery]))))))
|
||||||
|
|
||||||
(register-handler :broadcast-status
|
(register-handler :broadcast-status
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [{:keys [current-account-id accounts]} [_ status hashtags]]
|
(fn [{:keys [current-public-key web3 current-account-id accounts]}
|
||||||
(let [account (get accounts current-account-id)]
|
[_ status hashtags]]
|
||||||
(api/broadcast-discover-status account status hashtags)))))
|
(let [{:keys [name photo-path]} (get accounts current-account-id)]
|
||||||
|
(protocol/broadcats-discoveries!
|
||||||
|
{:web3 web3
|
||||||
|
:hashtags (set hashtags)
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)
|
||||||
|
:payload {:status status
|
||||||
|
:profile {:name name
|
||||||
|
:status status
|
||||||
|
:profile-image photo-path}}}})
|
||||||
|
(protocol/watch-hashtags! {:web3 web3
|
||||||
|
:hashtags (set hashtags)
|
||||||
|
:callback #(dispatch [:incoming-message %1 %2])})))))
|
||||||
|
|
||||||
(register-handler :show-discovery-tag
|
(register-handler :show-discovery-tag
|
||||||
(fn [db [_ tag]]
|
(fn [db [_ tag]]
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
[status-im.utils.handlers :refer [register-handler]]
|
[status-im.utils.handlers :refer [register-handler]]
|
||||||
[status-im.persistence.realm.core :as r]
|
[status-im.persistence.realm.core :as r]
|
||||||
[status-im.chat.handlers :refer [delete-messages!]]
|
[status-im.chat.handlers :refer [delete-messages!]]
|
||||||
[status-im.protocol.api :as api]
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.utils.random :as random]
|
[status-im.utils.random :as random]
|
||||||
[status-im.models.contacts :as contacts]
|
[status-im.models.contacts :as contacts]
|
||||||
[status-im.models.messages :as messages]
|
[status-im.models.messages :as messages]
|
||||||
|
@ -84,16 +84,17 @@
|
||||||
[{:keys [current-chat-id selected-participants] :as db} _]
|
[{:keys [current-chat-id selected-participants] :as db} _]
|
||||||
(let [chat (get-in db [:chats current-chat-id])]
|
(let [chat (get-in db [:chats current-chat-id])]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(r/create :account
|
(r/create :account
|
||||||
:chat
|
:chat
|
||||||
(update chat :contacts remove-identities selected-participants)
|
(update chat :contacts remove-identities selected-participants)
|
||||||
true)))))
|
true)))))
|
||||||
|
|
||||||
(defn notify-about-removing!
|
(defn notify-about-removing!
|
||||||
[{:keys [current-chat-id selected-participants]} _]
|
[{:keys [current-chat-id selected-participants]} _]
|
||||||
(doseq [participant selected-participants]
|
(doseq [participant selected-participants]
|
||||||
(api/group-remove-participant current-chat-id participant)))
|
;;todo implement
|
||||||
|
))
|
||||||
|
|
||||||
(defn system-message [message-id content]
|
(defn system-message [message-id content]
|
||||||
{:from "system"
|
{:from "system"
|
||||||
|
@ -138,14 +139,33 @@
|
||||||
(chats/chat-add-participants current-chat-id selected-participants))
|
(chats/chat-add-participants current-chat-id selected-participants))
|
||||||
|
|
||||||
(defn notify-about-new-members!
|
(defn notify-about-new-members!
|
||||||
[{:keys [current-chat-id selected-participants]} _]
|
[{:keys [current-chat-id selected-participants
|
||||||
(doseq [identity selected-participants]
|
current-public-key chats web3]} _]
|
||||||
(api/group-add-participant current-chat-id identity)))
|
(let [{:keys [public-key private-key name contacts]} (chats current-chat-id)
|
||||||
|
identities (map :identity contacts)
|
||||||
|
keypair {:public public-key
|
||||||
|
:private private-key}]
|
||||||
|
(protocol/invite-to-group!
|
||||||
|
{:web3 web3
|
||||||
|
:group {:id current-chat-id
|
||||||
|
:name name
|
||||||
|
:contacts (conj identities current-public-key)
|
||||||
|
:admin current-public-key
|
||||||
|
:keypair keypair}
|
||||||
|
:identities selected-participants
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)}})
|
||||||
|
(doseq [identity selected-participants]
|
||||||
|
(protocol/add-to-group! {:web3 web3
|
||||||
|
:group-id current-chat-id
|
||||||
|
:identity identity
|
||||||
|
:keypair keypair
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)}}))))
|
||||||
|
|
||||||
(register-handler :add-new-participants
|
(register-handler :add-new-participants
|
||||||
;; todo order of operations tbd
|
;; todo order of operations tbd
|
||||||
(-> add-memebers
|
(-> add-memebers
|
||||||
((after add-members-to-realm!))
|
((after add-members-to-realm!))
|
||||||
;; todo uncomment
|
((after notify-about-new-members!))
|
||||||
;((after notify-about-new-members!))
|
|
||||||
((enrich deselect-members))))
|
((enrich deselect-members))))
|
||||||
|
|
|
@ -60,22 +60,22 @@
|
||||||
(register-handler :initialize-db
|
(register-handler :initialize-db
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
(realm/reset-account)
|
(realm/reset-account)
|
||||||
(assoc app-db :current-account-id nil
|
(assoc app-db :current-account-id nil)))
|
||||||
:current-public-key nil)))
|
|
||||||
|
|
||||||
(register-handler :initialize-account-db
|
(register-handler :initialize-account-db
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(assoc db
|
(assoc db
|
||||||
|
:chats {}
|
||||||
|
:current-chat-id "console"
|
||||||
:signed-up (storage/get kv/kv-store :signed-up)
|
:signed-up (storage/get kv/kv-store :signed-up)
|
||||||
:password (storage/get kv/kv-store :password))))
|
:password (storage/get kv/kv-store :password))))
|
||||||
|
|
||||||
(register-handler :initialize-account
|
(register-handler :initialize-account
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ address]]
|
(fn [_ [_ address]]
|
||||||
(dispatch [:initialize-protocol address])
|
|
||||||
(dispatch [:initialize-account-db])
|
(dispatch [:initialize-account-db])
|
||||||
|
(dispatch [:initialize-protocol address])
|
||||||
(dispatch [:initialize-chats])
|
(dispatch [:initialize-chats])
|
||||||
(dispatch [:initialize-pending-messages])
|
|
||||||
(dispatch [:load-contacts])
|
(dispatch [:load-contacts])
|
||||||
(dispatch [:init-chat])
|
(dispatch [:init-chat])
|
||||||
(dispatch [:init-discoveries])
|
(dispatch [:init-discoveries])
|
||||||
|
|
|
@ -46,21 +46,21 @@
|
||||||
|
|
||||||
(defn re-join-group-chat [db group-id identities group-name]
|
(defn re-join-group-chat [db group-id identities group-name]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(let [new-identities (set identities)
|
(let [new-identities (set identities)
|
||||||
only-old-contacts (->> (chat-contacts group-id)
|
only-old-contacts (->> (chat-contacts group-id)
|
||||||
(r/cljs-list)
|
(r/cljs-list)
|
||||||
(remove (fn [{:keys [identity]}]
|
(remove (fn [{:keys [identity]}]
|
||||||
(new-identities identity))))
|
(new-identities identity))))
|
||||||
contacts (->> new-identities
|
contacts (->> new-identities
|
||||||
(mapv (fn [ident]
|
(mapv (fn [ident]
|
||||||
{:identity ident}))
|
{:identity ident}))
|
||||||
(concat only-old-contacts))]
|
(concat only-old-contacts))]
|
||||||
(r/create :account :chat
|
(r/create :account :chat
|
||||||
{:chat-id group-id
|
{:chat-id group-id
|
||||||
:is-active true
|
:is-active true
|
||||||
:name group-name
|
:name group-name
|
||||||
:contacts contacts} true))))
|
:contacts contacts} true))))
|
||||||
db)
|
db)
|
||||||
|
|
||||||
(defn normalize-contacts
|
(defn normalize-contacts
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
(map #(update % :contacts vals) chats))
|
(map #(update % :contacts vals) chats))
|
||||||
|
|
||||||
(defn chats-list []
|
(defn chats-list []
|
||||||
(-> (r/get-all :account :chat)
|
(-> (r/get-by-field :account :chat :is-active true)
|
||||||
(r/sorted :timestamp :desc)
|
(r/sorted :timestamp :desc)
|
||||||
r/realm-collection->list
|
r/realm-collection->list
|
||||||
normalize-contacts))
|
normalize-contacts))
|
||||||
|
@ -89,54 +89,46 @@
|
||||||
(defn create-chat
|
(defn create-chat
|
||||||
([{:keys [last-message-id] :as chat}]
|
([{:keys [last-message-id] :as chat}]
|
||||||
(let [chat (assoc chat :last-message-id (or last-message-id ""))]
|
(let [chat (assoc chat :last-message-id (or last-message-id ""))]
|
||||||
(r/write :account #(r/create :account :chat chat true))))
|
(r/write :account #(r/create :account :chat chat true)))))
|
||||||
([db chat-id identities group-chat? chat-name]
|
|
||||||
(when-not (chat-exists? chat-id)
|
|
||||||
(let [chat-name (or chat-name
|
|
||||||
(get-chat-name chat-id identities))
|
|
||||||
_ (log/debug "creating chat" chat-name)]
|
|
||||||
(r/write :account
|
|
||||||
(fn []
|
|
||||||
(let [contacts (mapv (fn [ident]
|
|
||||||
{:identity ident}) identities)]
|
|
||||||
(r/create :account :chat
|
|
||||||
{:chat-id chat-id
|
|
||||||
:is-active true
|
|
||||||
:name chat-name
|
|
||||||
:group-chat group-chat?
|
|
||||||
:timestamp (timestamp)
|
|
||||||
:contacts contacts
|
|
||||||
:last-message-id ""}))))
|
|
||||||
(add-status-message chat-id)))))
|
|
||||||
|
|
||||||
(defn chat-add-participants [chat-id identities]
|
(defn chat-add-participants [chat-id identities]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(let [contacts (chat-contacts chat-id)]
|
(let [contacts (chat-contacts chat-id)
|
||||||
(doseq [contact-identity identities]
|
added-at (timestamp)]
|
||||||
(if-let [contact-exists (.find contacts (fn [object index collection]
|
(doseq [contact-identity identities]
|
||||||
|
(if-let [contact (.find contacts (fn [object index collection]
|
||||||
(= contact-identity (aget object "identity"))))]
|
(= contact-identity (aget object "identity"))))]
|
||||||
(aset contact-exists "is-in-chat" true)
|
(doto contact
|
||||||
(.push contacts (clj->js {:identity contact-identity})))))))
|
(aset "is-in-chat" true)
|
||||||
|
(aset "added-at" added-at))
|
||||||
|
(.push contacts (clj->js {:identity contact-identity
|
||||||
|
:added-at added-at})))))))
|
||||||
;; TODO temp. Update chat in db atom
|
;; TODO temp. Update chat in db atom
|
||||||
(dispatch [:initialize-chats]))
|
(dispatch [:initialize-chats]))
|
||||||
|
|
||||||
;; TODO deprecated? (is there need to remove multiple member at once?)
|
|
||||||
(defn chat-remove-participants [chat-id identities]
|
(defn chat-remove-participants [chat-id identities]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(let [query (include-query :identity identities)
|
(let [query (include-query :identity identities)
|
||||||
chat (r/single (r/get-by-field :account :chat :chat-id chat-id))]
|
chat (r/single (r/get-by-field :account :chat :chat-id chat-id))]
|
||||||
(-> (aget chat "contacts")
|
(-> (aget chat "contacts")
|
||||||
(r/filtered query)
|
(r/filtered query)
|
||||||
(.forEach (fn [object _ _]
|
(.forEach (fn [object _ _]
|
||||||
(aset object "is-in-chat" false))))))))
|
(r/delete :account object))))))))
|
||||||
|
|
||||||
|
(defn- groups [active?]
|
||||||
|
(r/filtered (r/get-all :account :chat)
|
||||||
|
(str "group-chat = true && is-active = "
|
||||||
|
(if active? "true" "false"))))
|
||||||
|
|
||||||
(defn active-group-chats []
|
(defn active-group-chats []
|
||||||
(let [results (r/filtered (r/get-all :account :chat)
|
(map
|
||||||
"group-chat = true && is-active = true")]
|
(fn [{:keys [chat-id public-key private-key]}]
|
||||||
(js->clj (.map results (fn [object _ _]
|
{:chat-id chat-id
|
||||||
(aget object "chat-id"))))))
|
:keypair {:private private-key
|
||||||
|
:public public-key}})
|
||||||
|
(r/realm-collection->list (groups true))))
|
||||||
|
|
||||||
(defn set-chat-active [chat-id active?]
|
(defn set-chat-active [chat-id active?]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
|
@ -144,3 +136,19 @@
|
||||||
(-> (r/get-by-field :account :chat :chat-id chat-id)
|
(-> (r/get-by-field :account :chat :chat-id chat-id)
|
||||||
(r/single)
|
(r/single)
|
||||||
(aset "is-active" active?)))))
|
(aset "is-active" active?)))))
|
||||||
|
|
||||||
|
(defn get-property [chat-id property]
|
||||||
|
(when-let [chat (r/single (r/get-by-field :account :chat :chat-id chat-id))]
|
||||||
|
(aget chat (name property))))
|
||||||
|
|
||||||
|
(defn is-active? [chat-id]
|
||||||
|
(get-property chat-id :is-active))
|
||||||
|
|
||||||
|
(defn removed-at [chat-id]
|
||||||
|
(get-property chat-id :removed-at))
|
||||||
|
|
||||||
|
(defn contact [chat-id id]
|
||||||
|
(let [contacts (r/cljs-list (chat-contacts chat-id))]
|
||||||
|
(some (fn [{:keys [identity]}]
|
||||||
|
(= id identity))
|
||||||
|
contacts)))
|
||||||
|
|
|
@ -8,32 +8,55 @@
|
||||||
[status-im.constants :as c]
|
[status-im.constants :as c]
|
||||||
[status-im.utils.types :refer [clj->json json->clj]]
|
[status-im.utils.types :refer [clj->json json->clj]]
|
||||||
[status-im.commands.utils :refer [generate-hiccup]]
|
[status-im.commands.utils :refer [generate-hiccup]]
|
||||||
[status-im.utils.logging :as log]))
|
[status-im.utils.logging :as log]
|
||||||
|
[cljs.reader :as reader]
|
||||||
|
[clojure.string :as str]))
|
||||||
|
|
||||||
(defn get-pending-messages []
|
(defn get-pending-messages! []
|
||||||
(let [collection (-> (r/get-by-fields :account :pending-message :or [[:status :sending]
|
(->> (r/get-all :account :pending-message)
|
||||||
[:status :sent]
|
r/realm-collection->list
|
||||||
[:status :failed]])
|
(map (fn [{:keys [message-id] :as message}]
|
||||||
(r/sorted :timestamp :desc)
|
(-> message
|
||||||
(r/realm-collection->list))]
|
(update :topics reader/read-string)
|
||||||
(->> collection
|
(assoc :id message-id))))))
|
||||||
(map (fn [{:keys [message-id] :as message}]
|
|
||||||
(let [message (-> message
|
|
||||||
(update :message json->clj)
|
|
||||||
(update :identities json->clj))]
|
|
||||||
[message-id message])))
|
|
||||||
(into {}))))
|
|
||||||
|
|
||||||
(defn upsert-pending-message!
|
(defn- get-id
|
||||||
[message]
|
[message-id to]
|
||||||
|
(let [to' (if (and to (str/starts-with? "0x" to))
|
||||||
|
(subs to 2)
|
||||||
|
to)
|
||||||
|
to'' (when to' (subs to' 0 7))
|
||||||
|
id' (if to''
|
||||||
|
(str message-id "-" (subs to'' 0 7))
|
||||||
|
message-id)]
|
||||||
|
id'))
|
||||||
|
|
||||||
|
(defn add-pending-message!
|
||||||
|
[{:keys [id to group-id message] :as pending-message}]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(let [message (-> message
|
(let [{:keys [from topics payload]} message
|
||||||
(update :message clj->json)
|
id' (get-id id to)
|
||||||
(update :identities clj->json))]
|
chat-id (or group-id to)
|
||||||
(r/create :account :pending-message message true)))))
|
message' (-> pending-message
|
||||||
|
(assoc :id id'
|
||||||
|
:from from
|
||||||
|
:message-id id
|
||||||
|
:chat-id chat-id
|
||||||
|
:payload payload
|
||||||
|
:topics (prn-str topics))
|
||||||
|
(dissoc :message))]
|
||||||
|
(r/create :account :pending-message message' true)))))
|
||||||
|
|
||||||
(defn remove-pending-message! [message-id]
|
(defn remove-pending-message!
|
||||||
|
[{{:keys [message-id]} :payload}]
|
||||||
(r/write :account
|
(r/write :account
|
||||||
(fn []
|
(fn []
|
||||||
(r/delete :account (r/get-by-field :account :pending-message :message-id message-id)))))
|
(r/delete :account (r/get-by-field :account :pending-message :message-id message-id)))))
|
||||||
|
|
||||||
|
(defn remove-all-by-chat [chat-id]
|
||||||
|
(r/write
|
||||||
|
:account
|
||||||
|
(fn []
|
||||||
|
(r/delete :account
|
||||||
|
(r/get-by-field :account :pending-message :chat-id chat-id)))))
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
(ns status-im.models.protocol
|
(ns status-im.models.protocol
|
||||||
(:require [cljs.reader :refer [read-string]]
|
(:require [cljs.reader :refer [read-string]]
|
||||||
[status-im.protocol.state.storage :as s]
|
[status-im.protocol.state.storage :as s]
|
||||||
[status-im.utils.encryption :refer [password-encrypt
|
|
||||||
password-decrypt]]
|
|
||||||
[status-im.utils.types :refer [to-edn-string]]
|
[status-im.utils.types :refer [to-edn-string]]
|
||||||
[re-frame.db :refer [app-db]]
|
[re-frame.db :refer [app-db]]
|
||||||
[status-im.db :as db]
|
[status-im.db :as db]
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
(ns status-im.new-group.handlers
|
(ns status-im.new-group.handlers
|
||||||
(:require [status-im.protocol.api :as api]
|
(:require [status-im.protocol.core :as protocol]
|
||||||
[re-frame.core :refer [after dispatch debug enrich]]
|
[re-frame.core :refer [after dispatch debug enrich]]
|
||||||
[status-im.utils.handlers :refer [register-handler]]
|
[status-im.utils.handlers :refer [register-handler]]
|
||||||
[status-im.components.styles :refer [default-chat-color]]
|
[status-im.components.styles :refer [default-chat-color]]
|
||||||
[status-im.models.chats :as chats]
|
[status-im.models.chats :as chats]
|
||||||
[clojure.string :as s]
|
[clojure.string :as s]
|
||||||
[status-im.utils.handlers :as u]))
|
[status-im.utils.handlers :as u]
|
||||||
|
[status-im.utils.random :as random]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
|
|
||||||
(defn deselect-contact
|
(defn deselect-contact
|
||||||
[db [_ id]]
|
[db [_ id]]
|
||||||
|
@ -19,11 +21,6 @@
|
||||||
|
|
||||||
(register-handler :select-contact select-contact)
|
(register-handler :select-contact select-contact)
|
||||||
|
|
||||||
(defn start-group-chat!
|
|
||||||
[{:keys [selected-contacts] :as db} [_ group-name]]
|
|
||||||
(let [group-id (api/start-group-chat selected-contacts group-name)]
|
|
||||||
(assoc db :new-group-id group-id)))
|
|
||||||
|
|
||||||
(defn group-name-from-contacts
|
(defn group-name-from-contacts
|
||||||
[{:keys [contacts selected-contacts username]}]
|
[{:keys [contacts selected-contacts username]}]
|
||||||
(->> (select-keys contacts selected-contacts)
|
(->> (select-keys contacts selected-contacts)
|
||||||
|
@ -33,20 +30,21 @@
|
||||||
(s/join ", ")))
|
(s/join ", ")))
|
||||||
|
|
||||||
(defn prepare-chat
|
(defn prepare-chat
|
||||||
[{:keys [selected-contacts new-group-id] :as db} [_ group-name]]
|
[{:keys [selected-contacts] :as db} [_ group-name]]
|
||||||
(let [contacts (mapv #(hash-map :identity %) selected-contacts)
|
(let [contacts (mapv #(hash-map :identity %) selected-contacts)
|
||||||
chat-name (if-not (s/blank? group-name)
|
chat-name (if-not (s/blank? group-name)
|
||||||
group-name
|
group-name
|
||||||
(group-name-from-contacts db))]
|
(group-name-from-contacts db))
|
||||||
(assoc db :new-chat {:chat-id new-group-id
|
{:keys [public private]} (protocol/new-keypair!)]
|
||||||
:name chat-name
|
(assoc db :new-chat {:chat-id (random/id)
|
||||||
:color default-chat-color
|
:public-key public
|
||||||
:group-chat true
|
:private-key private
|
||||||
:is-active true
|
:name chat-name
|
||||||
:timestamp (.getTime (js/Date.))
|
:color default-chat-color
|
||||||
:contacts contacts
|
:group-chat true
|
||||||
:same-author false
|
:is-active true
|
||||||
:same-direction false})))
|
:timestamp (.getTime (js/Date.))
|
||||||
|
:contacts contacts})))
|
||||||
|
|
||||||
(defn add-chat
|
(defn add-chat
|
||||||
[{:keys [new-chat] :as db} _]
|
[{:keys [new-chat] :as db} _]
|
||||||
|
@ -59,22 +57,45 @@
|
||||||
(chats/create-chat new-chat))
|
(chats/create-chat new-chat))
|
||||||
|
|
||||||
(defn show-chat!
|
(defn show-chat!
|
||||||
[{:keys [new-group-id]} _]
|
[{:keys [new-chat]} _]
|
||||||
(dispatch [:navigation-replace :chat new-group-id]))
|
(dispatch [:navigation-replace :chat (:chat-id new-chat)]))
|
||||||
|
|
||||||
(defn enable-creat-buttion
|
(defn start-listen-group!
|
||||||
|
[{:keys [new-chat web3 current-public-key]}]
|
||||||
|
(let [{:keys [chat-id public-key private-key contacts name]} new-chat
|
||||||
|
identities (mapv :identity contacts)]
|
||||||
|
(protocol/invite-to-group!
|
||||||
|
{:web3 web3
|
||||||
|
:group {:id chat-id
|
||||||
|
:name name
|
||||||
|
:contacts (conj identities current-public-key)
|
||||||
|
:admin current-public-key
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}}
|
||||||
|
:identities identities
|
||||||
|
:message {:from current-public-key
|
||||||
|
:message-id (random/id)}})
|
||||||
|
(protocol/start-watching-group!
|
||||||
|
{:web3 web3
|
||||||
|
:group-id chat-id
|
||||||
|
:identity current-public-key
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}
|
||||||
|
:callback #(dispatch [:incoming-message %1 %2])})))
|
||||||
|
|
||||||
|
(defn enable-create-button
|
||||||
[db _]
|
[db _]
|
||||||
(assoc db :disable-group-creation false))
|
(assoc db :disable-group-creation false))
|
||||||
|
|
||||||
(register-handler :create-new-group
|
(register-handler :create-new-group
|
||||||
(-> start-group-chat!
|
(-> prepare-chat
|
||||||
((enrich prepare-chat))
|
|
||||||
((enrich add-chat))
|
((enrich add-chat))
|
||||||
((after create-chat!))
|
((after create-chat!))
|
||||||
((after show-chat!))
|
((after show-chat!))
|
||||||
((enrich enable-creat-buttion))))
|
((after start-listen-group!))
|
||||||
|
((enrich enable-create-button))))
|
||||||
|
|
||||||
(defn disable-creat-button
|
(defn disable-create-button
|
||||||
[db _]
|
[db _]
|
||||||
(assoc db :disable-group-creation true))
|
(assoc db :disable-group-creation true))
|
||||||
|
|
||||||
|
@ -84,18 +105,32 @@
|
||||||
|
|
||||||
(register-handler :init-group-creation
|
(register-handler :init-group-creation
|
||||||
(after dispatch-create-group)
|
(after dispatch-create-group)
|
||||||
disable-creat-button)
|
disable-create-button)
|
||||||
|
|
||||||
; todo rewrite
|
|
||||||
(register-handler :group-chat-invite-received
|
(register-handler :group-chat-invite-received
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [{:keys [current-public-key] :as db}
|
(fn [{:keys [current-public-key web3] :as db}
|
||||||
[action from group-id identities group-name]]
|
[_ {{:keys [group-id group-name contacts keypair timestamp] :as payload} :payload}]]
|
||||||
(if (chats/chat-exists? group-id)
|
(let [{:keys [private public]} keypair]
|
||||||
(chats/re-join-group-chat db group-id identities group-name)
|
(let [removed-at (chats/removed-at group-id)
|
||||||
(let [contacts (keep (fn [ident]
|
is-active (chats/is-active? group-id)
|
||||||
(when (not= ident current-public-key)
|
contacts' (keep (fn [ident]
|
||||||
{:identity ident})) identities)]
|
(when (not= ident current-public-key)
|
||||||
(dispatch [:add-chat group-id {:name group-name
|
{:identity ident})) contacts)
|
||||||
:group-chat true
|
chat {:name group-name
|
||||||
:contacts contacts}]))))))
|
:group-chat true
|
||||||
|
:public-key public
|
||||||
|
:private-key private
|
||||||
|
:contacts contacts'}]
|
||||||
|
(when (or (not (chats/chat-exists? group-id))
|
||||||
|
is-active
|
||||||
|
(> timestamp removed-at))
|
||||||
|
(dispatch [:add-chat group-id (assoc chat :is-active true
|
||||||
|
:timestamp timestamp)])
|
||||||
|
(when-not is-active
|
||||||
|
(protocol/start-watching-group!
|
||||||
|
{:web3 web3
|
||||||
|
:group-id group-id
|
||||||
|
:identity current-public-key
|
||||||
|
:keypair keypair
|
||||||
|
:callback #(dispatch [:incoming-message %1 %2])}))))))))
|
||||||
|
|
|
@ -3,14 +3,18 @@
|
||||||
|
|
||||||
(def base {:schema [{:name :account
|
(def base {:schema [{:name :account
|
||||||
:primaryKey :address
|
:primaryKey :address
|
||||||
:properties {:address "string"
|
:properties {:address "string"
|
||||||
:public-key "string"
|
:public-key "string"
|
||||||
:name {:type "string" :optional true}
|
:updates-public-key {:type :string
|
||||||
:phone {:type "string" :optional true}
|
:optional true}
|
||||||
:email {:type "string" :optional true}
|
:updates-private-key {:type :string
|
||||||
:status {:type "string" :optional true}
|
:optional true}
|
||||||
:photo-path "string"
|
:name {:type "string" :optional true}
|
||||||
:last-updated {:type "int" :default 0}}}
|
:phone {:type "string" :optional true}
|
||||||
|
:email {:type "string" :optional true}
|
||||||
|
:status {:type "string" :optional true}
|
||||||
|
:photo-path "string"
|
||||||
|
:last-updated {:type "int" :default 0}}}
|
||||||
{:name :kv-store
|
{:name :kv-store
|
||||||
:primaryKey :key
|
:primaryKey :key
|
||||||
:properties {:key "string"
|
:properties {:key "string"
|
||||||
|
@ -25,7 +29,11 @@
|
||||||
:photo-path {:type "string" :optional true}
|
:photo-path {:type "string" :optional true}
|
||||||
:last-updated {:type "int" :default 0}
|
:last-updated {:type "int" :default 0}
|
||||||
:last-online {:type "int" :default 0}
|
:last-online {:type "int" :default 0}
|
||||||
:pending {:type "bool" :default false}}}
|
:pending {:type "bool" :default false}
|
||||||
|
:public-key {:type :string
|
||||||
|
:optional true}
|
||||||
|
:private-key {:type :string
|
||||||
|
:optional true}}}
|
||||||
{:name :request
|
{:name :request
|
||||||
:properties {:message-id :string
|
:properties {:message-id :string
|
||||||
:chat-id :string
|
:chat-id :string
|
||||||
|
@ -85,19 +93,21 @@
|
||||||
:user-statuses {:type :list
|
:user-statuses {:type :list
|
||||||
:objectType "user-status"}}}
|
:objectType "user-status"}}}
|
||||||
{:name :pending-message
|
{:name :pending-message
|
||||||
:primaryKey :message-id
|
:primaryKey :id
|
||||||
:properties {:message-id "string"
|
:properties {:id :string
|
||||||
:chat-id {:type "string"
|
:message-id :string
|
||||||
:optional true}
|
:chat-id {:type :string
|
||||||
:message "string"
|
:optional true}
|
||||||
:timestamp "int"
|
:ack? :bool
|
||||||
:status "string"
|
:requires-ack? :bool
|
||||||
:retry-count "int"
|
:from :string
|
||||||
:send-once "bool"
|
:to {:type :string
|
||||||
:identities {:type "string"
|
:optional true}
|
||||||
:optional true}
|
:payload :string
|
||||||
:internal? {:type "bool"
|
:type :string
|
||||||
:optional true}}}
|
:topics :string
|
||||||
|
:attempts :int
|
||||||
|
:was-sent? :bool}}
|
||||||
{:name :chat-contact
|
{:name :chat-contact
|
||||||
:properties {:identity "string"
|
:properties {:identity "string"
|
||||||
:is-in-chat {:type "bool"
|
:is-in-chat {:type "bool"
|
||||||
|
@ -118,7 +128,13 @@
|
||||||
:optional true}
|
:optional true}
|
||||||
:dapp-hash {:type :int
|
:dapp-hash {:type :int
|
||||||
:optional true}
|
:optional true}
|
||||||
:last-message-id "string"}}
|
:removed-at {:type :int
|
||||||
|
:optional true}
|
||||||
|
:last-message-id "string"
|
||||||
|
:public-key {:type :string
|
||||||
|
:optional true}
|
||||||
|
:private-key {:type :string
|
||||||
|
:optional true}}}
|
||||||
{:name :command
|
{:name :command
|
||||||
:primaryKey :chat-id
|
:primaryKey :chat-id
|
||||||
:properties {:chat-id "string"
|
:properties {:chat-id "string"
|
||||||
|
|
|
@ -4,13 +4,9 @@
|
||||||
[status-im.components.react :refer [show-image-picker]]
|
[status-im.components.react :refer [show-image-picker]]
|
||||||
[status-im.utils.image-processing :refer [img->base64]]
|
[status-im.utils.image-processing :refer [img->base64]]
|
||||||
[status-im.i18n :refer [label]]
|
[status-im.i18n :refer [label]]
|
||||||
[status-im.utils.handlers :as u]
|
[status-im.utils.handlers :as u :refer [get-hashtags]]
|
||||||
[clojure.string :as str]))
|
[clojure.string :as str]))
|
||||||
|
|
||||||
(defn get-hashtags [status]
|
|
||||||
(let [hashtags (map #(str/lower-case (subs % 1)) (re-seq #"#[^ !?,;:.]+" status))]
|
|
||||||
(or hashtags [])))
|
|
||||||
|
|
||||||
(defn message-user [identity]
|
(defn message-user [identity]
|
||||||
(when identity
|
(when identity
|
||||||
(dispatch [:navigate-to :chat identity])))
|
(dispatch [:navigate-to :chat identity])))
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
my-profile-icon]]
|
my-profile-icon]]
|
||||||
[status-im.components.status-bar :refer [status-bar]]
|
[status-im.components.status-bar :refer [status-bar]]
|
||||||
[status-im.profile.styles :as st]
|
[status-im.profile.styles :as st]
|
||||||
[status-im.profile.handlers :refer [get-hashtags
|
[status-im.utils.handlers :refer [get-hashtags]]
|
||||||
message-user
|
[status-im.profile.handlers :refer [message-user
|
||||||
update-profile]]
|
update-profile]]
|
||||||
[status-im.components.qr-code :refer [qr-code]]
|
[status-im.components.qr-code :refer [qr-code]]
|
||||||
[status-im.utils.phone-number :refer [format-phone-number
|
[status-im.utils.phone-number :refer [format-phone-number
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
(ns status-im.protocol.ack
|
||||||
|
(:require [status-im.protocol.web3.delivery :as d]
|
||||||
|
[status-im.protocol.web3.filtering :as f]))
|
||||||
|
|
||||||
|
(defn check-ack!
|
||||||
|
[web3
|
||||||
|
from
|
||||||
|
{:keys [type requires-ack? message-id ack? group-id]}
|
||||||
|
identity]
|
||||||
|
(when (and requires-ack? (not ack?))
|
||||||
|
(let [message {:from identity
|
||||||
|
:to from
|
||||||
|
:message-id message-id
|
||||||
|
:topics [f/status-topic]
|
||||||
|
:type type
|
||||||
|
:ack? true
|
||||||
|
:payload {:type type
|
||||||
|
:ack? true
|
||||||
|
:group-id group-id}}]
|
||||||
|
(d/add-pending-message! web3 message)))
|
||||||
|
(when ack?
|
||||||
|
(d/remove-pending-message! web3 message-id from)))
|
|
@ -0,0 +1,43 @@
|
||||||
|
(ns status-im.protocol.chat
|
||||||
|
(:require [cljs.spec :as s]
|
||||||
|
[status-im.protocol.message :as m]
|
||||||
|
[status-im.protocol.web3.filtering :as f]
|
||||||
|
[status-im.protocol.web3.delivery :as d]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]))
|
||||||
|
|
||||||
|
(def message-defaults
|
||||||
|
{:topics [f/status-topic]})
|
||||||
|
|
||||||
|
(s/def ::timestamp int?)
|
||||||
|
(s/def ::user-message
|
||||||
|
(s/merge
|
||||||
|
:protocol/message
|
||||||
|
(s/keys :req-un [:message/to :chat-message/payload])))
|
||||||
|
|
||||||
|
(defn send!
|
||||||
|
[{:keys [web3 message]}]
|
||||||
|
{:pre [(valid? ::user-message message)]}
|
||||||
|
(let [message' (merge message-defaults
|
||||||
|
(assoc message
|
||||||
|
:type :message
|
||||||
|
:requires-ack? true))]
|
||||||
|
(debug :send-user-message message')
|
||||||
|
(d/add-pending-message! web3 message')))
|
||||||
|
|
||||||
|
(s/def ::seen-message
|
||||||
|
(s/merge :protocol/message (s/keys :req-un [:message/to])))
|
||||||
|
|
||||||
|
(defn send-seen!
|
||||||
|
[{:keys [web3 message]}]
|
||||||
|
{:pre [(valid? ::seen-message message)]}
|
||||||
|
(debug :send-seen message)
|
||||||
|
(d/add-pending-message!
|
||||||
|
web3
|
||||||
|
(merge message-defaults
|
||||||
|
(-> message
|
||||||
|
(assoc
|
||||||
|
:type :seen
|
||||||
|
:requires-ack? false)
|
||||||
|
(assoc-in [:payload :group-id] (:group-id message))
|
||||||
|
(dissoc :group-id)))))
|
|
@ -0,0 +1,105 @@
|
||||||
|
(ns status-im.protocol.core
|
||||||
|
(:require status-im.protocol.message
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[status-im.protocol.web3.filtering :as f]
|
||||||
|
[status-im.protocol.web3.delivery :as d]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[status-im.protocol.chat :as chat]
|
||||||
|
[status-im.protocol.group :as group]
|
||||||
|
[status-im.protocol.listeners :as l]
|
||||||
|
[status-im.protocol.encryption :as e]
|
||||||
|
[status-im.protocol.discoveries :as discoveries]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[status-im.utils.random :as random]))
|
||||||
|
|
||||||
|
;; user
|
||||||
|
(def send-message! chat/send!)
|
||||||
|
(def send-seen! chat/send-seen!)
|
||||||
|
(def reset-pending-messages! d/reset-pending-messages!)
|
||||||
|
|
||||||
|
;; group
|
||||||
|
(def start-watching-group! group/start-watching-group!)
|
||||||
|
(def stop-watching-group! group/stop-watching-group!)
|
||||||
|
(def send-group-message! group/send!)
|
||||||
|
(def invite-to-group! group/invite!)
|
||||||
|
(def update-group! group/update-group!)
|
||||||
|
(def remove-from-group! group/remove-identity!)
|
||||||
|
(def add-to-group! group/add-identity!)
|
||||||
|
(def leave-group-chat! group/leave!)
|
||||||
|
|
||||||
|
;; encryption
|
||||||
|
;; todo move somewhere, encryption functions shouldn't be there
|
||||||
|
(def new-keypair! e/new-keypair!)
|
||||||
|
|
||||||
|
;; discoveries
|
||||||
|
(def watch-user! discoveries/watch-user!)
|
||||||
|
(def contact-request! discoveries/contact-request!)
|
||||||
|
(def watch-hashtags! discoveries/watch-hashtags!)
|
||||||
|
(def broadcats-profile! discoveries/broadcats-profile!)
|
||||||
|
(def broadcats-discoveries! discoveries/broadcats-discoveries!)
|
||||||
|
|
||||||
|
;; initialization
|
||||||
|
(s/def ::rpc-url string?)
|
||||||
|
(s/def ::identity string?)
|
||||||
|
(s/def :message/chat-id string?)
|
||||||
|
(s/def ::group (s/keys :req-un [:message/chat-id :message/keypair]))
|
||||||
|
(s/def ::groups (s/* ::group))
|
||||||
|
(s/def ::callback fn?)
|
||||||
|
(s/def ::contact (s/keys :req-un [::identity :message/keypair]))
|
||||||
|
(s/def ::contacts (s/* ::contact))
|
||||||
|
(s/def ::profile-keypair :message/keypair)
|
||||||
|
(s/def ::options
|
||||||
|
(s/merge
|
||||||
|
(s/keys :req-un [::rpc-url ::identity ::groups ::profile-keypair
|
||||||
|
::callback :discoveries/hashtags ::contacts])
|
||||||
|
::d/delivery-options))
|
||||||
|
|
||||||
|
(def stop-watching-all! f/remove-all-filters!)
|
||||||
|
|
||||||
|
(defn init-whisper!
|
||||||
|
[{:keys [rpc-url identity groups callback
|
||||||
|
hashtags contacts profile-keypair pending-messages]
|
||||||
|
:as options}]
|
||||||
|
{:pre [(valid? ::options options)]}
|
||||||
|
(debug :init-whisper)
|
||||||
|
(stop-watching-all!)
|
||||||
|
(let [web3 (u/make-web3 rpc-url)
|
||||||
|
listener-options {:web3 web3
|
||||||
|
:identity identity}]
|
||||||
|
;; start listening to user's inbox
|
||||||
|
(f/add-filter!
|
||||||
|
web3
|
||||||
|
{:to identity
|
||||||
|
:topics [f/status-topic]}
|
||||||
|
(l/message-listener (assoc listener-options :callback callback)))
|
||||||
|
;; start listening to groups
|
||||||
|
(doseq [{:keys [chat-id keypair]} groups]
|
||||||
|
(f/add-filter!
|
||||||
|
web3
|
||||||
|
{:topics [chat-id]}
|
||||||
|
(l/message-listener (assoc listener-options :callback callback
|
||||||
|
:keypair keypair))))
|
||||||
|
;; start listening to discoveries
|
||||||
|
(watch-hashtags! {:web3 web3
|
||||||
|
:hashtags hashtags
|
||||||
|
:callback callback})
|
||||||
|
;; start listening to profiles
|
||||||
|
(doseq [{:keys [identity keypair]} contacts]
|
||||||
|
(watch-user! {:web3 web3
|
||||||
|
:identity identity
|
||||||
|
:keypair keypair
|
||||||
|
:callback callback}))
|
||||||
|
(d/set-pending-mesage-callback! callback)
|
||||||
|
(let [online-message #(discoveries/send-online!
|
||||||
|
{:web3 web3
|
||||||
|
:message {:from identity
|
||||||
|
:message-id (random/id)
|
||||||
|
:keypair profile-keypair}})]
|
||||||
|
(d/run-delivery-loop!
|
||||||
|
web3
|
||||||
|
(assoc options :online-message online-message)))
|
||||||
|
(doseq [pending-message pending-messages]
|
||||||
|
(d/add-prepeared-pending-message! web3 pending-message))
|
||||||
|
web3))
|
|
@ -0,0 +1,148 @@
|
||||||
|
(ns status-im.protocol.discoveries
|
||||||
|
(:require
|
||||||
|
[taoensso.timbre :refer-macros [debug]]
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[status-im.protocol.web3.delivery :as d]
|
||||||
|
[status-im.protocol.web3.filtering :as f]
|
||||||
|
[status-im.protocol.listeners :as l]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]
|
||||||
|
[status-im.utils.random :as random]))
|
||||||
|
|
||||||
|
(def discovery-topic "status-discovery")
|
||||||
|
(def discovery-topic-prefix "status-discovery-")
|
||||||
|
(def discovery-hashtag-prefix "status-hashtag-")
|
||||||
|
|
||||||
|
(defn- add-hashtag-prefix [hashtag]
|
||||||
|
(str discovery-hashtag-prefix hashtag))
|
||||||
|
|
||||||
|
(defn- make-discovery-topic [identity]
|
||||||
|
(str discovery-topic-prefix identity))
|
||||||
|
|
||||||
|
(s/def :send-online/message
|
||||||
|
(s/merge :protocol/message
|
||||||
|
(s/keys :req-un [:message/keypair])))
|
||||||
|
(s/def :send-online/options
|
||||||
|
(s/keys :req-un [:options/web3 :send-online/message]))
|
||||||
|
|
||||||
|
(defn send-online!
|
||||||
|
[{:keys [web3 message] :as options}]
|
||||||
|
{:pre [(valid? :send-online/options options)]}
|
||||||
|
(debug :send-online)
|
||||||
|
(let [message' (merge
|
||||||
|
message
|
||||||
|
{:requires-ack? false
|
||||||
|
:type :online
|
||||||
|
:payload {:timestamp (u/timestamp)}
|
||||||
|
:topics [(make-discovery-topic (:from message))]})]
|
||||||
|
(d/add-pending-message! web3 message')))
|
||||||
|
|
||||||
|
(s/def ::identity :message/from)
|
||||||
|
(s/def :watch-user/options
|
||||||
|
(s/keys :req-un [:options/web3 :message/keypair ::identity ::callback]))
|
||||||
|
|
||||||
|
(defn watch-user!
|
||||||
|
[{:keys [web3 identity] :as options}]
|
||||||
|
{:pre [(valid? :watch-user/options options)]}
|
||||||
|
(f/add-filter!
|
||||||
|
web3
|
||||||
|
{:from identity
|
||||||
|
:topics [(make-discovery-topic identity)]}
|
||||||
|
(l/message-listener (dissoc options :identity))))
|
||||||
|
|
||||||
|
(s/def :contact-request/contact map?)
|
||||||
|
|
||||||
|
(s/def :contact-request/payload
|
||||||
|
(s/merge :message/payload
|
||||||
|
(s/keys :req-un [:contact-request/contact :message/keypair])))
|
||||||
|
|
||||||
|
(s/def :contact-request/message
|
||||||
|
(s/merge :protocol/message
|
||||||
|
(s/keys :req-un [:message/to :contact-request/payload])))
|
||||||
|
|
||||||
|
(defn contact-request!
|
||||||
|
[{:keys [web3 message]}]
|
||||||
|
{:pre [(valid? :contact-request/message message)]}
|
||||||
|
(d/add-pending-message!
|
||||||
|
web3
|
||||||
|
(assoc message :type :contact-request
|
||||||
|
:requires-ack? true
|
||||||
|
:topics [f/status-topic])))
|
||||||
|
|
||||||
|
(defonce watched-hashtag-topics (atom nil))
|
||||||
|
|
||||||
|
(defn- hashtags->topics
|
||||||
|
"Create topics from hashtags."
|
||||||
|
[hashtags]
|
||||||
|
(->> hashtags
|
||||||
|
(map (fn [tag]
|
||||||
|
[tag [(add-hashtag-prefix tag) discovery-topic]]))
|
||||||
|
(into {})))
|
||||||
|
|
||||||
|
(s/def :discoveries/hashtags (s/every string? :kind-of set?))
|
||||||
|
|
||||||
|
(defn stop-watching-hashtags!
|
||||||
|
[web3]
|
||||||
|
(doseq [topics @watched-hashtag-topics]
|
||||||
|
(f/remove-filter! web3 topics)))
|
||||||
|
|
||||||
|
(s/def ::callback fn?)
|
||||||
|
(s/def :watch-hashtags/options
|
||||||
|
(s/keys :req-un [:options/web3 :discoveries/hashtags ::callback]))
|
||||||
|
|
||||||
|
(defn watch-hashtags!
|
||||||
|
[{:keys [web3 hashtags] :as options}]
|
||||||
|
{:pre [(valid? :watch-hashtags/options options)]}
|
||||||
|
(debug :watch-hashtags hashtags)
|
||||||
|
(stop-watching-hashtags! web3)
|
||||||
|
(let [hashtag-topics (vals (hashtags->topics hashtags))]
|
||||||
|
(reset! watched-hashtag-topics hashtag-topics)
|
||||||
|
(doseq [topics hashtag-topics]
|
||||||
|
(f/add-filter! web3 {:topics topics} (l/message-listener options)))))
|
||||||
|
|
||||||
|
(s/def ::status (s/nilable string?))
|
||||||
|
(s/def ::profile (s/keys :req-un [::status]))
|
||||||
|
(s/def :profile/payload
|
||||||
|
(s/merge :message/payload (s/keys :req-un [::profile])))
|
||||||
|
(s/def :profile/message
|
||||||
|
(s/merge :protocol/message (s/keys :req-un [:message/keypair
|
||||||
|
:profile/payload])))
|
||||||
|
(s/def :broadcast-profile/options
|
||||||
|
(s/keys :req-un [:profile/message :options/web3]))
|
||||||
|
|
||||||
|
(defn broadcats-profile!
|
||||||
|
[{:keys [web3 message] :as options}]
|
||||||
|
{:pre [(valid? :broadcast-profile/options options)]}
|
||||||
|
(debug :broadcasting-status)
|
||||||
|
(d/add-pending-message!
|
||||||
|
web3
|
||||||
|
(-> message
|
||||||
|
(assoc :type :profile
|
||||||
|
:topics [(make-discovery-topic (:from message))])
|
||||||
|
(assoc-in [:payload :timestamp] (u/timestamp))
|
||||||
|
(assoc-in [:payload :content :profile]
|
||||||
|
(get-in message [:payload :profile]))
|
||||||
|
(update :payload dissoc :profile))))
|
||||||
|
|
||||||
|
(s/def :status/payload
|
||||||
|
(s/merge :message/payload (s/keys :req-un [::status])))
|
||||||
|
(s/def :status/message
|
||||||
|
(s/merge :protocol/message (s/keys :req-un [:status/payload])))
|
||||||
|
(s/def :broadcast-hasthags/options
|
||||||
|
(s/keys :req-un [:discoveries/hashtags :status/message :options/web3]))
|
||||||
|
|
||||||
|
(defn broadcats-discoveries!
|
||||||
|
[{:keys [web3 hashtags message] :as options}]
|
||||||
|
{:pre [(valid? :broadcast-hasthags/options options)]}
|
||||||
|
(debug :broadcasting-status)
|
||||||
|
(let [discovery-id (random/id)]
|
||||||
|
(doseq [[tag hashtag-topics] (hashtags->topics hashtags)]
|
||||||
|
(d/add-pending-message!
|
||||||
|
web3
|
||||||
|
(-> message
|
||||||
|
(assoc :type :discovery
|
||||||
|
:topics hashtag-topics)
|
||||||
|
(assoc-in [:payload :tag] tag)
|
||||||
|
(assoc-in [:payload :hashtags] (vec hashtags))
|
||||||
|
(assoc-in [:payload :discovery-id] discovery-id)
|
||||||
|
(update :message-id str tag))))))
|
|
@ -0,0 +1,21 @@
|
||||||
|
(ns status-im.protocol.encryption
|
||||||
|
(:require [cljsjs.chance]
|
||||||
|
[cljsjs.eccjs]))
|
||||||
|
|
||||||
|
(def default-curve 384)
|
||||||
|
|
||||||
|
(defn new-keypair!
|
||||||
|
"Returns {:private \"private key\" :public \"public key\""
|
||||||
|
[]
|
||||||
|
(let [{:keys [enc dec]}
|
||||||
|
(-> (.generate js/ecc (.-ENC_DEC js/ecc) default-curve)
|
||||||
|
(js->clj :keywordize-keys true))]
|
||||||
|
{:private dec
|
||||||
|
:public enc}))
|
||||||
|
|
||||||
|
(defn encrypt [public-key content]
|
||||||
|
(.encrypt js/ecc public-key content))
|
||||||
|
|
||||||
|
(defn decrypt [private-key content]
|
||||||
|
(.decrypt js/ecc private-key content))
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
(ns status-im.protocol.group
|
||||||
|
(:require
|
||||||
|
[status-im.protocol.message :as m]
|
||||||
|
[status-im.protocol.web3.delivery :as d]
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]
|
||||||
|
[status-im.protocol.web3.filtering :as f]
|
||||||
|
[status-im.protocol.listeners :as l]))
|
||||||
|
|
||||||
|
(defn prepare-mesage
|
||||||
|
[{:keys [message group-id keypair new-keypair type]}]
|
||||||
|
(let [message' (-> message
|
||||||
|
(update :payload assoc
|
||||||
|
:group-id group-id
|
||||||
|
:type type
|
||||||
|
:timestamp (u/timestamp))
|
||||||
|
(assoc :topics [group-id]
|
||||||
|
:requires-ack? true
|
||||||
|
:keypair keypair
|
||||||
|
:type type))]
|
||||||
|
(if new-keypair
|
||||||
|
(assoc message' :new-keypair keypair)
|
||||||
|
message')))
|
||||||
|
|
||||||
|
(defn- send-group-message!
|
||||||
|
[{:keys [web3] :as opts} type]
|
||||||
|
(let [message (-> opts
|
||||||
|
(assoc :type type)
|
||||||
|
(prepare-mesage))]
|
||||||
|
(debug :send-group-message message)
|
||||||
|
(d/add-pending-message! web3 message)))
|
||||||
|
|
||||||
|
(s/def ::group-message
|
||||||
|
(s/merge :protocol/message (s/keys :req-un [:chat-message/payload])))
|
||||||
|
|
||||||
|
(defn send!
|
||||||
|
[{:keys [keypair message] :as options}]
|
||||||
|
{:pre [(valid? :message/keypair keypair)
|
||||||
|
(valid? ::group-message message)]}
|
||||||
|
(send-group-message! options :group-message))
|
||||||
|
|
||||||
|
(defn leave!
|
||||||
|
[options]
|
||||||
|
(send-group-message! options :leave-group))
|
||||||
|
|
||||||
|
(defn add-identity!
|
||||||
|
[{:keys [identity] :as options}]
|
||||||
|
{:pre [(valid? :message/to identity)]}
|
||||||
|
(let [options' (assoc-in options
|
||||||
|
[:message :payload :identity]
|
||||||
|
identity)]
|
||||||
|
(send-group-message! options' :add-group-identity)))
|
||||||
|
|
||||||
|
(defn remove-identity!
|
||||||
|
[{:keys [identity] :as options}]
|
||||||
|
{:pre [(valid? :message/to identity)]}
|
||||||
|
(let [options' (assoc-in options
|
||||||
|
[:message :payload :identity]
|
||||||
|
identity)]
|
||||||
|
(send-group-message! options' :remove-group-identity)))
|
||||||
|
|
||||||
|
(s/def :group/admin :message/from)
|
||||||
|
(s/def ::identities (s/* string?))
|
||||||
|
|
||||||
|
(s/def :group/name string?)
|
||||||
|
(s/def :group/id string?)
|
||||||
|
(s/def :group/contacts (s/* string?))
|
||||||
|
(s/def ::group
|
||||||
|
(s/keys :req-un [:group/name :group/id :group/contacts :message/keypair]))
|
||||||
|
(s/def :invite/options
|
||||||
|
(s/keys :req-un [:options/web3 :protocol/message ::group ::identities]))
|
||||||
|
|
||||||
|
(defn- notify-about-group!
|
||||||
|
[type {:keys [web3 message identities group]
|
||||||
|
:as options}]
|
||||||
|
{:pre [(valid? :invite/options options)]}
|
||||||
|
(let [{:keys [id admin name keypair contacts]} group
|
||||||
|
message' (-> message
|
||||||
|
(assoc :topics [f/status-topic]
|
||||||
|
:requires-ack? true
|
||||||
|
:type type)
|
||||||
|
(update :payload assoc
|
||||||
|
:timestamp (u/timestamp)
|
||||||
|
:group-id id
|
||||||
|
:group-admin admin
|
||||||
|
:group-name name
|
||||||
|
:keypair keypair
|
||||||
|
:contacts contacts
|
||||||
|
:type type))]
|
||||||
|
(doseq [identity identities]
|
||||||
|
(d/add-pending-message! web3 (assoc message' :to identity)))))
|
||||||
|
|
||||||
|
(defn invite!
|
||||||
|
[options]
|
||||||
|
(notify-about-group! :group-invitation options))
|
||||||
|
|
||||||
|
;; todo notify users about keypair change when someone leaves group (from admin)
|
||||||
|
(defn update-group!
|
||||||
|
[options]
|
||||||
|
(notify-about-group! :update-group options))
|
||||||
|
|
||||||
|
(defn stop-watching-group!
|
||||||
|
[{:keys [web3 group-id]}]
|
||||||
|
{:pre [(valid? :message/chat-id group-id)]}
|
||||||
|
(f/remove-filter! web3 [group-id]))
|
||||||
|
|
||||||
|
(defn start-watching-group!
|
||||||
|
[{:keys [web3 group-id keypair callback identity]}]
|
||||||
|
(f/add-filter!
|
||||||
|
web3
|
||||||
|
{:topics [group-id]}
|
||||||
|
(l/message-listener {:web3 web3
|
||||||
|
:identity identity
|
||||||
|
:callback callback
|
||||||
|
:keypair keypair})))
|
|
@ -3,26 +3,75 @@
|
||||||
(ns status-im.protocol.handlers
|
(ns status-im.protocol.handlers
|
||||||
(:require [status-im.utils.handlers :as u]
|
(:require [status-im.utils.handlers :as u]
|
||||||
[status-im.utils.logging :as log]
|
[status-im.utils.logging :as log]
|
||||||
[status-im.protocol.api :as api]
|
|
||||||
[re-frame.core :refer [dispatch after]]
|
[re-frame.core :refer [dispatch after]]
|
||||||
[status-im.utils.handlers :refer [register-handler]]
|
[status-im.utils.handlers :refer [register-handler]]
|
||||||
[status-im.models.contacts :as contacts]
|
[status-im.models.contacts :as contacts]
|
||||||
[status-im.models.messages :as messages]
|
[status-im.models.messages :as messages]
|
||||||
[status-im.models.pending-messages :as pending-messages]
|
[status-im.models.pending-messages :as pending-messages]
|
||||||
[status-im.models.chats :as chats]
|
[status-im.models.chats :as chats]
|
||||||
[status-im.protocol.api :refer [init-protocol]]
|
|
||||||
[status-im.protocol.protocol-handler :refer [make-handler]]
|
|
||||||
[status-im.models.protocol :refer [update-identity
|
[status-im.models.protocol :refer [update-identity
|
||||||
set-initialized]]
|
set-initialized]]
|
||||||
|
[status-im.protocol.core :as protocol]
|
||||||
[status-im.constants :refer [text-content-type]]
|
[status-im.constants :refer [text-content-type]]
|
||||||
[status-im.i18n :refer [label]]
|
[status-im.i18n :refer [label]]
|
||||||
[status-im.utils.random :as random]))
|
[status-im.utils.random :as random]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
|
|
||||||
(register-handler :initialize-protocol
|
(register-handler :initialize-protocol
|
||||||
|
(fn [db [_ current-account-id]]
|
||||||
|
(let [{:keys [public-key status updates-public-key
|
||||||
|
updates-private-key]}
|
||||||
|
(get-in db [:accounts current-account-id])]
|
||||||
|
(let [groups (chats/active-group-chats)
|
||||||
|
w3 (protocol/init-whisper!
|
||||||
|
{:rpc-url "http://localhost:8545"
|
||||||
|
:identity public-key
|
||||||
|
:groups groups
|
||||||
|
:callback #(dispatch [:incoming-message %1 %2])
|
||||||
|
:ack-not-received-s-interval 17
|
||||||
|
:default-ttl 15
|
||||||
|
:send-online-s-interval 180
|
||||||
|
:ttl {}
|
||||||
|
:max-attempts-number 3
|
||||||
|
:delivery-loop-ms-interval 500
|
||||||
|
:profile-keypair {:public updates-public-key
|
||||||
|
:private updates-private-key}
|
||||||
|
:hashtags (u/get-hashtags status)
|
||||||
|
:pending-messages (pending-messages/get-pending-messages!)
|
||||||
|
:contacts (keep (fn [{:keys [whisper-identity
|
||||||
|
public-key
|
||||||
|
private-key]}]
|
||||||
|
(when (and public-key private-key)
|
||||||
|
{:identity whisper-identity
|
||||||
|
:keypair {:public public-key
|
||||||
|
:private private-key}}))
|
||||||
|
|
||||||
|
(contacts/get-contacts))})]
|
||||||
|
(assoc db :web3 w3)))))
|
||||||
|
|
||||||
|
(register-handler :incoming-message
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [db [_ current-account-id]]
|
(fn [_ [_ type {:keys [payload] :as message}]]
|
||||||
(let [current-account (get-in db [:accounts current-account-id])]
|
(debug :incoming-message type)
|
||||||
(init-protocol current-account (make-handler db))))))
|
(case type
|
||||||
|
:message (dispatch [:received-protocol-message! message])
|
||||||
|
:group-message (dispatch [:received-protocol-message! message])
|
||||||
|
:ack (when (#{:message :group-message} (:type payload))
|
||||||
|
(dispatch [:message-delivered message]))
|
||||||
|
:seen (dispatch [:message-seen message])
|
||||||
|
:group-invitation (dispatch [:group-chat-invite-received message])
|
||||||
|
:leave-group (dispatch [:participant-left-group message])
|
||||||
|
:contact-request (dispatch [:contact-request-received message])
|
||||||
|
:discovery (dispatch [:discovery-response-received message])
|
||||||
|
:profile (dispatch [:contact-update-received message])
|
||||||
|
:online (dispatch [:contact-online-received message])
|
||||||
|
:pending (dispatch [:pending-message-upsert message])
|
||||||
|
:sent (let [{:keys [to id group-id]} message
|
||||||
|
message' {:from to
|
||||||
|
:payload {:message-id id
|
||||||
|
:group-id group-id}}]
|
||||||
|
(dispatch [:message-sent message']))
|
||||||
|
(debug "Unknown message type" type)))))
|
||||||
|
|
||||||
(register-handler :protocol-initialized
|
(register-handler :protocol-initialized
|
||||||
(fn [db [_ identity]]
|
(fn [db [_ identity]]
|
||||||
|
@ -43,9 +92,9 @@
|
||||||
:content (str (or contact-name from) " " (label :t/received-invitation))
|
:content (str (or contact-name from) " " (label :t/received-invitation))
|
||||||
:content-type text-content-type})))
|
:content-type text-content-type})))
|
||||||
|
|
||||||
(defn participant-invited-to-group-message [chat-id identity from message-id]
|
(defn participant-invited-to-group-message [chat-id current-identity identity from message-id]
|
||||||
(let [inviter-name (:name (contacts/contact-by-identity from))
|
(let [inviter-name (:name (contacts/contact-by-identity from))
|
||||||
invitee-name (if (= identity (api/my-identity))
|
invitee-name (if (= identity current-identity)
|
||||||
(label :t/You)
|
(label :t/You)
|
||||||
(:name (contacts/contact-by-identity identity)))]
|
(:name (contacts/contact-by-identity identity)))]
|
||||||
(messages/save-message chat-id {:from "system"
|
(messages/save-message chat-id {:from "system"
|
||||||
|
@ -94,42 +143,94 @@
|
||||||
|
|
||||||
(register-handler :participant-left-group
|
(register-handler :participant-left-group
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [action from group-id message-id]]
|
(fn [{:keys [current-public-key]}
|
||||||
(log/debug action message-id from group-id)
|
[_ {:keys [from]
|
||||||
(when-not (= (api/my-identity) from)
|
{:keys [group-id message-id timestamp]} :payload}]]
|
||||||
(participant-left-group-message group-id from message-id)))))
|
(when (and (not= current-public-key from)
|
||||||
|
(chats/is-active? group-id)
|
||||||
|
(> timestamp (chats/get-property group-id :timestamp)))
|
||||||
|
(participant-left-group-message group-id from message-id)
|
||||||
|
(dispatch [::remove-identity-from-chat group-id from])
|
||||||
|
(dispatch [::remove-identity-from-chat! group-id from])))))
|
||||||
|
|
||||||
|
(register-handler ::remove-identity-from-chat
|
||||||
|
(fn [db [_ chat-id id]]
|
||||||
|
(update-in db [:chats chat-id :contacts]
|
||||||
|
#(remove (fn [{:keys [identity]}]
|
||||||
|
(= identity id)) %))))
|
||||||
|
|
||||||
|
(register-handler ::remove-identity-from-chat!
|
||||||
|
(u/side-effect!
|
||||||
|
(fn [_ [_ group-id identity]]
|
||||||
|
(chats/chat-remove-participants group-id [identity]))))
|
||||||
|
|
||||||
(register-handler :participant-invited-to-group
|
(register-handler :participant-invited-to-group
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [action from group-id identity message-id]]
|
(fn [{:keys [current-public-key]} [action from group-id identity message-id]]
|
||||||
(log/debug action message-id from group-id identity)
|
(log/debug action message-id from group-id identity)
|
||||||
(participant-invited-to-group-message group-id identity from message-id))))
|
(participant-invited-to-group-message group-id current-public-key identity from message-id)
|
||||||
|
;; todo uncomment
|
||||||
|
#_(dispatch [:add-contact-to-group! group-id identity]))))
|
||||||
|
|
||||||
|
(register-handler :add-contact-to-group!
|
||||||
|
(u/side-effect!
|
||||||
|
(fn [_ [_ group-id identity]]
|
||||||
|
(when-not (chats/contact group-id identity)
|
||||||
|
(dispatch [::add-contact group-id identity])
|
||||||
|
(dispatch [::store-contact! group-id identity])))))
|
||||||
|
|
||||||
|
(register-handler ::add-contact
|
||||||
|
(fn [db [_ group-id identity]]
|
||||||
|
(update-in db [:chats group-id :contacts] conj {:identity identity})))
|
||||||
|
|
||||||
|
(register-handler ::store-contact!
|
||||||
|
(u/side-effect!
|
||||||
|
(fn [_ [_ group-id identity]]
|
||||||
|
(chats/chat-add-participants group-id [identity]))))
|
||||||
|
|
||||||
(defn save-message-status! [status]
|
(defn save-message-status! [status]
|
||||||
(fn [_ [_ {:keys [message-id whisper-identity]}]]
|
(fn [_ [_
|
||||||
|
{:keys [from]
|
||||||
|
{:keys [message-id group-id]} :payload}]]
|
||||||
(when-let [message (messages/get-message message-id)]
|
(when-let [message (messages/get-message message-id)]
|
||||||
(let [message (if whisper-identity
|
(let [group? (boolean group-id)
|
||||||
|
message (if (and group? (not= status :sent))
|
||||||
(update-in message
|
(update-in message
|
||||||
[:user-statuses whisper-identity]
|
[:user-statuses from]
|
||||||
(fn [{old-status :status}]
|
(fn [{old-status :status}]
|
||||||
{:id (random/id)
|
{:id (random/id)
|
||||||
:whisper-identity whisper-identity
|
:whisper-identity from
|
||||||
:status (if (= (keyword old-status) :seen)
|
:status (if (= (keyword old-status) :seen)
|
||||||
old-status
|
old-status
|
||||||
status)}))
|
status)}))
|
||||||
(assoc message :message-status status))]
|
(assoc message :message-status status))]
|
||||||
(messages/update-message! message)))))
|
(messages/update-message! message)))))
|
||||||
|
|
||||||
|
|
||||||
(defn update-message-status [status]
|
(defn update-message-status [status]
|
||||||
(fn [db [_ {:keys [message-id whisper-identity]}]]
|
(fn [db
|
||||||
(let [db-key (if whisper-identity
|
[_ {:keys [from]
|
||||||
[:message-user-statuses message-id whisper-identity]
|
{:keys [message-id group-id]} :payload}]]
|
||||||
[:message-statuses message-id])
|
(if (chats/is-active? (or group-id from))
|
||||||
current-status (get-in db db-key)]
|
(let [group? (boolean group-id)
|
||||||
(if-not (= :seen current-status)
|
status-path (if (and group? (not= status :sent))
|
||||||
(assoc-in db db-key {:whisper-identity whisper-identity
|
[:message-user-statuses message-id from]
|
||||||
:status status})
|
[:message-statuses message-id])
|
||||||
db))))
|
current-status (get-in db status-path)]
|
||||||
|
(if-not (= :seen current-status)
|
||||||
|
(assoc-in db status-path {:whisper-identity from
|
||||||
|
:status status})
|
||||||
|
db))
|
||||||
|
db)))
|
||||||
|
|
||||||
|
(defn remove-pending-message
|
||||||
|
[_ [_ message]]
|
||||||
|
(dispatch [:pending-message-remove message]))
|
||||||
|
|
||||||
|
(register-handler :message-delivered
|
||||||
|
[(after (save-message-status! :delivered))
|
||||||
|
(after remove-pending-message)]
|
||||||
|
(update-message-status :delivered))
|
||||||
|
|
||||||
(register-handler :message-failed
|
(register-handler :message-failed
|
||||||
(after (save-message-status! :failed))
|
(after (save-message-status! :failed))
|
||||||
|
@ -139,10 +240,6 @@
|
||||||
(after (save-message-status! :sent))
|
(after (save-message-status! :sent))
|
||||||
(update-message-status :sent))
|
(update-message-status :sent))
|
||||||
|
|
||||||
(register-handler :message-delivered
|
|
||||||
(after (save-message-status! :delivered))
|
|
||||||
(update-message-status :delivered))
|
|
||||||
|
|
||||||
(register-handler :message-seen
|
(register-handler :message-seen
|
||||||
[(after (save-message-status! :seen))
|
[(after (save-message-status! :seen))
|
||||||
(after (fn [_ [_ {:keys [chat-id]}]]
|
(after (fn [_ [_ {:keys [chat-id]}]]
|
||||||
|
@ -150,16 +247,39 @@
|
||||||
(update-message-status :seen))
|
(update-message-status :seen))
|
||||||
|
|
||||||
(register-handler :pending-message-upsert
|
(register-handler :pending-message-upsert
|
||||||
(u/side-effect!
|
(after
|
||||||
(fn [_ [_ pending-message]]
|
(fn [_ [_ {:keys [type id] :as pending-message}]]
|
||||||
(pending-messages/upsert-pending-message! pending-message))))
|
(pending-messages/add-pending-message! pending-message)
|
||||||
|
(when (#{:message :group-message} type)
|
||||||
|
(messages/update-message! {:message-id id
|
||||||
|
:delivery-status :pending}))))
|
||||||
|
(fn [db [_ {:keys [type id to groupd-id]}]]
|
||||||
|
(if (#{:message :group-message} type)
|
||||||
|
(let [chat-id (or groupd-id to)
|
||||||
|
current-status (get-in db [:message-status chat-id id])]
|
||||||
|
(if-not (= :seen current-status)
|
||||||
|
(assoc-in db [:message-status chat-id id] :pending)
|
||||||
|
db))
|
||||||
|
db)))
|
||||||
|
|
||||||
(register-handler :pending-message-remove
|
(register-handler :pending-message-remove
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ message-id]]
|
(fn [_ [_ message]]
|
||||||
(pending-messages/remove-pending-message! message-id))))
|
(pending-messages/remove-pending-message! message))))
|
||||||
|
|
||||||
(register-handler :send-transaction!
|
(register-handler :contact-request-received
|
||||||
(u/side-effect!
|
(u/side-effect!
|
||||||
(fn [_ [_ amount message]]
|
(fn [_ [_ {:keys [from payload]}]]
|
||||||
(println :send-transacion! amount message))))
|
(when from
|
||||||
|
(let [{{:keys [name profile-image address]} :contact
|
||||||
|
{:keys [public private]} :keypair} payload
|
||||||
|
|
||||||
|
contact {:whisper-identity from
|
||||||
|
:public-key public
|
||||||
|
:private-key private
|
||||||
|
:address address
|
||||||
|
:photo-path profile-image
|
||||||
|
:name name
|
||||||
|
:pending true}]
|
||||||
|
(dispatch [:watch-contact contact])
|
||||||
|
(dispatch [:add-contacts [contact]]))))))
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
(ns status-im.protocol.listeners
|
||||||
|
(:require [cljs.reader :as r]
|
||||||
|
[status-im.protocol.ack :as ack]
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[status-im.protocol.encryption :as e]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
|
|
||||||
|
(defn- parse-payload [payload]
|
||||||
|
(debug :parse-payload)
|
||||||
|
(r/read-string (u/to-utf8 payload)))
|
||||||
|
|
||||||
|
(defn- parse-content [key {:keys [content]} was-encrypted?]
|
||||||
|
(debug :parse-content
|
||||||
|
"Key exitsts:" (not (nil? key))
|
||||||
|
"Content exists:" (not (nil? content)))
|
||||||
|
(if (and (not was-encrypted?) key content)
|
||||||
|
(r/read-string (e/decrypt key content))
|
||||||
|
content))
|
||||||
|
|
||||||
|
(defn message-listener
|
||||||
|
[{:keys [web3 identity callback keypair]}]
|
||||||
|
(fn [error js-message]
|
||||||
|
;; todo handle error
|
||||||
|
(when error
|
||||||
|
(debug :listener-error error))
|
||||||
|
(when-not error
|
||||||
|
(debug :message-received)
|
||||||
|
(let [{:keys [from payload to] :as message}
|
||||||
|
(js->clj js-message :keywordize-keys true)]
|
||||||
|
(when-not (= identity from)
|
||||||
|
(let [{:keys [type ack?] :as payload'}
|
||||||
|
(parse-payload payload)
|
||||||
|
|
||||||
|
content (parse-content (:private keypair) payload' (not= "0x0" to))
|
||||||
|
payload'' (assoc payload' :content content)
|
||||||
|
|
||||||
|
message' (assoc message :payload payload'')]
|
||||||
|
(callback (if ack? :ack type) message')
|
||||||
|
(ack/check-ack! web3 from payload'' identity)))))))
|
|
@ -0,0 +1,47 @@
|
||||||
|
(ns status-im.protocol.message
|
||||||
|
(:require [cljs.spec :as s]))
|
||||||
|
|
||||||
|
(s/def :message/ttl (s/and int? pos?))
|
||||||
|
(s/def :message/from string?)
|
||||||
|
(s/def :message/to string?)
|
||||||
|
(s/def :message/message-id string?)
|
||||||
|
(s/def :message/requires-ack? boolean?)
|
||||||
|
(s/def :keypair/private string?)
|
||||||
|
(s/def :keypair/public string?)
|
||||||
|
(s/def :message/keypair (s/keys :req-un [:keypair/private
|
||||||
|
:keypair/public]))
|
||||||
|
(s/def :message/topics (s/* string?))
|
||||||
|
|
||||||
|
(s/def :payload/content (s/or :string-message string?
|
||||||
|
:command map?))
|
||||||
|
(s/def :payload/content-type string?)
|
||||||
|
(s/def :payload/timestamp (s/and int? pos?))
|
||||||
|
(s/def :payload/new-keypair :message/keypair)
|
||||||
|
|
||||||
|
(s/def :group-message/type
|
||||||
|
#{:group-message :group-invitation :add-group-identity
|
||||||
|
:remove-group-identity :leave-group :update-group})
|
||||||
|
|
||||||
|
(s/def :discovery-message/type #{:online :status :discovery :contact-request})
|
||||||
|
|
||||||
|
(s/def :message/type
|
||||||
|
(s/or :group :group-message/type
|
||||||
|
:discovery :discovery-message/type
|
||||||
|
:user #{:message}))
|
||||||
|
|
||||||
|
(s/def :message/payload
|
||||||
|
(s/keys :opt-un [:message/type
|
||||||
|
:payload/content
|
||||||
|
:payload/content-type
|
||||||
|
:payload/new-keypair
|
||||||
|
:payload/timestamp]))
|
||||||
|
|
||||||
|
(s/def :protocol/message
|
||||||
|
(s/keys :req-un [:message/from :message/message-id]
|
||||||
|
:opt-un [:message/to :message/topics :message/requires-ack?
|
||||||
|
:message/keypair :message/ttl :message/payload]))
|
||||||
|
|
||||||
|
(s/def :chat-message/payload
|
||||||
|
(s/keys :req-un [:payload/content :payload/content-type :payload/timestamp]))
|
||||||
|
|
||||||
|
(s/def :options/web3 #(not (nil? %)))
|
|
@ -5,64 +5,3 @@
|
||||||
[status-im.models.protocol :refer [stored-identity]]
|
[status-im.models.protocol :refer [stored-identity]]
|
||||||
[status-im.persistence.simple-kv-store :as kv]
|
[status-im.persistence.simple-kv-store :as kv]
|
||||||
[status-im.models.chats :refer [active-group-chats]]))
|
[status-im.models.chats :refer [active-group-chats]]))
|
||||||
|
|
||||||
|
|
||||||
(defn make-handler [db]
|
|
||||||
{:ethereum-rpc-url ethereum-rpc-url
|
|
||||||
:identity (stored-identity db)
|
|
||||||
;; :active-group-ids is never used in protocol
|
|
||||||
:active-group-ids (active-group-chats)
|
|
||||||
:storage kv/kv-store
|
|
||||||
:handler (fn [{:keys [event-type] :as event}]
|
|
||||||
(case event-type
|
|
||||||
:initialized (let [{:keys [identity]} event]
|
|
||||||
(dispatch [:protocol-initialized identity]))
|
|
||||||
:message-received (let [{:keys [from to payload]} event]
|
|
||||||
(dispatch [:received-message (assoc payload :chat-id from
|
|
||||||
:from from
|
|
||||||
:to to)]))
|
|
||||||
:contact-request (let [{:keys [from payload]} event]
|
|
||||||
(dispatch [:contact-request-received (assoc payload :from from)]))
|
|
||||||
:message-delivered (let [{:keys [from message-id]} event]
|
|
||||||
(dispatch [:message-delivered {:whisper-identity from
|
|
||||||
:message-id message-id}]))
|
|
||||||
:message-seen (let [{:keys [from message-id]} event]
|
|
||||||
(dispatch [:message-seen {:whisper-identity from
|
|
||||||
:message-id message-id}]))
|
|
||||||
:message-failed (let [{:keys [chat-id message-id]} event]
|
|
||||||
(dispatch [:message-failed {:chat-id chat-id
|
|
||||||
:message-id message-id}]))
|
|
||||||
:message-sent (let [{:keys [chat-id message-id] :as data} event]
|
|
||||||
(dispatch [:message-sent {:chat-id chat-id
|
|
||||||
:message-id message-id}]))
|
|
||||||
:user-discovery-keypair (let [{:keys [from]} event]
|
|
||||||
(dispatch [:contact-keypair-received from]))
|
|
||||||
:pending-message-upsert (let [{message :message} event]
|
|
||||||
(dispatch [:pending-message-upsert message]))
|
|
||||||
:pending-message-remove (let [{:keys [message-id]} event]
|
|
||||||
(dispatch [:pending-message-remove message-id]))
|
|
||||||
:new-group-chat (let [{:keys [from group-id identities group-name]} event]
|
|
||||||
(dispatch [:group-chat-invite-received from group-id identities group-name]))
|
|
||||||
:new-group-message (let [{from :from
|
|
||||||
group-id :group-id
|
|
||||||
payload :payload} event]
|
|
||||||
(dispatch [:received-message (assoc payload
|
|
||||||
:chat-id group-id
|
|
||||||
:from from)]))
|
|
||||||
:group-chat-invite-acked (let [{:keys [from group-id ack-message-id]} event]
|
|
||||||
(dispatch [:group-chat-invite-acked from group-id ack-message-id]))
|
|
||||||
:group-new-participant (let [{:keys [group-id identity from message-id]} event]
|
|
||||||
(dispatch [:participant-invited-to-group from group-id identity message-id]))
|
|
||||||
:group-removed-participant (let [{:keys [group-id identity from message-id]} event]
|
|
||||||
(dispatch [:participant-removed-from-group from group-id identity message-id]))
|
|
||||||
:removed-from-group (let [{:keys [group-id from message-id]} event]
|
|
||||||
(dispatch [:you-removed-from-group from group-id message-id]))
|
|
||||||
:participant-left-group (let [{:keys [group-id from message-id]} event]
|
|
||||||
(dispatch [:participant-left-group from group-id message-id]))
|
|
||||||
:discover-response (let [{:keys [from payload]} event]
|
|
||||||
(dispatch [:discovery-response-received from payload]))
|
|
||||||
:contact-update (let [{:keys [from payload]} event]
|
|
||||||
(dispatch [:contact-update-received from payload]))
|
|
||||||
:contact-online (let [{:keys [from payload]} event]
|
|
||||||
(dispatch [:contact-online-received from payload]))
|
|
||||||
(log/info "Don't know how to handle" event-type)))})
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
(ns status-im.protocol.validation)
|
||||||
|
|
||||||
|
(defn- fline [and-form] (:line (meta and-form)))
|
||||||
|
|
||||||
|
(defmacro valid? [spec x]
|
||||||
|
`(let [v?# (cljs.spec/valid? ~spec ~x)]
|
||||||
|
(when-not v?#
|
||||||
|
(let [explanation# (cljs.spec/explain-str ~spec ~x)]
|
||||||
|
(taoensso.timbre/log! :error :p
|
||||||
|
[explanation#]
|
||||||
|
~{:?line (fline &form)})))
|
||||||
|
v?#))
|
|
@ -0,0 +1,2 @@
|
||||||
|
(ns status-im.protocol.validation
|
||||||
|
(:require-macros [status-im.protocol.validation :as macros]))
|
|
@ -0,0 +1,193 @@
|
||||||
|
(ns status-im.protocol.web3.delivery
|
||||||
|
(:require-macros [cljs.core.async.macros :refer [go-loop go]])
|
||||||
|
(:require [cljs.core.async :refer [<! timeout]]
|
||||||
|
[status-im.protocol.web3.transport :as t]
|
||||||
|
[status-im.protocol.web3.utils :as u]
|
||||||
|
[status-im.protocol.encryption :as e]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[taoensso.timbre :refer-macros [debug] :as timbre]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]
|
||||||
|
[clojure.set :as set]))
|
||||||
|
|
||||||
|
(defonce loop-state (atom nil))
|
||||||
|
(defonce messages (atom {}))
|
||||||
|
|
||||||
|
(defn prepare-message
|
||||||
|
[{:keys [payload keypair to] :as message}]
|
||||||
|
(debug :prepare-message!)
|
||||||
|
(let [{:keys [public]} keypair
|
||||||
|
|
||||||
|
content (:content payload)
|
||||||
|
content' (if (and (not to) public content)
|
||||||
|
(e/encrypt public (prn-str content))
|
||||||
|
content)
|
||||||
|
|
||||||
|
payload' (-> message
|
||||||
|
(select-keys [:message-id :requires-ack? :type])
|
||||||
|
(merge payload)
|
||||||
|
(assoc :content content')
|
||||||
|
prn-str
|
||||||
|
u/from-utf8)]
|
||||||
|
(-> message (select-keys [:from :to :topics :ttl])
|
||||||
|
(assoc :payload payload'))))
|
||||||
|
|
||||||
|
(s/def :shh/pending-message
|
||||||
|
(s/keys :req-un [:message/from :shh/payload :message/topics]
|
||||||
|
:opt-un [:message/ttl :message/to]))
|
||||||
|
|
||||||
|
(defonce pending-mesage-callback (atom nil))
|
||||||
|
(defonce recipient->pending-message (atom {}))
|
||||||
|
|
||||||
|
(defn set-pending-mesage-callback!
|
||||||
|
[callback]
|
||||||
|
(reset! pending-mesage-callback callback))
|
||||||
|
|
||||||
|
(defn add-pending-message!
|
||||||
|
[web3 {:keys [type message-id requires-ack? to ack?] :as message}]
|
||||||
|
{:pre [(valid? :protocol/message message)]}
|
||||||
|
(go
|
||||||
|
(debug :add-pending-message!)
|
||||||
|
;; encryption can take some time, better to run asynchronously
|
||||||
|
(let [message' (prepare-message message)]
|
||||||
|
(when (valid? :shh/pending-message message')
|
||||||
|
(let [group-id (get-in message [:payload :group-id])
|
||||||
|
pending-message {:id message-id
|
||||||
|
:ack? (boolean ack?)
|
||||||
|
:message message'
|
||||||
|
:to to
|
||||||
|
:type type
|
||||||
|
:group-id group-id
|
||||||
|
:requires-ack? (boolean requires-ack?)
|
||||||
|
:attempts 0
|
||||||
|
:was-sent? false}]
|
||||||
|
(when (and @pending-mesage-callback requires-ack?)
|
||||||
|
(@pending-mesage-callback :pending pending-message))
|
||||||
|
(swap! messages assoc-in [web3 message-id to] pending-message)
|
||||||
|
(when to
|
||||||
|
(swap! recipient->pending-message
|
||||||
|
update to set/union #{[web3 message-id to]})))))))
|
||||||
|
|
||||||
|
(s/def :delivery/pending-message
|
||||||
|
(s/keys :req-un [:message/to :message/from :shh/payload
|
||||||
|
:message/requires-ack? :payload/ack? ::id :message/topics
|
||||||
|
::attempts ::was-sent?]))
|
||||||
|
|
||||||
|
(defn add-prepeared-pending-message!
|
||||||
|
[web3 {:keys [id to] :as pending-message}]
|
||||||
|
{:pre [(valid? :delivery/pending-message pending-message)]}
|
||||||
|
(debug :add-prepeared-pending-message!)
|
||||||
|
(let [message (select-keys pending-message [:from :to :topics :payload])
|
||||||
|
pending-message' (assoc pending-message :message message)]
|
||||||
|
(swap! messages assoc-in [web3 id to] pending-message')
|
||||||
|
(when to
|
||||||
|
(swap! recipient->pending-message
|
||||||
|
update to set/union #{[web3 id to]}))))
|
||||||
|
|
||||||
|
(defn remove-pending-message! [web3 id to]
|
||||||
|
(swap! messages update-in [web3 id] dissoc to)
|
||||||
|
(when to
|
||||||
|
(swap! recipient->pending-message
|
||||||
|
update to set/difference #{[web3 id to]})))
|
||||||
|
|
||||||
|
(defn message-was-sent! [web3 id to]
|
||||||
|
(let [messages' (swap! messages update-in [web3 id to]
|
||||||
|
(fn [message]
|
||||||
|
(assoc message :was-sent? true
|
||||||
|
:attemps 1)))]
|
||||||
|
(when @pending-mesage-callback
|
||||||
|
(@pending-mesage-callback :sent (get-in messages' [web3 id to])))))
|
||||||
|
|
||||||
|
(defn attempt-was-made! [web3 id to]
|
||||||
|
(debug :attempt-was-made id)
|
||||||
|
(swap! messages update-in [web3 id to]
|
||||||
|
(fn [{:keys [attempts] :as data}]
|
||||||
|
(assoc data :attempts (inc attempts)
|
||||||
|
:last-attempt (u/timestamp)))))
|
||||||
|
|
||||||
|
(defn delivery-callback
|
||||||
|
[web3 {:keys [id requires-ack? to]}]
|
||||||
|
(fn [error _]
|
||||||
|
(when error (timbre/error :shh-post-error error))
|
||||||
|
(when-not error
|
||||||
|
(debug :delivery-callback)
|
||||||
|
(message-was-sent! web3 id to)
|
||||||
|
(when-not requires-ack?
|
||||||
|
(remove-pending-message! web3 id to)))))
|
||||||
|
|
||||||
|
(s/def ::pos-int (s/and pos? int?))
|
||||||
|
(s/def ::delivery-loop-ms-interval ::pos-int)
|
||||||
|
(s/def ::ack-not-received-s-interval ::pos-int)
|
||||||
|
(s/def ::max-attempts-number ::pos-int)
|
||||||
|
(s/def ::default-ttl ::pos-int)
|
||||||
|
(s/def ::send-online-s-interval ::pos-int)
|
||||||
|
(s/def ::online-message fn?)
|
||||||
|
|
||||||
|
(s/def ::delivery-options
|
||||||
|
(s/keys :req-un [::delivery-loop-ms-interval ::ack-not-received-s-interval
|
||||||
|
::max-attempts-number ::default-ttl ::send-online-s-interval]
|
||||||
|
:opt-un [::online-message]))
|
||||||
|
|
||||||
|
(defn should-be-retransmitted?
|
||||||
|
"Checks if messages should be transmitted again."
|
||||||
|
[{:keys [ack-not-received-s-interval max-attempts-number]}
|
||||||
|
{:keys [was-sent? attempts last-attempt]}]
|
||||||
|
(if-not was-sent?
|
||||||
|
;; message was not sent succesfully via web3.shh, but maybe
|
||||||
|
;; better to do this only when we receive error from shh.post
|
||||||
|
;; todo add some notification about network issues
|
||||||
|
(<= attempts (* 5 max-attempts-number))
|
||||||
|
(and
|
||||||
|
;; if message was not send lees then max-attempts-number times
|
||||||
|
;; continue attempts
|
||||||
|
(<= attempts max-attempts-number)
|
||||||
|
;; check retransmition interval
|
||||||
|
(<= (+ last-attempt (* 1000 ack-not-received-s-interval)) (u/timestamp)))))
|
||||||
|
|
||||||
|
(defn- check-ttl
|
||||||
|
[message message-type ttl-config default-ttl]
|
||||||
|
(update message :ttl #(or % ((keyword message-type) ttl-config) default-ttl)))
|
||||||
|
|
||||||
|
(defn run-delivery-loop!
|
||||||
|
[web3 {:keys [delivery-loop-ms-interval default-ttl ttl-config
|
||||||
|
send-online-s-interval online-message]
|
||||||
|
:as options}]
|
||||||
|
{:pre [(valid? ::delivery-options options)]}
|
||||||
|
(debug :run-delivery-loop!)
|
||||||
|
(let [previous-stop-flag @loop-state
|
||||||
|
stop? (atom false)]
|
||||||
|
;; stop previous delivery loop if it exists
|
||||||
|
(when previous-stop-flag
|
||||||
|
(reset! previous-stop-flag true))
|
||||||
|
;; reset stop flag for a new loop
|
||||||
|
(reset! loop-state stop?)
|
||||||
|
;; go go!!!
|
||||||
|
(debug :init-loop)
|
||||||
|
(go-loop [_ nil]
|
||||||
|
(doseq [[_ messages] (@messages web3)]
|
||||||
|
(doseq [[_ {:keys [id message to type] :as data}] messages]
|
||||||
|
;; check each message asynchronously
|
||||||
|
(go
|
||||||
|
(when (should-be-retransmitted? options data)
|
||||||
|
(try
|
||||||
|
(let [message' (check-ttl message type ttl-config default-ttl)
|
||||||
|
callback (delivery-callback web3 data)]
|
||||||
|
(t/post-message! web3 message' callback))
|
||||||
|
(catch :default err
|
||||||
|
(timbre/error :post-message-error err))
|
||||||
|
(finally
|
||||||
|
(attempt-was-made! web3 id to)))))))
|
||||||
|
(when-not @stop?
|
||||||
|
(recur (<! (timeout delivery-loop-ms-interval)))))
|
||||||
|
(go-loop [_ nil]
|
||||||
|
(when-not @stop?
|
||||||
|
(online-message)
|
||||||
|
(recur (<! (timeout (* 1000 send-online-s-interval))))))))
|
||||||
|
|
||||||
|
(defn reset-pending-messages! [to]
|
||||||
|
(doseq [key (@recipient->pending-message to)]
|
||||||
|
(swap! messages
|
||||||
|
(fn [messages]
|
||||||
|
(when (get-in messages key)
|
||||||
|
(update-in messages key assoc
|
||||||
|
:last-attempt 0
|
||||||
|
:attempts 0))))))
|
|
@ -0,0 +1,29 @@
|
||||||
|
(ns status-im.protocol.web3.filtering
|
||||||
|
(:require [status-im.protocol.web3.utils :as u]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
|
|
||||||
|
(def status-topic "status-dapp-topic")
|
||||||
|
(defonce filters (atom {}))
|
||||||
|
|
||||||
|
(s/def ::options (s/keys :opt-un [:message/to :message/topics]))
|
||||||
|
|
||||||
|
(defn remove-filter! [web3 options]
|
||||||
|
(when-let [filter (get-in @filters [web3 options])]
|
||||||
|
(.stopWatching filter)
|
||||||
|
(debug :stop-watching options)
|
||||||
|
(swap! filters update web3 dissoc options)))
|
||||||
|
|
||||||
|
(defn add-filter!
|
||||||
|
[web3 options callback]
|
||||||
|
(remove-filter! web3 options)
|
||||||
|
(debug :add-filter options)
|
||||||
|
(let [filter (.filter (u/shh web3)
|
||||||
|
(clj->js options)
|
||||||
|
callback)]
|
||||||
|
(swap! filters assoc-in [web3 options] filter)))
|
||||||
|
|
||||||
|
(defn remove-all-filters! []
|
||||||
|
(doseq [[web3 filters] @filters]
|
||||||
|
(doseq [options (keys filters)]
|
||||||
|
(remove-filter! web3 options))))
|
|
@ -0,0 +1,17 @@
|
||||||
|
(ns status-im.protocol.web3.transport
|
||||||
|
(:require [status-im.protocol.web3.utils :as u]
|
||||||
|
[cljs.spec :as s]
|
||||||
|
[status-im.protocol.validation :refer-macros [valid?]]
|
||||||
|
[taoensso.timbre :refer-macros [debug]]))
|
||||||
|
|
||||||
|
(s/def :shh/payload string?)
|
||||||
|
(s/def :shh/message
|
||||||
|
(s/keys
|
||||||
|
:req-un [:shh/payload :message/ttl :message/from :message/topics]
|
||||||
|
:opt-un [:message/to]))
|
||||||
|
|
||||||
|
(defn post-message!
|
||||||
|
[web3 message callback]
|
||||||
|
{:pre [(valid? :shh/message message)]}
|
||||||
|
(debug :post-message web3 message)
|
||||||
|
(.post (u/shh web3) (clj->js message) callback))
|
|
@ -0,0 +1,23 @@
|
||||||
|
(ns status-im.protocol.web3.utils
|
||||||
|
(:require cljsjs.web3
|
||||||
|
[cljs-time.core :refer [now]]
|
||||||
|
[cljs-time.coerce :refer [to-long]]))
|
||||||
|
|
||||||
|
(def web3 js/Web3)
|
||||||
|
(def status-app-topic "status-app")
|
||||||
|
|
||||||
|
(defn from-utf8 [s]
|
||||||
|
(.fromUtf8 web3.prototype s))
|
||||||
|
|
||||||
|
(defn to-utf8 [s]
|
||||||
|
(.toUtf8 web3.prototype s))
|
||||||
|
|
||||||
|
(defn shh [web3]
|
||||||
|
(.-shh web3))
|
||||||
|
|
||||||
|
(defn make-web3 [rpc-url]
|
||||||
|
(->> (web3.providers.HttpProvider. rpc-url)
|
||||||
|
(web3.)))
|
||||||
|
|
||||||
|
(defn timestamp []
|
||||||
|
(to-long (now)))
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
;messages
|
;messages
|
||||||
:status-sending "Sending"
|
:status-sending "Sending"
|
||||||
|
:status-pending "Sending"
|
||||||
:status-sent "Sent"
|
:status-sent "Sent"
|
||||||
:status-seen-by-everyone "Seen by everyone"
|
:status-seen-by-everyone "Seen by everyone"
|
||||||
:status-seen "Seen"
|
:status-seen "Seen"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
(ns status-im.utils.handlers
|
(ns status-im.utils.handlers
|
||||||
(:require [re-frame.core :refer [after dispatch debug] :as re-core]
|
(:require [re-frame.core :refer [after dispatch debug] :as re-core]
|
||||||
[re-frame.utils :refer [log]]))
|
[re-frame.utils :refer [log]]
|
||||||
|
[clojure.string :as str]))
|
||||||
|
|
||||||
(defn side-effect!
|
(defn side-effect!
|
||||||
"Middleware for handlers that will not affect db."
|
"Middleware for handlers that will not affect db."
|
||||||
|
@ -24,3 +25,10 @@
|
||||||
([name handler] (register-handler name nil handler))
|
([name handler] (register-handler name nil handler))
|
||||||
([name middleware handler]
|
([name middleware handler]
|
||||||
(re-core/register-handler name [debug-handlers-names middleware] handler)))
|
(re-core/register-handler name [debug-handlers-names middleware] handler)))
|
||||||
|
|
||||||
|
(defn get-hashtags [status]
|
||||||
|
(if status
|
||||||
|
(let [hashtags (map #(str/lower-case (subs % 1))
|
||||||
|
(re-seq #"#[^ !?,;:.]+" status))]
|
||||||
|
(set (or hashtags [])))
|
||||||
|
#{}))
|
||||||
|
|
Loading…
Reference in New Issue