Merge pull request #216 from status-im/protocol-rework
Protocol rework
Former-commit-id: 677379f655
This commit is contained in:
commit
82f4b7748d
10
project.clj
10
project.clj
|
@ -3,8 +3,8 @@
|
|||
:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.9.0-alpha7"]
|
||||
[org.clojure/clojurescript "1.9.76"]
|
||||
:dependencies [[org.clojure/clojure "1.9.0-alpha11"]
|
||||
[org.clojure/clojurescript "1.9.227"]
|
||||
[reagent "0.5.1" :exclusions [cljsjs/react]]
|
||||
[re-frame "0.7.0"]
|
||||
[prismatic/schema "1.0.4"]
|
||||
|
@ -13,7 +13,11 @@
|
|||
[natal-shell "0.3.0"]
|
||||
[com.andrewmcveigh/cljs-time "0.4.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"]
|
||||
[lein-figwheel "0.5.0-2"]
|
||||
[lein-voom "0.1.0-20160311_203101-g259fbfc"]]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:require [status-im.models.accounts :as accounts-model]
|
||||
[re-frame.core :refer [register-handler after dispatch dispatch-sync debug]]
|
||||
[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.utils.types :refer [json->clj]]
|
||||
[status-im.persistence.simple-kv-store :as kv]
|
||||
|
@ -33,14 +33,17 @@
|
|||
(storage/put kv/kv-store :password password))
|
||||
|
||||
(defn account-created [result password]
|
||||
(let [data (json->clj result)
|
||||
(let [data (json->clj result)
|
||||
public-key (:pubkey data)
|
||||
address (:address data)
|
||||
mnemonic (:mnemonic data)
|
||||
account {:public-key public-key
|
||||
:address address
|
||||
:name address
|
||||
:photo-path (identicon public-key)}]
|
||||
address (:address data)
|
||||
mnemonic (:mnemonic data)
|
||||
{:keys [public private]} (protocol/new-keypair!)
|
||||
account {:public-key public-key
|
||||
:address address
|
||||
:name address
|
||||
:updates-public-key public
|
||||
:updates-private-key private
|
||||
:photo-path (identicon public-key)}]
|
||||
(log/debug "account-created")
|
||||
(when (not (str/blank? public-key))
|
||||
(do
|
||||
|
@ -61,15 +64,25 @@
|
|||
(accounts-model/save-accounts [(get accounts current-account-id)] true))
|
||||
|
||||
(defn send-account-update
|
||||
[{:keys [current-account-id accounts]} _]
|
||||
(api/send-account-update (get accounts current-account-id)))
|
||||
[{:keys [current-account-id current-public-key web3 accounts]} _]
|
||||
(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
|
||||
:account-update
|
||||
(-> (fn [{:keys [current-account-id accounts] :as db} [_ data]]
|
||||
(let [data (assoc data :last-updated (time/now-ms))
|
||||
account (-> (get accounts current-account-id)
|
||||
(merge data))]
|
||||
(let [data (assoc data :last-updated (time/now-ms))
|
||||
account (-> (get accounts current-account-id)
|
||||
(merge data))]
|
||||
(assoc-in db [:accounts current-account-id] account)))
|
||||
((after save-account-to-realm!))
|
||||
((after send-account-update))))
|
||||
|
@ -79,7 +92,7 @@
|
|||
(u/side-effect!
|
||||
(fn [{:keys [current-account-id accounts]} _]
|
||||
(let [{:keys [last-updated]} (get accounts current-account-id)
|
||||
now (time/now-ms)
|
||||
now (time/now-ms)
|
||||
needs-update? (> (- now last-updated) time/week)]
|
||||
(log/info "Need to send account-update: " needs-update?)
|
||||
(when needs-update?
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
[status-im.components.styles :refer [default-chat-color]]
|
||||
[status-im.chat.suggestions :as suggestions]
|
||||
[status-im.protocol.api :as api]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.models.chats :as chats]
|
||||
[status-im.models.messages :as messages]
|
||||
[status-im.models.pending-messages :as pending-messages]
|
||||
|
@ -174,7 +175,7 @@
|
|||
|
||||
(defn init-console-chat
|
||||
[{:keys [chats] :as db} existing-account?]
|
||||
(let [chat-id "console"
|
||||
(let [chat-id "console"
|
||||
new-chat sign-up-service/console-chat]
|
||||
(if (chats chat-id)
|
||||
db
|
||||
|
@ -264,11 +265,6 @@
|
|||
(after #(dispatch [:load-unviewed-messages!]))
|
||||
((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
|
||||
[{:keys [current-chat-id] :as db} [_ _ id]]
|
||||
(let [chat-id (or id current-chat-id)
|
||||
|
@ -324,9 +320,9 @@
|
|||
(dispatch [::start-chat! contact-id options navigation-type])))))
|
||||
|
||||
(register-handler :add-chat
|
||||
(-> prepare-chat
|
||||
((enrich add-chat))
|
||||
((after save-new-chat!))))
|
||||
(-> prepare-chat
|
||||
((enrich add-chat))
|
||||
((after save-new-chat!))))
|
||||
|
||||
(register-handler :switch-command-suggestions!
|
||||
(u/side-effect!
|
||||
|
@ -335,12 +331,8 @@
|
|||
(dispatch [:set-chat-input-text text])))))
|
||||
|
||||
(defn remove-chat
|
||||
[{:keys [current-chat-id] :as db} _]
|
||||
(update db :chats dissoc current-chat-id))
|
||||
|
||||
(defn notify-about-leaving!
|
||||
[{:keys [current-chat-id]} _]
|
||||
(api/leave-group-chat current-chat-id))
|
||||
[db [_ chat-id]]
|
||||
(update db :chats dissoc chat-id))
|
||||
|
||||
; todo do we really need this message?
|
||||
(defn leaving-message!
|
||||
|
@ -353,27 +345,47 @@
|
|||
:content-type text-content-type}))
|
||||
|
||||
(defn delete-messages!
|
||||
[{:keys [current-chat-id]} _]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(r/delete :account (r/get-by-field :account :message :chat-id current-chat-id)))))
|
||||
[{:keys [current-chat-id]} [_ chat-id]]
|
||||
(let [id (or chat-id current-chat-id)]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(r/delete :account
|
||||
(r/get-by-field :account :message :chat-id id))))))
|
||||
|
||||
(defn delete-chat!
|
||||
[{:keys [current-chat-id]} _]
|
||||
[_ [_ chat-id]]
|
||||
(r/write :account
|
||||
(fn [] :account
|
||||
(->> (r/get-by-field :account :chat :chat-id current-chat-id)
|
||||
(r/single)
|
||||
(r/delete :account)))))
|
||||
(when-let [chat (->> (r/get-by-field :account :chat :chat-id chat-id)
|
||||
(r/single))]
|
||||
(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
|
||||
;; todo oreder of operations tbd
|
||||
(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
|
||||
;; todo uncomment
|
||||
;((after notify-about-leaving!))
|
||||
;((after leaving-message!))
|
||||
((after delete-messages!))
|
||||
((after remove-pending-messages!))
|
||||
((after delete-chat!))))
|
||||
|
||||
(defn edit-mode-handler [mode]
|
||||
|
@ -405,13 +417,18 @@
|
|||
(assoc db :layout-height h)))
|
||||
|
||||
(register-handler :send-seen!
|
||||
(after (fn [_ [_ chat-id message-id]]
|
||||
(dispatch [:message-seen {:message-id message-id
|
||||
:chat-id chat-id}])))
|
||||
(after (fn [_ [_ options]]
|
||||
(dispatch [:message-seen options])))
|
||||
(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)
|
||||
(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
|
||||
(fn [{:keys [current-chat-id] :as db} [_ url]]
|
||||
|
|
|
@ -27,23 +27,35 @@
|
|||
(defn receive-message
|
||||
[db [_ {:keys [from group-id chat-id message-id] :as message}]]
|
||||
(let [same-message (messages/get-message message-id)
|
||||
current-identity (get-current-identity db)]
|
||||
(when-not (or same-message (= from current-identity))
|
||||
current-identity (get-current-identity db)
|
||||
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))
|
||||
chat-id' (or chat-id from)
|
||||
previous-message (messages/get-last-message chat-id')
|
||||
message' (assoc (->> message
|
||||
(cu/check-author-direction previous-message)
|
||||
(check-preview))
|
||||
:chat-id chat-id')]
|
||||
(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-message message'])
|
||||
(when (= (:content-type message') content-type-command-request)
|
||||
(dispatch [:add-request chat-id' message']))
|
||||
(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
|
||||
(u/side-effect! receive-message))
|
||||
|
||||
|
|
|
@ -5,34 +5,15 @@
|
|||
[status-im.components.status :as status]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.utils.datetime :as time]
|
||||
[re-frame.core :refer [enrich after debug dispatch path]]
|
||||
[status-im.chat.suggestions :as suggestions]
|
||||
[status-im.models.commands :as commands]
|
||||
[re-frame.core :refer [enrich after dispatch path]]
|
||||
[status-im.chat.utils :as cu]
|
||||
[status-im.constants :refer [text-content-type
|
||||
content-type-command
|
||||
content-type-command-request
|
||||
default-number-of-messages]]
|
||||
[status-im.protocol.api :as api]
|
||||
[status-im.utils.logging :as log]))
|
||||
|
||||
(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)))))
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[taoensso.timbre :refer-macros [debug]]))
|
||||
|
||||
(defn prepare-command
|
||||
[identity chat-id {:keys [preview preview-string content command to-message]}]
|
||||
|
@ -52,13 +33,13 @@
|
|||
|
||||
(register-handler :send-chat-message
|
||||
(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]))
|
||||
text (get-in db [:chats current-chat-id :input-text])
|
||||
data {:commands staged-commands
|
||||
:message text
|
||||
:chat-id current-chat-id
|
||||
:identity identity
|
||||
:identity current-public-key
|
||||
:address current-account-id}]
|
||||
(dispatch [:clear-input current-chat-id])
|
||||
(cond
|
||||
|
@ -88,9 +69,9 @@
|
|||
|
||||
(register-handler :prepare-command!
|
||||
(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
|
||||
(prepare-command identity chat-id)
|
||||
(prepare-command current-public-key chat-id)
|
||||
(cu/check-author-direction db chat-id))]
|
||||
(dispatch [::clear-command chat-id (:id command)])
|
||||
(dispatch [::send-command! (assoc params :command command')])))))
|
||||
|
@ -115,7 +96,7 @@
|
|||
|
||||
(register-handler ::save-command!
|
||||
(u/side-effect!
|
||||
(fn [_ [_ {:keys [command chat-id]}]]
|
||||
(fn [{:keys [current-public-key]} [_ {:keys [command chat-id]}]]
|
||||
(messages/save-message
|
||||
chat-id
|
||||
(dissoc command :rendered-preview :to-message :has-handler)))))
|
||||
|
@ -177,22 +158,41 @@
|
|||
|
||||
(register-handler ::send-message!
|
||||
(u/side-effect!
|
||||
(fn [_ [_ {{:keys [message-type]
|
||||
:as message} :message
|
||||
chat-id :chat-id}]]
|
||||
(fn [{:keys [web3 chats]} [_ {{:keys [message-type]
|
||||
:as message} :message
|
||||
chat-id :chat-id}]]
|
||||
(when (and message (cu/not-console? chat-id))
|
||||
(if (= message-type :group-user-message)
|
||||
(api/send-group-user-message message)
|
||||
(api/send-user-message message))))))
|
||||
(let [message' (select-keys message [:from :message-id])
|
||||
payload (select-keys message [:timestamp :content :content-type])
|
||||
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!
|
||||
(u/side-effect!
|
||||
(fn [db [_ {:keys [chat-id command]}]]
|
||||
(let [{:keys [content]} command]
|
||||
(fn [{:keys [web3 current-public-key chats] :as db} [_ {:keys [chat-id command]}]]
|
||||
(let [{:keys [content message-id]} command]
|
||||
(when (cu/not-console? chat-id)
|
||||
(let [{:keys [group-chat]} (get-in db [:chats chat-id])
|
||||
message {:content content
|
||||
:content-type content-type-command}]
|
||||
(let [{:keys [public-key private-key]} (chats chat-id)
|
||||
{:keys [group-chat]} (get-in db [:chats chat-id])
|
||||
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
|
||||
(api/send-group-user-message (assoc message :group-id chat-id))
|
||||
(api/send-user-message (assoc message :to 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] chat-id)))))))))
|
||||
|
|
|
@ -262,7 +262,7 @@
|
|||
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)
|
||||
status (subscribe [:get-in [:message-user-statuses message-id my-identity]])]
|
||||
(r/create-class
|
||||
|
@ -271,7 +271,9 @@
|
|||
(when (and (not outgoing)
|
||||
(not= :seen (keyword @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
|
||||
(fn [{:keys [outgoing timestamp new-day group-chat] :as message}]
|
||||
[message-container message
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
[status-im.models.contacts :as contacts]
|
||||
[status-im.utils.crypt :refer [encrypt]]
|
||||
[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.phone-number :refer [format-phone-number]]
|
||||
[status-im.utils.handlers :as u]
|
||||
[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
|
||||
|
@ -32,15 +32,30 @@
|
|||
(contacts/save-contacts [contact]))
|
||||
|
||||
(defn watch-contact
|
||||
[_ [_ {:keys [whisper-identity]}]]
|
||||
(api/watch-user whisper-identity))
|
||||
[{:keys [web3]} [_ {:keys [whisper-identity public-key private-key]}]]
|
||||
(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))
|
||||
|
||||
(defn send-contact-request
|
||||
[{:keys [current-account-id accounts]} [_ contact]]
|
||||
(let [account (get accounts current-account-id)]
|
||||
(api/send-contact-request contact account)))
|
||||
[{:keys [current-public-key web3 current-account-id accounts]} [_ contact]]
|
||||
(let [{:keys [whisper-identity]} contact
|
||||
{: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!
|
||||
(-> (fn [db [_ {:keys [whisper-identity] :as contact}]]
|
||||
|
@ -107,7 +122,7 @@
|
|||
|
||||
(defn request-stored-contacts [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}
|
||||
(fn [{:keys [contacts]}]
|
||||
(let [contacts' (add-identity contacts-by-hash contacts)]
|
||||
|
@ -131,7 +146,7 @@
|
|||
|
||||
(defn add-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
|
||||
(map #(update-pending-status contacts %))
|
||||
(remove #(identities (:whisper-identity %)))
|
||||
|
@ -159,8 +174,7 @@
|
|||
(register-handler ::prepare-contact
|
||||
(-> add-new-contact
|
||||
((after save-contact))
|
||||
((after send-contact-request))
|
||||
((after watch-contact))))
|
||||
((after send-contact-request))))
|
||||
|
||||
(register-handler ::update-pending-contact
|
||||
(-> add-new-contact
|
||||
|
@ -168,9 +182,21 @@
|
|||
|
||||
(register-handler :add-pending-contact
|
||||
(u/side-effect!
|
||||
(fn [_ [_ {:keys [whisper-identity] :as contact}]]
|
||||
(let [contact (assoc contact :pending false)]
|
||||
(api/send-discovery-keypair whisper-identity)
|
||||
(fn [{:keys [current-public-key web3 current-account-id accounts]}
|
||||
[_ {:keys [whisper-identity] :as contact}]]
|
||||
(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])))))
|
||||
|
||||
(defn set-contact-identity-from-qr
|
||||
|
@ -181,34 +207,28 @@
|
|||
|
||||
(register-handler :contact-update-received
|
||||
(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 [db [_ from {{:keys [public-key last-updated name] :as account} :account}]]
|
||||
(let [prev-last-updated (get-in db [:contacts public-key :last-updated])]
|
||||
(if (<= prev-last-updated last-updated)
|
||||
(let [contact (-> (assoc account :whisper-identity public-key)
|
||||
(dissoc :public-key))]
|
||||
(fn [{:keys [chats] :as db} [_ {:keys [from payload]}]]
|
||||
(let [{:keys [content timestamp]} payload
|
||||
{:keys [status name profile-image]} (:profile content)
|
||||
prev-last-updated (get-in db [:contacts from :last-updated])]
|
||||
(if (<= prev-last-updated timestamp)
|
||||
(let [contact {:whisper-identity from
|
||||
:name name
|
||||
:photo-path profile-image
|
||||
:status status
|
||||
:last-updated timestamp}]
|
||||
(dispatch [:update-contact! contact])
|
||||
(dispatch [:update-chat! {:chat-id public-key
|
||||
:name name}])))))))
|
||||
(when (chats from)
|
||||
(dispatch [:update-chat! {:chat-id from
|
||||
:name name}]))))))))
|
||||
|
||||
(register-handler :contact-online-received
|
||||
(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])]
|
||||
(when (< prev-last-online last-online)
|
||||
(api/resend-pending-messages from)
|
||||
(when (< prev-last-online timestamp)
|
||||
(protocol/reset-pending-messages! 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
|
||||
(def app-db {:identity-password "replace-me-with-user-entered-password"
|
||||
:identity "me"
|
||||
:current-public-key "me"
|
||||
|
||||
:accounts {}
|
||||
:current-account-id nil
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
[status-im.utils.utils :refer [first-index]]
|
||||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[status-im.protocol.api :as api]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.navigation.handlers :as nav]
|
||||
[status-im.discovery.model :as discoveries]
|
||||
[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
|
||||
(fn [db _]
|
||||
|
@ -14,8 +16,8 @@
|
|||
(assoc :discoveries []))))
|
||||
|
||||
(defn calculate-priority [{:keys [chats contacts]} from payload]
|
||||
(let [contact (get contacts from)
|
||||
chat (get chats from)
|
||||
(let [contact (get contacts from)
|
||||
chat (get chats from)
|
||||
seen-online-recently? (< (- (time/now-ms) (get contact :last-online))
|
||||
time/hour)]
|
||||
(+ (time/now-ms) ;; message is newer => priority is higher
|
||||
|
@ -35,23 +37,37 @@
|
|||
|
||||
(register-handler :discovery-response-received
|
||||
(u/side-effect!
|
||||
(fn [db [_ from payload]]
|
||||
(let [{:keys [message-id name photo-path status hashtags]} payload
|
||||
discovery {:message-id message-id
|
||||
:name name
|
||||
:photo-path photo-path
|
||||
:status status
|
||||
:whisper-id from
|
||||
:tags (map #(hash-map :name %) hashtags)
|
||||
:last-updated (js/Date.)
|
||||
:priority (calculate-priority db from payload)}]
|
||||
(dispatch [:add-discovery discovery])))))
|
||||
(fn [{:keys [current-public-key] :as db} [_ {:keys [from payload]}]]
|
||||
(when-not (= current-public-key from)
|
||||
(let [{:keys [discovery-id profile hashtags]} payload
|
||||
{:keys [name profile-image status]} profile
|
||||
discovery {:message-id discovery-id
|
||||
:name name
|
||||
:photo-path profile-image
|
||||
:status status
|
||||
:whisper-id from
|
||||
:tags (map #(hash-map :name %) hashtags)
|
||||
:last-updated (js/Date.)
|
||||
:priority (calculate-priority db from payload)}]
|
||||
(dispatch [:add-discovery discovery]))))))
|
||||
|
||||
(register-handler :broadcast-status
|
||||
(u/side-effect!
|
||||
(fn [{:keys [current-account-id accounts]} [_ status hashtags]]
|
||||
(let [account (get accounts current-account-id)]
|
||||
(api/broadcast-discover-status account status hashtags)))))
|
||||
(fn [{:keys [current-public-key web3 current-account-id accounts]}
|
||||
[_ 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
|
||||
(fn [db [_ tag]]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[status-im.persistence.realm.core :as r]
|
||||
[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.models.contacts :as contacts]
|
||||
[status-im.models.messages :as messages]
|
||||
|
@ -84,16 +84,17 @@
|
|||
[{:keys [current-chat-id selected-participants] :as db} _]
|
||||
(let [chat (get-in db [:chats current-chat-id])]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(r/create :account
|
||||
:chat
|
||||
(update chat :contacts remove-identities selected-participants)
|
||||
true)))))
|
||||
(fn []
|
||||
(r/create :account
|
||||
:chat
|
||||
(update chat :contacts remove-identities selected-participants)
|
||||
true)))))
|
||||
|
||||
(defn notify-about-removing!
|
||||
[{:keys [current-chat-id selected-participants]} _]
|
||||
(doseq [participant selected-participants]
|
||||
(api/group-remove-participant current-chat-id participant)))
|
||||
;;todo implement
|
||||
))
|
||||
|
||||
(defn system-message [message-id content]
|
||||
{:from "system"
|
||||
|
@ -138,14 +139,33 @@
|
|||
(chats/chat-add-participants current-chat-id selected-participants))
|
||||
|
||||
(defn notify-about-new-members!
|
||||
[{:keys [current-chat-id selected-participants]} _]
|
||||
(doseq [identity selected-participants]
|
||||
(api/group-add-participant current-chat-id identity)))
|
||||
[{:keys [current-chat-id selected-participants
|
||||
current-public-key chats web3]} _]
|
||||
(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
|
||||
;; todo order of operations tbd
|
||||
(-> add-memebers
|
||||
((after add-members-to-realm!))
|
||||
;; todo uncomment
|
||||
;((after notify-about-new-members!))
|
||||
((after notify-about-new-members!))
|
||||
((enrich deselect-members))))
|
||||
|
|
|
@ -60,22 +60,22 @@
|
|||
(register-handler :initialize-db
|
||||
(fn [_ _]
|
||||
(realm/reset-account)
|
||||
(assoc app-db :current-account-id nil
|
||||
:current-public-key nil)))
|
||||
(assoc app-db :current-account-id nil)))
|
||||
|
||||
(register-handler :initialize-account-db
|
||||
(fn [db _]
|
||||
(assoc db
|
||||
:chats {}
|
||||
:current-chat-id "console"
|
||||
:signed-up (storage/get kv/kv-store :signed-up)
|
||||
:password (storage/get kv/kv-store :password))))
|
||||
|
||||
(register-handler :initialize-account
|
||||
(u/side-effect!
|
||||
(fn [_ [_ address]]
|
||||
(dispatch [:initialize-protocol address])
|
||||
(dispatch [:initialize-account-db])
|
||||
(dispatch [:initialize-protocol address])
|
||||
(dispatch [:initialize-chats])
|
||||
(dispatch [:initialize-pending-messages])
|
||||
(dispatch [:load-contacts])
|
||||
(dispatch [:init-chat])
|
||||
(dispatch [:init-discoveries])
|
||||
|
|
|
@ -46,21 +46,21 @@
|
|||
|
||||
(defn re-join-group-chat [db group-id identities group-name]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(let [new-identities (set identities)
|
||||
only-old-contacts (->> (chat-contacts group-id)
|
||||
(r/cljs-list)
|
||||
(remove (fn [{:keys [identity]}]
|
||||
(new-identities identity))))
|
||||
contacts (->> new-identities
|
||||
(mapv (fn [ident]
|
||||
{:identity ident}))
|
||||
(concat only-old-contacts))]
|
||||
(r/create :account :chat
|
||||
{:chat-id group-id
|
||||
:is-active true
|
||||
:name group-name
|
||||
:contacts contacts} true))))
|
||||
(fn []
|
||||
(let [new-identities (set identities)
|
||||
only-old-contacts (->> (chat-contacts group-id)
|
||||
(r/cljs-list)
|
||||
(remove (fn [{:keys [identity]}]
|
||||
(new-identities identity))))
|
||||
contacts (->> new-identities
|
||||
(mapv (fn [ident]
|
||||
{:identity ident}))
|
||||
(concat only-old-contacts))]
|
||||
(r/create :account :chat
|
||||
{:chat-id group-id
|
||||
:is-active true
|
||||
:name group-name
|
||||
:contacts contacts} true))))
|
||||
db)
|
||||
|
||||
(defn normalize-contacts
|
||||
|
@ -68,7 +68,7 @@
|
|||
(map #(update % :contacts vals) chats))
|
||||
|
||||
(defn chats-list []
|
||||
(-> (r/get-all :account :chat)
|
||||
(-> (r/get-by-field :account :chat :is-active true)
|
||||
(r/sorted :timestamp :desc)
|
||||
r/realm-collection->list
|
||||
normalize-contacts))
|
||||
|
@ -89,54 +89,46 @@
|
|||
(defn create-chat
|
||||
([{:keys [last-message-id] :as chat}]
|
||||
(let [chat (assoc chat :last-message-id (or last-message-id ""))]
|
||||
(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)))))
|
||||
(r/write :account #(r/create :account :chat chat true)))))
|
||||
|
||||
(defn chat-add-participants [chat-id identities]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(let [contacts (chat-contacts chat-id)]
|
||||
(doseq [contact-identity identities]
|
||||
(if-let [contact-exists (.find contacts (fn [object index collection]
|
||||
(fn []
|
||||
(let [contacts (chat-contacts chat-id)
|
||||
added-at (timestamp)]
|
||||
(doseq [contact-identity identities]
|
||||
(if-let [contact (.find contacts (fn [object index collection]
|
||||
(= contact-identity (aget object "identity"))))]
|
||||
(aset contact-exists "is-in-chat" true)
|
||||
(.push contacts (clj->js {:identity contact-identity})))))))
|
||||
(doto contact
|
||||
(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
|
||||
(dispatch [:initialize-chats]))
|
||||
|
||||
;; TODO deprecated? (is there need to remove multiple member at once?)
|
||||
(defn chat-remove-participants [chat-id identities]
|
||||
(r/write :account
|
||||
(fn []
|
||||
(let [query (include-query :identity identities)
|
||||
chat (r/single (r/get-by-field :account :chat :chat-id chat-id))]
|
||||
(-> (aget chat "contacts")
|
||||
(r/filtered query)
|
||||
(.forEach (fn [object _ _]
|
||||
(aset object "is-in-chat" false))))))))
|
||||
(fn []
|
||||
(let [query (include-query :identity identities)
|
||||
chat (r/single (r/get-by-field :account :chat :chat-id chat-id))]
|
||||
(-> (aget chat "contacts")
|
||||
(r/filtered query)
|
||||
(.forEach (fn [object _ _]
|
||||
(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 []
|
||||
(let [results (r/filtered (r/get-all :account :chat)
|
||||
"group-chat = true && is-active = true")]
|
||||
(js->clj (.map results (fn [object _ _]
|
||||
(aget object "chat-id"))))))
|
||||
(map
|
||||
(fn [{:keys [chat-id public-key private-key]}]
|
||||
{:chat-id chat-id
|
||||
:keypair {:private private-key
|
||||
:public public-key}})
|
||||
(r/realm-collection->list (groups true))))
|
||||
|
||||
(defn set-chat-active [chat-id active?]
|
||||
(r/write :account
|
||||
|
@ -144,3 +136,19 @@
|
|||
(-> (r/get-by-field :account :chat :chat-id chat-id)
|
||||
(r/single)
|
||||
(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.utils.types :refer [clj->json json->clj]]
|
||||
[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 []
|
||||
(let [collection (-> (r/get-by-fields :account :pending-message :or [[:status :sending]
|
||||
[:status :sent]
|
||||
[:status :failed]])
|
||||
(r/sorted :timestamp :desc)
|
||||
(r/realm-collection->list))]
|
||||
(->> collection
|
||||
(map (fn [{:keys [message-id] :as message}]
|
||||
(let [message (-> message
|
||||
(update :message json->clj)
|
||||
(update :identities json->clj))]
|
||||
[message-id message])))
|
||||
(into {}))))
|
||||
(defn get-pending-messages! []
|
||||
(->> (r/get-all :account :pending-message)
|
||||
r/realm-collection->list
|
||||
(map (fn [{:keys [message-id] :as message}]
|
||||
(-> message
|
||||
(update :topics reader/read-string)
|
||||
(assoc :id message-id))))))
|
||||
|
||||
(defn upsert-pending-message!
|
||||
[message]
|
||||
(defn- get-id
|
||||
[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
|
||||
(fn []
|
||||
(let [message (-> message
|
||||
(update :message clj->json)
|
||||
(update :identities clj->json))]
|
||||
(r/create :account :pending-message message true)))))
|
||||
(let [{:keys [from topics payload]} message
|
||||
id' (get-id id to)
|
||||
chat-id (or group-id to)
|
||||
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
|
||||
(fn []
|
||||
(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
|
||||
(:require [cljs.reader :refer [read-string]]
|
||||
[status-im.protocol.state.storage :as s]
|
||||
[status-im.utils.encryption :refer [password-encrypt
|
||||
password-decrypt]]
|
||||
[status-im.utils.types :refer [to-edn-string]]
|
||||
[re-frame.db :refer [app-db]]
|
||||
[status-im.db :as db]
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
(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]]
|
||||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[status-im.components.styles :refer [default-chat-color]]
|
||||
[status-im.models.chats :as chats]
|
||||
[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
|
||||
[db [_ id]]
|
||||
|
@ -19,11 +21,6 @@
|
|||
|
||||
(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
|
||||
[{:keys [contacts selected-contacts username]}]
|
||||
(->> (select-keys contacts selected-contacts)
|
||||
|
@ -33,20 +30,21 @@
|
|||
(s/join ", ")))
|
||||
|
||||
(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)
|
||||
chat-name (if-not (s/blank? group-name)
|
||||
group-name
|
||||
(group-name-from-contacts db))]
|
||||
(assoc db :new-chat {:chat-id new-group-id
|
||||
:name chat-name
|
||||
:color default-chat-color
|
||||
:group-chat true
|
||||
:is-active true
|
||||
:timestamp (.getTime (js/Date.))
|
||||
:contacts contacts
|
||||
:same-author false
|
||||
:same-direction false})))
|
||||
(group-name-from-contacts db))
|
||||
{:keys [public private]} (protocol/new-keypair!)]
|
||||
(assoc db :new-chat {:chat-id (random/id)
|
||||
:public-key public
|
||||
:private-key private
|
||||
:name chat-name
|
||||
:color default-chat-color
|
||||
:group-chat true
|
||||
:is-active true
|
||||
:timestamp (.getTime (js/Date.))
|
||||
:contacts contacts})))
|
||||
|
||||
(defn add-chat
|
||||
[{:keys [new-chat] :as db} _]
|
||||
|
@ -59,22 +57,45 @@
|
|||
(chats/create-chat new-chat))
|
||||
|
||||
(defn show-chat!
|
||||
[{:keys [new-group-id]} _]
|
||||
(dispatch [:navigation-replace :chat new-group-id]))
|
||||
[{:keys [new-chat]} _]
|
||||
(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 _]
|
||||
(assoc db :disable-group-creation false))
|
||||
|
||||
(register-handler :create-new-group
|
||||
(-> start-group-chat!
|
||||
((enrich prepare-chat))
|
||||
(-> prepare-chat
|
||||
((enrich add-chat))
|
||||
((after create-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 _]
|
||||
(assoc db :disable-group-creation true))
|
||||
|
||||
|
@ -84,18 +105,32 @@
|
|||
|
||||
(register-handler :init-group-creation
|
||||
(after dispatch-create-group)
|
||||
disable-creat-button)
|
||||
disable-create-button)
|
||||
|
||||
; todo rewrite
|
||||
(register-handler :group-chat-invite-received
|
||||
(u/side-effect!
|
||||
(fn [{:keys [current-public-key] :as db}
|
||||
[action from group-id identities group-name]]
|
||||
(if (chats/chat-exists? group-id)
|
||||
(chats/re-join-group-chat db group-id identities group-name)
|
||||
(let [contacts (keep (fn [ident]
|
||||
(when (not= ident current-public-key)
|
||||
{:identity ident})) identities)]
|
||||
(dispatch [:add-chat group-id {:name group-name
|
||||
:group-chat true
|
||||
:contacts contacts}]))))))
|
||||
(fn [{:keys [current-public-key web3] :as db}
|
||||
[_ {{:keys [group-id group-name contacts keypair timestamp] :as payload} :payload}]]
|
||||
(let [{:keys [private public]} keypair]
|
||||
(let [removed-at (chats/removed-at group-id)
|
||||
is-active (chats/is-active? group-id)
|
||||
contacts' (keep (fn [ident]
|
||||
(when (not= ident current-public-key)
|
||||
{:identity ident})) contacts)
|
||||
chat {:name group-name
|
||||
: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
|
||||
:primaryKey :address
|
||||
:properties {:address "string"
|
||||
:public-key "string"
|
||||
:name {:type "string" :optional true}
|
||||
:phone {:type "string" :optional true}
|
||||
:email {:type "string" :optional true}
|
||||
:status {:type "string" :optional true}
|
||||
:photo-path "string"
|
||||
:last-updated {:type "int" :default 0}}}
|
||||
:properties {:address "string"
|
||||
:public-key "string"
|
||||
:updates-public-key {:type :string
|
||||
:optional true}
|
||||
:updates-private-key {:type :string
|
||||
:optional true}
|
||||
:name {:type "string" :optional true}
|
||||
: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
|
||||
:primaryKey :key
|
||||
:properties {:key "string"
|
||||
|
@ -25,7 +29,11 @@
|
|||
:photo-path {:type "string" :optional true}
|
||||
:last-updated {: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
|
||||
:properties {:message-id :string
|
||||
:chat-id :string
|
||||
|
@ -85,19 +93,21 @@
|
|||
:user-statuses {:type :list
|
||||
:objectType "user-status"}}}
|
||||
{:name :pending-message
|
||||
:primaryKey :message-id
|
||||
:properties {:message-id "string"
|
||||
:chat-id {:type "string"
|
||||
:optional true}
|
||||
:message "string"
|
||||
:timestamp "int"
|
||||
:status "string"
|
||||
:retry-count "int"
|
||||
:send-once "bool"
|
||||
:identities {:type "string"
|
||||
:optional true}
|
||||
:internal? {:type "bool"
|
||||
:optional true}}}
|
||||
:primaryKey :id
|
||||
:properties {:id :string
|
||||
:message-id :string
|
||||
:chat-id {:type :string
|
||||
:optional true}
|
||||
:ack? :bool
|
||||
:requires-ack? :bool
|
||||
:from :string
|
||||
:to {:type :string
|
||||
:optional true}
|
||||
:payload :string
|
||||
:type :string
|
||||
:topics :string
|
||||
:attempts :int
|
||||
:was-sent? :bool}}
|
||||
{:name :chat-contact
|
||||
:properties {:identity "string"
|
||||
:is-in-chat {:type "bool"
|
||||
|
@ -118,7 +128,13 @@
|
|||
:optional true}
|
||||
:dapp-hash {:type :int
|
||||
: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
|
||||
:primaryKey :chat-id
|
||||
:properties {:chat-id "string"
|
||||
|
|
|
@ -4,13 +4,9 @@
|
|||
[status-im.components.react :refer [show-image-picker]]
|
||||
[status-im.utils.image-processing :refer [img->base64]]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.utils.handlers :as u]
|
||||
[status-im.utils.handlers :as u :refer [get-hashtags]]
|
||||
[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]
|
||||
(when identity
|
||||
(dispatch [:navigate-to :chat identity])))
|
||||
|
@ -59,4 +55,4 @@
|
|||
0 (dispatch [:show-profile-photo-capture])
|
||||
1 (dispatch [:open-image-picker])
|
||||
:default))
|
||||
:cancel-text (label :t/image-source-cancel)}))))
|
||||
:cancel-text (label :t/image-source-cancel)}))))
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
my-profile-icon]]
|
||||
[status-im.components.status-bar :refer [status-bar]]
|
||||
[status-im.profile.styles :as st]
|
||||
[status-im.profile.handlers :refer [get-hashtags
|
||||
message-user
|
||||
[status-im.utils.handlers :refer [get-hashtags]]
|
||||
[status-im.profile.handlers :refer [message-user
|
||||
update-profile]]
|
||||
[status-im.components.qr-code :refer [qr-code]]
|
||||
[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
|
||||
(:require [status-im.utils.handlers :as u]
|
||||
[status-im.utils.logging :as log]
|
||||
[status-im.protocol.api :as api]
|
||||
[re-frame.core :refer [dispatch after]]
|
||||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[status-im.models.contacts :as contacts]
|
||||
[status-im.models.messages :as messages]
|
||||
[status-im.models.pending-messages :as pending-messages]
|
||||
[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
|
||||
set-initialized]]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.constants :refer [text-content-type]]
|
||||
[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
|
||||
(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!
|
||||
(fn [db [_ current-account-id]]
|
||||
(let [current-account (get-in db [:accounts current-account-id])]
|
||||
(init-protocol current-account (make-handler db))))))
|
||||
(fn [_ [_ type {:keys [payload] :as message}]]
|
||||
(debug :incoming-message type)
|
||||
(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
|
||||
(fn [db [_ identity]]
|
||||
|
@ -43,9 +92,9 @@
|
|||
:content (str (or contact-name from) " " (label :t/received-invitation))
|
||||
: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))
|
||||
invitee-name (if (= identity (api/my-identity))
|
||||
invitee-name (if (= identity current-identity)
|
||||
(label :t/You)
|
||||
(:name (contacts/contact-by-identity identity)))]
|
||||
(messages/save-message chat-id {:from "system"
|
||||
|
@ -94,42 +143,94 @@
|
|||
|
||||
(register-handler :participant-left-group
|
||||
(u/side-effect!
|
||||
(fn [_ [action from group-id message-id]]
|
||||
(log/debug action message-id from group-id)
|
||||
(when-not (= (api/my-identity) from)
|
||||
(participant-left-group-message group-id from message-id)))))
|
||||
(fn [{:keys [current-public-key]}
|
||||
[_ {:keys [from]
|
||||
{:keys [group-id message-id timestamp]} :payload}]]
|
||||
(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
|
||||
(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)
|
||||
(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]
|
||||
(fn [_ [_ {:keys [message-id whisper-identity]}]]
|
||||
(fn [_ [_
|
||||
{:keys [from]
|
||||
{:keys [message-id group-id]} :payload}]]
|
||||
(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
|
||||
[:user-statuses whisper-identity]
|
||||
[:user-statuses from]
|
||||
(fn [{old-status :status}]
|
||||
{:id (random/id)
|
||||
:whisper-identity whisper-identity
|
||||
:whisper-identity from
|
||||
:status (if (= (keyword old-status) :seen)
|
||||
old-status
|
||||
status)}))
|
||||
(assoc message :message-status status))]
|
||||
(messages/update-message! message)))))
|
||||
|
||||
|
||||
(defn update-message-status [status]
|
||||
(fn [db [_ {:keys [message-id whisper-identity]}]]
|
||||
(let [db-key (if whisper-identity
|
||||
[:message-user-statuses message-id whisper-identity]
|
||||
[:message-statuses message-id])
|
||||
current-status (get-in db db-key)]
|
||||
(if-not (= :seen current-status)
|
||||
(assoc-in db db-key {:whisper-identity whisper-identity
|
||||
:status status})
|
||||
db))))
|
||||
(fn [db
|
||||
[_ {:keys [from]
|
||||
{:keys [message-id group-id]} :payload}]]
|
||||
(if (chats/is-active? (or group-id from))
|
||||
(let [group? (boolean group-id)
|
||||
status-path (if (and group? (not= status :sent))
|
||||
[:message-user-statuses message-id from]
|
||||
[:message-statuses message-id])
|
||||
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
|
||||
(after (save-message-status! :failed))
|
||||
|
@ -139,10 +240,6 @@
|
|||
(after (save-message-status! :sent))
|
||||
(update-message-status :sent))
|
||||
|
||||
(register-handler :message-delivered
|
||||
(after (save-message-status! :delivered))
|
||||
(update-message-status :delivered))
|
||||
|
||||
(register-handler :message-seen
|
||||
[(after (save-message-status! :seen))
|
||||
(after (fn [_ [_ {:keys [chat-id]}]]
|
||||
|
@ -150,16 +247,39 @@
|
|||
(update-message-status :seen))
|
||||
|
||||
(register-handler :pending-message-upsert
|
||||
(u/side-effect!
|
||||
(fn [_ [_ pending-message]]
|
||||
(pending-messages/upsert-pending-message! pending-message))))
|
||||
(after
|
||||
(fn [_ [_ {:keys [type id] :as 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
|
||||
(u/side-effect!
|
||||
(fn [_ [_ message-id]]
|
||||
(pending-messages/remove-pending-message! message-id))))
|
||||
(fn [_ [_ message]]
|
||||
(pending-messages/remove-pending-message! message))))
|
||||
|
||||
(register-handler :send-transaction!
|
||||
(register-handler :contact-request-received
|
||||
(u/side-effect!
|
||||
(fn [_ [_ amount message]]
|
||||
(println :send-transacion! amount message))))
|
||||
(fn [_ [_ {:keys [from payload]}]]
|
||||
(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.persistence.simple-kv-store :as kv]
|
||||
[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
|
||||
:status-sending "Sending"
|
||||
:status-pending "Sending"
|
||||
:status-sent "Sent"
|
||||
:status-seen-by-everyone "Seen by everyone"
|
||||
:status-seen "Seen"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.utils.handlers
|
||||
(: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!
|
||||
"Middleware for handlers that will not affect db."
|
||||
|
@ -24,3 +25,10 @@
|
|||
([name handler] (register-handler name nil handler))
|
||||
([name 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