Add new protocol

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Eric Dvorsak 2018-04-02 18:17:15 +02:00 committed by Andrea Maria Piana
parent 66f90cfdbc
commit df17c50612
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
111 changed files with 3225 additions and 4215 deletions

View File

@ -22,11 +22,11 @@
:function :subscription :function :subscription
:parameters {:name sub-name :parameters {:name sub-name
:subscriptions (subscription-values sub-params (get bot-db bot))} :subscriptions (subscription-values sub-params (get bot-db bot))}
:callback-events-creator (fn [jail-response] :callback-event-creator (fn [jail-response]
[[::calculated-subscription [::calculated-subscription
{:bot bot {:bot bot
:path [sub-name] :path [sub-name]
:result jail-response}]])})})) :result jail-response}])})}))
(defn set-in-bot-db (defn set-in-bot-db
"Associates value at specified path in bot-db and checks if there are any subscriptions "Associates value at specified path in bot-db and checks if there are any subscriptions

View File

@ -26,7 +26,9 @@
:photo-path (str "contacts://" constants/console-chat-id) :photo-path (str "contacts://" constants/console-chat-id)
:contacts [{:identity constants/console-chat-id :contacts [{:identity constants/console-chat-id
:text-color "#FFFFFF" :text-color "#FFFFFF"
:background-color "#AB7967"}]}) :background-color "#AB7967"}]
:last-to-clock-value 0
:last-from-clock-value 0})
(def contact (def contact
{:whisper-identity constants/console-chat-id {:whisper-identity constants/console-chat-id

View File

@ -0,0 +1,12 @@
(ns status-im.chat.core)
;; Seen messages
(defn receive-seen
[chat-id sender message-ids {:keys [db]}]
(when-let [seen-messages-ids (-> (get-in db [:chats chat-id :messages])
(select-keys message-ids)
keys)]
{:db (reduce (fn [new-db message-id]
(assoc-in new-db [:chats chat-id :messages message-id :user-statuses sender] :seen))
db
seen-messages-ids)}))

View File

@ -1,19 +1,19 @@
(ns status-im.chat.events (ns status-im.chat.events
(:require [clojure.set :as set] (:require [clojure.set :as set]
[cljs.core.async :as async] [clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.protocol.core :as protocol]
[status-im.chat.models :as models] [status-im.chat.models :as models]
[status-im.chat.console :as console] [status-im.chat.console :as console]
[status-im.data-store.chats :as chats] [status-im.chat.constants :as chat.constants]
[status-im.data-store.messages :as messages]
[status-im.data-store.pending-messages :as pending-messages]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.async :as utils.async]
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.transport.message.core :as transport]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.message.v1.public-chat :as public-chat]
[status-im.transport.message.v1.group-chat :as group-chat]
status-im.chat.events.commands status-im.chat.events.commands
status-im.chat.events.requests status-im.chat.events.requests
status-im.chat.events.send-message status-im.chat.events.send-message
@ -22,88 +22,8 @@
status-im.chat.events.console status-im.chat.events.console
status-im.chat.events.webview-bridge)) status-im.chat.events.webview-bridge))
;;;; Coeffects
(re-frame/reg-cofx
:stored-unviewed-messages
(fn [cofx _]
(assoc cofx :stored-unviewed-messages
(messages/get-unviewed (-> cofx :db :current-public-key)))))
(re-frame/reg-cofx
:get-stored-message
(fn [cofx _]
(assoc cofx :get-stored-message messages/get-by-id)))
(re-frame/reg-cofx
:get-stored-messages
(fn [cofx _]
(assoc cofx :get-stored-messages messages/get-by-chat-id)))
(re-frame/reg-cofx
:stored-message-ids
(fn [cofx _]
(assoc cofx :stored-message-ids (messages/get-stored-message-ids))))
(re-frame/reg-cofx
:all-stored-chats
(fn [cofx _]
(assoc cofx :all-stored-chats (chats/get-all))))
(re-frame/reg-cofx
:get-stored-chat
(fn [cofx _]
(assoc cofx :get-stored-chat chats/get-by-id)))
(re-frame/reg-cofx
:inactive-chat-ids
(fn [cofx _]
(assoc cofx :inactive-chat-ids (chats/get-inactive-ids))))
;;;; Effects ;;;; Effects
(def ^:private realm-queue (utils.async/task-queue 2000))
(re-frame/reg-fx
:update-message
(fn [message]
(async/go (async/>! realm-queue #(messages/update-message message)))))
(re-frame/reg-fx
:save-message
(fn [message]
(async/go (async/>! realm-queue #(messages/save message)))))
(re-frame/reg-fx
:delete-messages
(fn [chat-id]
(async/go (async/>! realm-queue #(messages/delete-by-chat-id chat-id)))))
(re-frame/reg-fx
:delete-pending-messages
(fn [chat-id]
(async/go (async/>! realm-queue #(pending-messages/delete-all-by-chat-id chat-id)))))
(re-frame/reg-fx
:save-chat
(fn [chat]
(async/go (async/>! realm-queue #(chats/save chat)))))
(re-frame/reg-fx
:deactivate-chat
(fn [chat-id]
(async/go (async/>! realm-queue #(chats/set-inactive chat-id)))))
(re-frame/reg-fx
:delete-chat
(fn [chat-id]
(async/go (async/>! realm-queue #(chats/delete chat-id)))))
(re-frame/reg-fx
:protocol-send-seen
(fn [params]
(protocol/send-seen! params)))
(re-frame/reg-fx (re-frame/reg-fx
:browse :browse
(fn [link] (fn [link]
@ -134,7 +54,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:load-more-messages :load-more-messages
[(re-frame/inject-cofx :get-stored-messages)] [(re-frame/inject-cofx :data-store/get-messages)]
(fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _] (fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _]
(when-not (get-in db [:chats current-chat-id :all-loaded?]) (when-not (get-in db [:chats current-chat-id :all-loaded?])
(let [loaded-count (count (get-in db [:chats current-chat-id :messages])) (let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
@ -151,16 +71,25 @@
(fn [db [{:keys [chat-id message-id]}]] (fn [db [{:keys [chat-id message-id]}]]
(update-in db [:chats chat-id :messages message-id] assoc :appearing? false))) (update-in db [:chats chat-id :messages message-id] assoc :appearing? false)))
(handlers/register-handler-fx
:update-message-status
[re-frame/trim-v]
(fn [{:keys [db]} [chat-id message-id user-id status]]
(let [msg-path [:chats chat-id :messages message-id]
new-db (update-in db (conj msg-path :user-statuses) assoc user-id status)]
{:db new-db
:data-store/update-message (-> (get-in new-db msg-path) (select-keys [:message-id :user-statuses]))})))
(defn init-console-chat (defn init-console-chat
[{:keys [chats] :as db}] [{:keys [chats] :as db}]
(if (chats constants/console-chat-id) (if (chats constants/console-chat-id)
{:db db} {:db db}
{:db (-> db {:db (-> db
(assoc :current-chat-id constants/console-chat-id) (assoc :current-chat-id constants/console-chat-id)
(update :chats assoc constants/console-chat-id console/chat)) (update :chats assoc constants/console-chat-id console/chat))
:dispatch [:add-contacts [console/contact]] :dispatch [:add-contacts [console/contact]]
:save-chat console/chat :data-store/save-chat console/chat
:save-all-contacts [console/contact]})) :data-store/save-contact console/contact}))
(handlers/register-handler-fx (handlers/register-handler-fx
:init-console-chat :init-console-chat
@ -169,12 +98,12 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-chats :initialize-chats
[(re-frame/inject-cofx :all-stored-chats) [(re-frame/inject-cofx :data-store/all-chats)
(re-frame/inject-cofx :inactive-chat-ids) (re-frame/inject-cofx :data-store/inactive-chat-ids)
(re-frame/inject-cofx :get-stored-messages) (re-frame/inject-cofx :data-store/get-messages)
(re-frame/inject-cofx :stored-unviewed-messages) (re-frame/inject-cofx :data-store/unviewed-messages)
(re-frame/inject-cofx :stored-message-ids) (re-frame/inject-cofx :data-store/message-ids)
(re-frame/inject-cofx :get-stored-unanswered-requests)] (re-frame/inject-cofx :data-store/get-unanswered-requests)]
(fn [{:keys [db (fn [{:keys [db
all-stored-chats all-stored-chats
inactive-chat-ids inactive-chat-ids
@ -189,12 +118,12 @@
chats (reduce (fn [acc {:keys [chat-id] :as chat}] chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(let [chat-messages (index-messages (get-stored-messages chat-id))] (let [chat-messages (index-messages (get-stored-messages chat-id))]
(assoc acc chat-id (assoc acc chat-id
(assoc chat (assoc chat
:unviewed-messages (get stored-unviewed-messages chat-id) :unviewed-messages (get stored-unviewed-messages chat-id)
:requests (get chat->message-id->request chat-id) :requests (get chat->message-id->request chat-id)
:messages chat-messages :messages chat-messages
:not-loaded-message-ids (set/difference (get stored-message-ids chat-id) :not-loaded-message-ids (set/difference (get stored-message-ids chat-id)
(-> chat-messages keys set)))))) (-> chat-messages keys set))))))
{} {}
all-stored-chats)] all-stored-chats)]
(-> db (-> db
@ -203,153 +132,213 @@
init-console-chat init-console-chat
(update :dispatch-n conj [:load-default-contacts!]))))) (update :dispatch-n conj [:load-default-contacts!])))))
(handlers/register-handler-fx
:send-seen!
[re-frame/trim-v]
(fn [{:keys [db]} [{:keys [chat-id from me message-id]}]]
(let [{:keys [web3 chats] :contacts/keys [contacts]} db
{:keys [group-chat public? messages]} (get chats chat-id)
statuses (assoc (get-in messages [message-id :user-statuses]) me :seen)]
(cond-> {:db (-> db
(update-in [:chats chat-id :unviewed-messages] disj message-id)
(assoc-in [:chats chat-id :messages message-id :user-statuses] statuses))
:update-message {:message-id message-id
:user-statuses statuses}}
;; for public chats and 1-1 bot/dapp chats, it makes no sense to signalise `:seen` msg
(not (or public? (get-in contacts [chat-id :dapp?])))
(assoc :protocol-send-seen {:web3 web3
:message (cond-> {:from me
:to from
:message-id message-id}
group-chat (assoc :group-id chat-id))})))))
(handlers/register-handler-fx (handlers/register-handler-fx
:browse-link-from-message :browse-link-from-message
(fn [_ [_ link]] (fn [_ [_ link]]
{:browse link})) {:browse link}))
(defn preload-chat-data (defn- persist-seen-messages
"Takes coeffects map and chat-id, returns effects necessary when navigating to chat" [chat-id unseen-messages-ids {:keys [db]}]
[{:keys [db]} chat-id] {:data-store/update-messages (map (fn [message-id]
(let [chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event])] (-> (get-in db [:chats chat-id :messages message-id])
(cond-> {:db (-> db (select-keys [:message-id :user-statuses])))
(assoc :current-chat-id chat-id) unseen-messages-ids)})
(assoc-in [:chats chat-id :was-opened?] true)
(models/set-chat-ui-props {:validation-messages nil})
(update-in [:chats chat-id] dissoc :chat-loaded-event))}
chat-loaded-event (defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
(assoc :dispatch chat-loaded-event)))) (when (and (seq message-ids)
(not (models/bot-only-chat? db chat-id)))
(transport/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
;;TODO (yenda) find a more elegant solution for system messages
(defn- mark-messages-seen
[chat-id {:keys [db] :as cofx}]
(let [me (:current-public-key db)
messages-path [:chats chat-id :messages]
unseen-messages-ids (into #{}
(comp (filter (fn [[_ {:keys [from user-statuses outgoing]}]]
(and (not outgoing)
(not= constants/system from)
(not= (get user-statuses me) :seen))))
(map first))
(get-in db messages-path))
unseen-system-messages-ids (into #{}
(comp (filter (fn [[_ {:keys [from user-statuses]}]]
(and (= constants/system from)
(not= (get user-statuses me) :seen))))
(map first))
(get-in db messages-path))]
(when (or (seq unseen-messages-ids)
(seq unseen-system-messages-ids))
(handlers/merge-fx cofx
{:db (-> (reduce (fn [new-db message-id]
(assoc-in new-db (into messages-path [message-id :user-statuses me]) :seen))
db
(into unseen-messages-ids unseen-system-messages-ids))
(update-in [:chats chat-id :unviewed-messages] set/difference unseen-messages-ids unseen-system-messages-ids))}
(persist-seen-messages chat-id (into unseen-messages-ids unseen-system-messages-ids))
(send-messages-seen chat-id unseen-messages-ids)))))
(defn- fire-off-chat-loaded-event
[chat-id {:keys [db]}]
(when-let [event (get-in db [:chats chat-id :chat-loaded-event])]
{:db (update-in db [:chats chat-id] dissoc :chat-loaded-event)
:dispatch event}))
(defn- preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
[chat-id {:keys [db] :as cofx}]
(handlers/merge-fx cofx
{:db (-> (assoc db :current-chat-id chat-id)
(models/set-chat-ui-props {:validation-messages nil}))}
(fire-off-chat-loaded-event chat-id)
(mark-messages-seen chat-id)))
(handlers/register-handler-fx (handlers/register-handler-fx
:add-chat-loaded-event :add-chat-loaded-event
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] [(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
(fn [{:keys [db] :as cofx} [chat-id event]] (fn [{:keys [db] :as cofx} [chat-id event]]
(if (get (:chats db) chat-id) (if (get (:chats db) chat-id)
{:db (assoc-in db [:chats chat-id :chat-loaded-event] event)} {:db (assoc-in db [:chats chat-id :chat-loaded-event] event)}
(-> (models/add-chat cofx chat-id) ; chat not created yet, we have to create it (-> (models/add-chat chat-id cofx) ; chat not created yet, we have to create it
(assoc-in [:db :chats chat-id :chat-loaded-event] event))))) (assoc-in [:db :chats chat-id :chat-loaded-event] event)))))
;; TODO(janherich): remove this unnecessary event in the future (only model function `add-chat` will stay) ;; TODO(janherich): remove this unnecessary event in the future (only model function `add-chat` will stay)
(handlers/register-handler-fx (handlers/register-handler-fx
:add-chat :add-chat
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] [(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
(fn [cofx [chat-id chat-props]] (fn [cofx [chat-id chat-props]]
(models/add-chat cofx chat-id chat-props))) (models/add-chat chat-id chat-props cofx)))
(defn- ensure-chat-exists (defn- ensure-chat-exists
"Takes chat-id and coeffects map and returns fx to create chat if it doesn't exist" "Takes chat-id and coeffects map and returns fx to create chat if it doesn't exist"
[chat-id cofx] [chat-id {:keys [db] :as cofx}]
(when-not (get-in cofx [:db :chats chat-id]) (when-not (get-in cofx [:db :chats chat-id])
(models/add-chat cofx chat-id))) (models/add-chat chat-id cofx)))
(defn- navigate-to-chat (defn- navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
[chat-id navigation-replace? cofx] [chat-id {:keys [navigation-replace?]} {:keys [db] :as cofx}]
(let [nav-fn (if navigation-replace? (if navigation-replace?
#(navigation/replace-view % :chat) (handlers/merge-fx cofx
#(navigation/navigate-to % :chat))] (navigation/replace-view :chat)
(-> (preload-chat-data cofx chat-id) (preload-chat-data chat-id))
(update :db nav-fn)))) (handlers/merge-fx cofx
;; TODO janherich - refactor `navigate-to` so it can be used with `merge-fx` macro
{:db (navigation/navigate-to db :chat)}
(preload-chat-data chat-id))))
(handlers/register-handler-fx (handlers/register-handler-fx
:navigate-to-chat :navigate-to-chat
[re-frame/trim-v] [re-frame/trim-v]
(fn [cofx [chat-id {:keys [navigation-replace?]}]] (fn [cofx [chat-id opts]]
(navigate-to-chat chat-id navigation-replace? cofx))) (navigate-to-chat chat-id opts cofx)))
(defn start-chat (defn start-chat
"Start a chat, making sure it exists" "Start a chat, making sure it exists"
[chat-id navigation-replace? {:keys [db] :as cofx}] [chat-id opts {:keys [db] :as cofx}]
(when (not= (:current-public-key db) chat-id) ; don't allow to open chat with yourself (when (not= (:current-public-key db) chat-id) ; don't allow to open chat with yourself
(handlers/merge-fx (handlers/merge-fx cofx
cofx (ensure-chat-exists chat-id)
(ensure-chat-exists chat-id) (navigate-to-chat chat-id opts))))
(navigate-to-chat chat-id navigation-replace?))))
(handlers/register-handler-fx (handlers/register-handler-fx
:start-chat :start-chat
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] [(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
(fn [cofx [contact-id {:keys [navigation-replace?]}]] (fn [cofx [contact-id opts]]
(start-chat contact-id navigation-replace? cofx))) (start-chat contact-id opts cofx)))
;; TODO(janherich): remove this unnecessary event in the future (only model function `update-chat` will stay) ;; TODO(janherich): remove this unnecessary event in the future (only model function `update-chat` will stay)
(handlers/register-handler-fx (handlers/register-handler-fx
:update-chat! :update-chat!
[re-frame/trim-v] [re-frame/trim-v]
(fn [cofx [chat]] (fn [cofx [chat]]
(models/update-chat cofx chat))) (models/update-chat chat cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:remove-chat :remove-chat
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [chat-id]] (fn [cofx [chat-id]]
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])] (models/remove-chat chat-id cofx)))
(cond-> {:db (-> db
(update :chats dissoc chat-id)
(update :deleted-chats (fnil conj #{}) chat-id))
:delete-pending-messages chat-id}
(or group-chat debug?)
(assoc :delete-messages chat-id)
debug?
(assoc :delete-chat chat-id)
(not debug?)
(assoc :deactivate-chat chat-id)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:delete-chat :remove-chat-and-navigate-home
[re-frame/trim-v] [re-frame/trim-v]
(fn [cofx [chat-id]] (fn [cofx [chat-id]]
(-> (models/remove-chat cofx chat-id) (handlers/merge-fx cofx
(update :db navigation/replace-view :home)))) (models/remove-chat chat-id)
(navigation/replace-view :home))))
(handlers/register-handler-fx (handlers/register-handler-fx
:delete-chat? :remove-chat-and-navigate-home?
[re-frame/trim-v] [re-frame/trim-v]
(fn [_ [chat-id group?]] (fn [_ [chat-id group?]]
{:show-confirmation {:title (i18n/label :t/delete-confirmation) {:show-confirmation {:title (i18n/label :t/delete-confirmation)
:content (i18n/label (if group? :t/delete-group-chat-confirmation :t/delete-chat-confirmation)) :content (i18n/label (if group? :t/delete-group-chat-confirmation :t/delete-chat-confirmation))
:confirm-button-text (i18n/label :t/delete) :confirm-button-text (i18n/label :t/delete)
:on-accept #(re-frame/dispatch [:delete-chat chat-id])}})) :on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
(defn remove-chats [db chat-id]
(let [chat (get-in db [:chats chat-id])]
{:db (-> db
(update :chats dissoc chat-id)
(update :deleted-chats (fnil conj #{}) chat-id))
:delete-chat chat
:delete-chat-messages chat}))
(handlers/register-handler-fx (handlers/register-handler-fx
:remove-chat :create-new-public-chat
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [chat-id]] (fn [{:keys [db now] :as cofx} [topic]]
(remove-chats db chat-id))) (if (get-in db [:chats topic])
(handlers/merge-fx cofx
(navigation/navigate-to-clean :home)
(navigate-to-chat topic {}))
(handlers/merge-fx cofx
(models/add-public-chat topic)
(navigation/navigate-to-clean :home)
(navigate-to-chat topic {})
(public-chat/join-public-chat topic)))))
(defn- group-name-from-contacts [selected-contacts all-contacts username]
(->> selected-contacts
(map (comp :name (partial get all-contacts)))
(cons username)
(string/join ", ")))
(handlers/register-handler-fx (handlers/register-handler-fx
:remove-chat-and-navigate-home :create-new-group-chat-and-open
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{:keys [db now random-id] :as cofx} [group-name]]
(let [selected-contacts (:group/selected-contacts db)
chat-name (if-not (string/blank? group-name)
group-name
(group-name-from-contacts selected-contacts
(:contacts/contacts db)
(:username db)))]
(handlers/merge-fx cofx
{:db (assoc db :group/selected-contacts #{})}
(models/add-group-chat random-id chat-name (:current-public-key db) selected-contacts)
(navigation/navigate-to-clean :home)
(navigate-to-chat random-id {})
(transport/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
(defn- broadcast-leave [{:keys [public? chat-id]} cofx]
(when-not public?
(transport/send (group-chat/GroupLeave.) chat-id cofx)))
(handlers/register-handler-fx
:leave-group-chat
;; stop listening to group here
(fn [{{:keys [current-chat-id chats] :as db} :db :as cofx} _]
(handlers/merge-fx cofx
(models/remove-chat current-chat-id)
(navigation/replace-view :home)
(broadcast-leave (get chats current-chat-id)))))
(handlers/register-handler-fx
:leave-group-chat?
(fn [_ _]
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
:content (i18n/label :t/leave-group-chat-confirmation)
:confirm-button-text (i18n/label :t/leave)
:on-accept #(re-frame/dispatch [:leave-group-chat])}}))
(handlers/register-handler-fx
:show-profile
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [chat-id]] (fn [{:keys [db] :as cofx} [identity]]
(merge (remove-chats db chat-id) (handlers/merge-fx cofx
{:dispatch [:navigation-replace :home]}))) {:db (assoc db :contacts/identity identity)}
(navigation/navigate-forget :profile))))

View File

@ -1,6 +1,7 @@
(ns status-im.chat.events.commands (ns status-im.chat.events.commands
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.chat.models.message :as models.message]
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]))
@ -9,45 +10,38 @@
(defn- generate-context (defn- generate-context
"Generates context for jail call" "Generates context for jail call"
[current-account-id chat-id to group-id] [current-account-id chat-id group-chat? to]
(merge {:platform platform/os (merge {:platform platform/os
:from current-account-id :from current-account-id
:to to :to to
:chat {:chat-id chat-id :chat {:chat-id chat-id
:group-chat (not (nil? group-id))}} :group-chat (boolean group-chat?)}}
i18n/delimeters)) i18n/delimeters))
(defn request-command-message-data (defn request-command-message-data
"Requests command message data from jail" "Requests command message data from jail"
[db [{:accounts/keys [current-account-id] :contacts/keys [contacts] :as db}
{{command-name :command {{:keys [command command-scope-bitmask bot params type]} :content
content-command-name :content-command :keys [chat-id group-id] :as message}
:keys [content-command-scope-bitmask bot scope-bitmask params type]} :content
:keys [chat-id group-id jail-id] :as message}
{:keys [data-type] :as opts}] {:keys [data-type] :as opts}]
(let [{:accounts/keys [current-account-id] (if-not (get contacts bot) ;; bot is not even in contacts, do nothing
:contacts/keys [contacts]} db {:db db}
jail-id (or bot jail-id chat-id) (if (get-in contacts [bot :jail-loaded?])
jail-command-name (or content-command-name command-name)] (let [path [(if (= :response (keyword type)) :responses :commands)
(if-not (get contacts jail-id) ;; bot is not even in contacts, do nothing [command command-scope-bitmask]
{:db db} data-type]
(if (get-in contacts [jail-id :jail-loaded?]) to (get-in contacts [chat-id :address])
(let [path [(if (= :response (keyword type)) :responses :commands) jail-params {:parameters params
[jail-command-name :context (generate-context current-account-id chat-id (models.message/group-message? message) to)}]
(or content-command-scope-bitmask scope-bitmask)] {:db db
data-type] :call-jail {:jail-id bot
to (get-in contacts [chat-id :address]) :path path
jail-params {:parameters params :params jail-params
:context (generate-context current-account-id chat-id to group-id)}] :callback-event-creator (fn [jail-response]
{:db db [::jail-command-data-response
:call-jail {:jail-id jail-id jail-response message opts])}})
:path path {:db (update-in db [:contacts/contacts bot :jail-loaded-events]
:params jail-params conj [:request-command-message-data message opts])})))
:callback-events-creator (fn [jail-response]
[[::jail-command-data-response
jail-response message opts]])}})
{:db (update-in db [:contacts/contacts jail-id :jail-loaded-events]
conj [:request-command-message-data message opts])}))))
;;;; Handlers ;;;; Handlers
@ -60,7 +54,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:request-command-message-data :request-command-message-data
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] [re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [{:keys [db]} [message opts]] (fn [{:keys [db]} [message opts]]
(request-command-message-data db message opts))) (request-command-message-data db message opts)))

View File

@ -6,30 +6,29 @@
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[goog.string :as gstring] [goog.string :as gstring]
goog.string.format)) goog.string.format
[status-im.utils.handlers :as handlers]))
;;;; Helper fns ;;;; Helper fns
(defn console-respond-command-messages (defn console-respond-command-messages
[command random-id-seq] [{:keys [name] :as command} handler-data random-id-seq]
(let [{:keys [command handler-data]} command] (when command
(when command (case name
(let [{:keys [name]} command] "js" (let [{:keys [err data messages]} handler-data
(case name content (or err data)
"js" (let [{:keys [err data messages]} handler-data message-events (mapv (fn [{:keys [message type]} id]
content (or err data) (console-chat/console-message
message-events (mapv (fn [{:keys [message type]} id] {:message-id id
(console-chat/console-message :content (str type ": " message)
{:message-id id :content-type constants/text-content-type}))
:content (str type ": " message) messages random-id-seq)]
:content-type constants/text-content-type})) (conj message-events
messages random-id-seq)] (console-chat/console-message
(conj message-events {:message-id (first random-id-seq)
(console-chat/console-message :content (str content)
{:message-id (first random-id-seq) :content-type constants/text-content-type})))
:content (str content) (log/debug "ignoring command: " name))))
:content-type constants/text-content-type})))
(log/debug "ignoring command: " name))))))
(defn faucet-base-url->url [url] (defn faucet-base-url->url [url]
(str url "/donate/0x%s")) (str url "/donate/0x%s"))
@ -42,7 +41,7 @@
(def console-commands->fx (def console-commands->fx
{"faucet" {"faucet"
(fn [{:keys [db random-id]} {:keys [params]}] (fn [{:keys [db random-id] :as cofx} {:keys [params]}]
(let [{:accounts/keys [accounts current-account-id]} db (let [{:accounts/keys [accounts current-account-id]} db
current-address (get-in accounts [current-account-id :address]) current-address (get-in accounts [current-account-id :address])
faucet-url (faucet-base-url->url (:url params))] faucet-url (faucet-base-url->url (:url params))]
@ -58,19 +57,19 @@
(i18n/label :t/faucet-error)))}})) (i18n/label :t/faucet-error)))}}))
"debug" "debug"
(fn [{:keys [db random-id now]} {:keys [params]}] (fn [{:keys [db random-id now] :as cofx} {:keys [params]}]
(let [debug? (= "On" (:mode params))] (let [debug? (= "On" (:mode params))]
(-> {:db db} (handlers/merge-fx cofx
(accounts-events/account-update {:debug? debug? {:dispatch-n (if debug?
:last-updated now}) [[:initialize-debugging {:force-start? true}]
(assoc :dispatch-n (if debug? [:chat-received-message/add
[[:initialize-debugging {:force-start? true}] (console-chat/console-message
[:chat-received-message/add {:message-id random-id
(console-chat/console-message :content (i18n/label :t/debug-enabled)
{:message-id random-id :content-type constants/text-content-type})]]
:content (i18n/label :t/debug-enabled) [[:stop-debugging]])}
:content-type constants/text-content-type})]] (accounts-events/account-update {:debug? debug?
[[:stop-debugging]])))))}) :last-updated now}))))})
(def commands-names (set (keys console-commands->fx))) (def commands-names (set (keys console-commands->fx)))

View File

@ -56,7 +56,7 @@
chat-text (if append? chat-text (if append?
(str current-input new-input) (str current-input new-input)
new-input)] new-input)]
(cond-> db (cond-> (model/set-chat-ui-props db {:validation-messages nil})
true true
(assoc-in [:chats current-chat-id :input-text] (input-model/text->emoji chat-text)) (assoc-in [:chats current-chat-id :input-text] (input-model/text->emoji chat-text))
@ -146,12 +146,12 @@
{:call-jail {:jail-id owner-id {:call-jail {:jail-id owner-id
:path path :path path
:params params :params params
:callback-events-creator (fn [jail-response] :callback-event-creator (fn [jail-response]
[[:chat-received-message/bot-response [:chat-received-message/bot-response
{:chat-id current-chat-id {:chat-id current-chat-id
:command command :command command
:parameter-index parameter-index} :parameter-index parameter-index}
jail-response]])}})))) jail-response])}}))))
(defn chat-input-focus (defn chat-input-focus
"Returns fx for focusing on active chat input reference" "Returns fx for focusing on active chat input reference"
@ -179,15 +179,13 @@
(defn select-chat-input-command (defn select-chat-input-command
"Selects command + (optional) arguments as input for active chat" "Selects command + (optional) arguments as input for active chat"
[{:keys [current-chat-id chat-ui-props] :as db} [{:keys [prefill prefill-bot-db sequential-params name owner-id] :as command} metadata prevent-auto-focus? {:keys [db]}]
{:keys [prefill prefill-bot-db sequential-params name owner-id] :as command} metadata prevent-auto-focus?] (let [{:keys [current-chat-id chat-ui-props]} db
(let [db' (-> db db' (-> db
(bots-events/clear-bot-db owner-id) (bots-events/clear-bot-db owner-id)
clear-seq-arguments clear-seq-arguments
(model/set-chat-ui-props {:show-suggestions? false (model/set-chat-ui-props {:show-suggestions? false
:result-box nil :result-box nil})
:validation-messages nil
:prev-command name})
(set-chat-input-metadata metadata) (set-chat-input-metadata metadata)
(set-chat-input-text (str (commands-model/command-name command) (set-chat-input-text (str (commands-model/command-name command)
constants/spacing-char constants/spacing-char
@ -237,16 +235,16 @@
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects ;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
(defn- command->message (defn- command->message
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}] [{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
(cond-> {:chat-id current-chat-id (message-model/add-message-type
:jail-id (:owner-id command) {:chat-id current-chat-id
:content {:command (:name command) :content {:bot (:owner-id command)
:type (:type command) :command (:name command)
:scope-bitmask (:scope-bitmask command) :type (:type command)
:params (assoc (input-model/args->params command-params) :command-scope-bitmask (:scope-bitmask command)
:bot-db (get bot-db (:owner-id command)))}} :params (assoc (input-model/args->params command-params)
(get-in chats [current-chat-id :group-chat]) :bot-db (get bot-db (:owner-id command)))}}
(assoc :group-id current-chat-id))) (get chats current-chat-id)))
(defn proceed-command (defn proceed-command
"Proceed with command processing by creating command message + setting up and executing chain of events: "Proceed with command processing by creating command message + setting up and executing chain of events:
@ -260,8 +258,7 @@
:to-message (:to-message-id metadata) :to-message (:to-message-id metadata)
:created-at current-time :created-at current-time
:id message-id :id message-id
:chat-id current-chat-id :chat-id current-chat-id}
:jail-id (:jail-id message)}
event-chain {:data-type :validator event-chain {:data-type :validator
:proceed-event-creator (fn [validation-response] :proceed-event-creator (fn [validation-response]
[::proceed-validation [::proceed-validation
@ -282,35 +279,21 @@
;;;; Handlers ;;;; Handlers
(handlers/register-handler-db
:update-input-data
(fn [db]
(input-model/modified-db-after-change db)))
(handlers/register-handler-fx (handlers/register-handler-fx
:set-chat-input-text :set-chat-input-text
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [text]] (fn [{:keys [db]} [text]]
(-> (set-chat-input-text db text) (let [new-db (set-chat-input-text db text)
(call-on-message-input-change)))) fx (call-on-message-input-change new-db)]
(if-let [{:keys [command]} (input-model/selected-chat-command new-db)]
(handlers/register-handler-db (merge fx (load-chat-parameter-box new-db command))
:add-to-chat-input-text fx))))
[re-frame/trim-v]
(fn [db [text-to-add]]
(set-chat-input-text db text-to-add :append? true)))
(handlers/register-handler-fx (handlers/register-handler-fx
:select-chat-input-command :select-chat-input-command
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [command metadata prevent-auto-focus?]] (fn [cofx [command metadata prevent-auto-focus?]]
(select-chat-input-command db command metadata prevent-auto-focus?))) (select-chat-input-command command metadata prevent-auto-focus? cofx)))
(handlers/register-handler-db
:set-chat-input-metadata
[re-frame/trim-v]
(fn [db [data]]
(set-chat-input-metadata db data)))
(handlers/register-handler-db (handlers/register-handler-db
:set-command-argument :set-command-argument
@ -331,39 +314,18 @@
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])] (when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
{::blur-rn-component cmp-ref}))) {::blur-rn-component cmp-ref})))
(handlers/register-handler-fx
:load-chat-parameter-box
[re-frame/trim-v]
(fn [{:keys [db]} [command]]
(load-chat-parameter-box db command)))
(handlers/register-handler-fx (handlers/register-handler-fx
::proceed-validation ::proceed-validation
[re-frame/trim-v] [re-frame/trim-v]
(fn [_ [{:keys [markup validationHandler parameters]} proceed-events]] (fn [_ [{:keys [markup parameters]} proceed-events]]
(let [error-events-creator (fn [validator-result] (let [error-events-creator (fn [validator-result]
[[:set-chat-ui-props {:validation-messages validator-result [[:set-chat-ui-props {:validation-messages validator-result
:sending-in-progress? false}]]) :sending-in-progress? false}]])
events (cond events (if markup
markup
(error-events-creator markup) (error-events-creator markup)
validationHandler
[[::execute-validation-handler
validationHandler parameters error-events-creator proceed-events]]
:default
proceed-events)] proceed-events)]
{:dispatch-n events}))) {:dispatch-n events})))
(handlers/register-handler-fx
::execute-validation-handler
[re-frame/trim-v]
(fn [_ [validation-handler-name params error-events-creator proceed-events]]
(let [error-events (when-let [validator (input-model/validation-handler validation-handler-name)]
(validator params error-events-creator))]
{:dispatch-n (or error-events proceed-events)})))
(handlers/register-handler-fx (handlers/register-handler-fx
::send-command ::send-command
message-model/send-interceptors message-model/send-interceptors
@ -432,12 +394,6 @@
(command-not-complete-fx db input-text)) (command-not-complete-fx db input-text))
(plain-text-message-fx db cofx input-text current-chat-id current-public-key)))))) (plain-text-message-fx db cofx input-text current-chat-id current-public-key))))))
;; TODO: remove this handler and leave only helper fn once all invocations are refactored
(handlers/register-handler-db
:clear-seq-arguments
(fn [db]
(clear-seq-arguments db)))
(handlers/register-handler-db (handlers/register-handler-db
::update-seq-arguments ::update-seq-arguments
[re-frame/trim-v] [re-frame/trim-v]

View File

@ -1,7 +1,6 @@
(ns status-im.chat.events.receive-message (ns status-im.chat.events.receive-message
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.data-store.messages :as messages-store]
[status-im.chat.events.commands :as commands-events] [status-im.chat.events.commands :as commands-events]
[status-im.chat.models.message :as message-model] [status-im.chat.models.message :as message-model]
[status-im.constants :as constants] [status-im.constants :as constants]
@ -14,7 +13,7 @@
::received-message ::received-message
message-model/receive-interceptors message-model/receive-interceptors
(fn [cofx [message]] (fn [cofx [message]]
(message-model/receive cofx message))) (message-model/receive message cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:chat-received-message/add :chat-received-message/add
@ -39,7 +38,7 @@
{:short-preview short-preview {:short-preview short-preview
:preview preview})])}])}) :preview preview})])}])})
;; regular non command message, we can add it right away ;; regular non command message, we can add it right away
(message-model/receive cofx message))))) (message-model/receive message cofx)))))
;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored ;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -1,43 +1,26 @@
(ns status-im.chat.events.requests (ns status-im.chat.events.requests
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers] [status-im.constants :as constants]
[status-im.data-store.requests :as requests-store])) [status-im.utils.handlers :as handlers]))
;; Coeffects
(re-frame/reg-cofx
:get-stored-unanswered-requests
(fn [cofx _]
(assoc cofx :stored-unanswered-requests (requests-store/get-all-unanswered))))
;; Effects
(re-frame/reg-fx
::mark-as-answered
(fn [{:keys [chat-id message-id]}]
(requests-store/mark-as-answered chat-id message-id)))
(re-frame/reg-fx
::save-request
(fn [request]
(requests-store/save request)))
;; Functions ;; Functions
(defn request-answered (defn request-answered
"Takes fx, chat-id and message, updates fx with necessary data for markin request as answered" "Takes chat-id, message-id and cofx, returns fx necessary data for marking request as answered"
[fx chat-id message-id] [chat-id message-id {:keys [db]}]
(-> fx (when message-id
(update-in [:db :chats chat-id :requests] dissoc message-id) {:db (update-in db [:chats chat-id :requests] dissoc message-id)
(assoc ::mark-as-answered {:chat-id chat-id :data-store/mark-request-as-answered {:chat-id chat-id
:message-id message-id}))) :message-id message-id}}))
(defn add-request (defn add-request
"Takes fx, chat-id and message, updates fx with necessary data for adding new request" "Takes chat-id, message-id + cofx and returns fx with necessary data for adding new request"
[fx chat-id {:keys [message-id content]}] [chat-id message-id {:keys [db]}]
(let [request {:chat-id chat-id (let [{:keys [content-type content]} (get-in db [:chats chat-id :messages message-id])]
:message-id message-id (when (= content-type constants/content-type-command-request)
:response (:command content) (let [request {:chat-id chat-id
:status "open"}] :message-id message-id
(-> fx :response (:request-command content)
(assoc-in [:db :chats chat-id :requests message-id] request) :status "open"}]
(assoc ::save-request request)))) {:db (assoc-in db [:chats chat-id :requests message-id] request)
:data-store/save-request request}))))

View File

@ -3,8 +3,7 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.chat.models.message :as message-model] [status-im.chat.models.message :as message-model]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.protocol.core :as protocol] [status-im.utils.handlers :as handlers]
[status-im.utils.handlers :as handlers]
[status-im.utils.types :as types])) [status-im.utils.types :as types]))
(re-frame/reg-fx (re-frame/reg-fx
@ -15,21 +14,6 @@
(log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json) (log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json)
(status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %))))) (status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %)))))
(re-frame/reg-fx
:send-group-message
(fn [value]
(protocol/send-group-message! value)))
(re-frame/reg-fx
:send-public-group-message
(fn [value]
(protocol/send-public-group-message! value)))
(re-frame/reg-fx
:send-message
(fn [value]
(protocol/send-message! value)))
;;;; Handlers ;;;; Handlers
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -1,195 +0,0 @@
(ns status-im.chat.handlers
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.models :as models]
[status-im.i18n :as i18n]
[status-im.protocol.core :as protocol]
[status-im.ui.components.styles :as components.styles]
[status-im.utils.handlers :as handlers]
[status-im.utils.random :as random]
status-im.chat.events
[status-im.utils.datetime :as datetime]))
(handlers/register-handler
:leave-group-chat
;; todo order of operations tbd
(re-frame/after (fn [_ _] (re-frame/dispatch [:navigation-replace :home])))
(handlers/side-effect!
(fn [{:keys [web3 current-chat-id chats current-public-key]} _]
(let [{:keys [public-key private-key public?]} (chats current-chat-id)]
(protocol/stop-watching-group!
{:web3 web3
:group-id current-chat-id})
(when-not public?
(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)}})))
(re-frame/dispatch [:remove-chat current-chat-id]))))
(handlers/register-handler-fx
:leave-group-chat?
(fn []
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
:content (i18n/label :t/leave-group-chat-confirmation)
:confirm-button-text (i18n/label :t/leave)
:on-accept #(re-frame/dispatch [:leave-group-chat])}}))
(handlers/register-handler :update-group-message
(handlers/side-effect!
(fn [{:keys [current-public-key web3 chats]}
[_ {:keys [from]
{:keys [group-id keypair timestamp]} :payload}]]
(let [{:keys [private public]} keypair
{:keys [group-admin is-active] :as chat} (get chats group-id)]
(when (and (= from group-admin
(or (nil? chat)
(models/new-update? chat timestamp))))
(re-frame/dispatch [:update-chat! {:chat-id group-id
:public-key public
:private-key private
:updated-at timestamp}])
(when is-active
(protocol/start-watching-group!
{:web3 web3
:group-id group-id
:identity current-public-key
:keypair keypair
:callback #(re-frame/dispatch [:incoming-message %1 %2])})))))))
(re-frame/reg-fx
::start-watching-group
(fn [{:keys [group-id web3 current-public-key keypair]}]
(protocol/start-watching-group!
{:web3 web3
:group-id group-id
:identity current-public-key
:keypair keypair
:callback #(re-frame/dispatch [:incoming-message %1 %2])})))
(handlers/register-handler-fx
:create-new-public-chat
[(re-frame/inject-cofx :now)]
(fn [{:keys [db now]} [_ topic]]
(let [exists? (boolean (get-in db [:chats topic]))
chat {:chat-id topic
:name topic
:color components.styles/default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp now}]
(merge
(when-not exists?
{:db (assoc-in db [:chats (:chat-id chat)] chat)
:save-chat chat
::start-watching-group (merge {:group-id topic}
(select-keys db [:web3 :current-public-key]))})
{:dispatch-n [[:navigate-to-clean :home]
[:navigate-to-chat topic]]}))))
(re-frame/reg-fx
::start-listen-group
(fn [{: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 #(re-frame/dispatch [:incoming-message %1 %2])}))))
(defn group-name-from-contacts [contacts selected-contacts username]
(->> (select-keys contacts selected-contacts)
vals
(map :name)
(cons username)
(string/join ", ")))
(defn prepare-group-chat
[{:keys [current-public-key username]
:group/keys [selected-contacts]
:contacts/keys [contacts]} group-name timestamp]
(let [selected-contacts' (mapv #(hash-map :identity %) selected-contacts)
chat-name (if-not (string/blank? group-name)
group-name
(group-name-from-contacts contacts selected-contacts username))
{:keys [public private]} (protocol/new-keypair!)]
{:chat-id (random/id)
:public-key public
:private-key private
:name chat-name
:color components.styles/default-chat-color
:group-chat true
:group-admin current-public-key
:is-active true
:timestamp timestamp
:contacts selected-contacts'}))
(handlers/register-handler-fx
:create-new-group-chat-and-open
[(re-frame/inject-cofx :now)]
(fn [{:keys [db now]} [_ group-name]]
(let [new-chat (prepare-group-chat (select-keys db [:group/selected-contacts :current-public-key :username
:contacts/contacts])
group-name
now)]
{:db (-> db
(assoc-in [:chats (:chat-id new-chat)] new-chat)
(assoc :group/selected-contacts #{}))
:save-chat new-chat
::start-listen-group (merge {:new-chat new-chat}
(select-keys db [:web3 :current-public-key]))
:dispatch-n [[:navigate-to-clean :home]
[:navigate-to-chat (:chat-id new-chat)]]})))
(handlers/register-handler-fx
:group-chat-invite-received
(fn [{{:keys [current-public-key] :as db} :db}
[_ {:keys [from]
{:keys [group-id group-name contacts keypair timestamp]} :payload}]]
(let [{:keys [private public]} keypair]
(let [contacts' (keep (fn [ident]
(when (not= ident current-public-key)
{:identity ident})) contacts)
chat (get-in db [:chats group-id])
new-chat {:chat-id group-id
:name group-name
:group-chat true
:group-admin from
:public-key public
:private-key private
:contacts contacts'
:added-to-at timestamp
:timestamp timestamp
:is-active true}]
(when (or (nil? chat)
(models/new-update? chat timestamp))
{::start-watching-group (merge {:group-id group-id
:keypair keypair}
(select-keys db [:web3 :current-public-key]))
:dispatch (if chat
[:update-chat! new-chat]
[:add-chat group-id new-chat])})))))
(handlers/register-handler-fx
:show-profile
(fn [{db :db} [_ identity]]
{:db (assoc db :contacts/identity identity)
:dispatch [:navigate-forget :profile]}))

View File

@ -1,6 +1,8 @@
(ns status-im.chat.models (ns status-im.chat.models
(:require [status-im.ui.components.styles :as styles] (:require [status-im.ui.components.styles :as styles]
[status-im.utils.gfycat.core :as gfycat])) [status-im.utils.gfycat.core :as gfycat]
[status-im.utils.handlers :as handlers]
[status-im.transport.core :as transport]))
(defn set-chat-ui-props (defn set-chat-ui-props
"Updates ui-props in active chat by merging provided kvs into them" "Updates ui-props in active chat by merging provided kvs into them"
@ -13,67 +15,105 @@
(update-in db [:chat-ui-props current-chat-id ui-element] not)) (update-in db [:chat-ui-props current-chat-id ui-element] not))
(defn- create-new-chat (defn- create-new-chat
[{:keys [db now]} chat-id] [chat-id {:keys [db now]}]
(let [name (get-in db [:contacts/contacts chat-id :name])] (let [name (get-in db [:contacts/contacts chat-id :name])]
{:chat-id chat-id {:chat-id chat-id
:name (or name (gfycat/generate-gfy chat-id)) :name (or name (gfycat/generate-gfy chat-id))
:color styles/default-chat-color :color styles/default-chat-color
:group-chat false :group-chat false
:is-active true :is-active true
:timestamp now :timestamp now
:contacts [{:identity chat-id}]})) :contacts [{:identity chat-id}]
:last-from-clock-value 0
:last-to-clock-value 0}))
(defn add-chat (defn add-chat
"Adds new chat to db & realm, if the chat with same id already exists, justs restores it" "Adds new chat to db & realm, if the chat with same id already exists, justs restores it"
([cofx chat-id] ([chat-id cofx]
(add-chat cofx chat-id {})) (add-chat chat-id {} cofx))
([{:keys [db get-stored-chat] :as cofx} chat-id chat-props] ([chat-id chat-props {:keys [db get-stored-chat] :as cofx}]
(let [{:keys [deleted-chats]} db (let [{:keys [deleted-chats]} db
new-chat (merge (if (get deleted-chats chat-id) new-chat (merge (if (get deleted-chats chat-id)
(assoc (get-stored-chat chat-id) :is-active true) (assoc (get-stored-chat chat-id) :is-active true)
(create-new-chat cofx chat-id)) (create-new-chat chat-id cofx))
chat-props)] chat-props)]
{:db (-> db {:db (-> db
(update :chats assoc chat-id new-chat) (update :chats assoc chat-id new-chat)
(update :deleted-chats (fnil disj #{}) chat-id)) (update :deleted-chats (fnil disj #{}) chat-id))
:save-chat new-chat}))) :data-store/save-chat new-chat})))
(defn add-public-chat
"Adds new public group chat to db & realm"
[topic {:keys [db now] :as cofx}]
(let [chat {:chat-id topic
:name topic
:color styles/default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp now
:last-to-clock-value 0
:last-from-clock-value 0}]
{:db (assoc-in db [:chats topic] chat)
:data-store/save-chat chat}))
(defn add-group-chat
"Adds new private group chat to db & realm"
[chat-id chat-name admin participants {:keys [db now] :as cofx}]
(let [chat {:chat-id chat-id
:name chat-name
:color styles/default-chat-color
:group-chat true
:group-admin admin
:is-active true
:timestamp now
:contacts (mapv (partial hash-map :identity) participants)
:last-to-clock-value 0
:last-from-clock-value 0}]
{:db (assoc-in db [:chats chat-id] chat)
:data-store/save-chat chat}))
;; TODO (yenda): there should be an option to update the timestamp ;; TODO (yenda): there should be an option to update the timestamp
;; this shouldn't need a specific function like `upsert-chat` which ;; this shouldn't need a specific function like `upsert-chat` which
;; is wrongfuly named ;; is wrongfuly named
(defn update-chat (defn update-chat
"Updates chat properties when not deleted, if chat is not present in app-db, creates a default new one" "Updates chat properties when not deleted, if chat is not present in app-db, creates a default new one"
[{:keys [db] :as cofx} {:keys [chat-id] :as chat-props}] [{:keys [chat-id] :as chat-props} {:keys [db] :as cofx}]
(let [{:keys [chats deleted-chats]} db] (let [{:keys [chats deleted-chats]} db]
(if (get deleted-chats chat-id) ;; when chat is deleted, don't change anything (if (get deleted-chats chat-id) ;; when chat is deleted, don't change anything
{:db db} {:db db}
(let [chat (merge (or (get chats chat-id) (let [chat (merge (or (get chats chat-id)
(create-new-chat cofx chat-id)) (create-new-chat chat-id cofx))
chat-props)] chat-props)]
{:db (update-in db [:chats chat-id] merge chat) {:db (update-in db [:chats chat-id] merge chat)
:save-chat chat})))) :data-store/save-chat chat}))))
;; TODO (yenda): an upsert is suppose to add the entry if it doesn't ;; TODO (yenda): an upsert is suppose to add the entry if it doesn't
;; exist and update it if it does ;; exist and update it if it does
(defn upsert-chat (defn upsert-chat
"Just like `update-chat` only implicitely updates timestamp" "Just like `update-chat` only implicitely updates timestamp"
[cofx chat] [chat cofx]
(update-chat cofx (assoc chat :timestamp (:now cofx)))) (update-chat (assoc chat :timestamp (:now cofx)) cofx))
(defn new-update? [{:keys [added-to-at removed-at removed-from-at]} timestamp] (defn new-update? [{:keys [added-to-at removed-at removed-from-at]} timestamp]
(and (> timestamp added-to-at) (and (> timestamp added-to-at)
(> timestamp removed-at) (> timestamp removed-at)
(> timestamp removed-from-at))) (> timestamp removed-from-at)))
(defn remove-chat [{:keys [db]} chat-id] (defn remove-chat [chat-id {:keys [db] :as cofx}]
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])] (let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])
(cond-> {:db (-> db fx (cond-> {:db (-> db
(update :chats dissoc chat-id) (update :chats dissoc chat-id)
(update :deleted-chats (fnil conj #{}) chat-id)) (update :deleted-chats (fnil conj #{}) chat-id))}
:delete-pending-messages chat-id} (or group-chat debug?)
(or group-chat debug?) (assoc :data-store/delete-messages chat-id)
(assoc :delete-messages chat-id) debug?
debug? (assoc :data-store/delete-chat chat-id)
(assoc :delete-chat chat-id) (not debug?)
(not debug?) (assoc :data-store/deactivate-chat chat-id))]
(assoc :deactivate-chat chat-id)))) (handlers/merge-fx cofx fx (transport/unsubscribe-from-chat chat-id))))
(defn bot-only-chat? [db chat-id]
(let [{:keys [group-chat contacts]} (get-in db [:chats chat-id])]
(and (not group-chat)
(get-in db [:contacts/contacts (:identity (first contacts)) :dapp?]))))

View File

@ -1,12 +1,8 @@
(ns status-im.chat.models.input (ns status-im.chat.models.input
(:require [clojure.string :as str] (:require [clojure.string :as str]
[goog.object :as object] [goog.object :as object]
[status-im.ui.components.react :as rc]
[status-im.native-module.core :as status]
[status-im.chat.constants :as const] [status-im.chat.constants :as const]
[status-im.chat.models.commands :as commands-model] [status-im.chat.models.commands :as commands-model]
[status-im.chat.views.input.validation-messages :refer [validation-message]]
[status-im.i18n :as i18n]
[status-im.js-dependencies :as dependencies] [status-im.js-dependencies :as dependencies]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
@ -203,28 +199,3 @@
[(keyword (get-in params [i :name])) value])) [(keyword (get-in params [i :name])) value]))
(remove #(nil? (first %))) (remove #(nil? (first %)))
(into {})))) (into {}))))
(defn modified-db-after-change
"Returns the new db object that should be used after any input change."
[{:keys [current-chat-id] :as db}]
(let [command (selected-chat-command db)
prev-command (get-in db [:chat-ui-props current-chat-id :prev-command])]
(if command
(cond-> db
;; clear the bot db
(not= prev-command (-> command :command :name))
(assoc-in [:bot-db (or (:bot command) current-chat-id)] nil)
;; clear the chat's validation messages
true
(assoc-in [:chat-ui-props current-chat-id :validation-messages] nil))
(-> db
;; clear input metadata
(assoc-in [:chats current-chat-id :input-metadata] nil)
;; clear
(update-in [:chat-ui-props current-chat-id]
merge
{:result-box nil
:validation-messages nil
:prev-command (-> command :command :name)})))))
(defmulti validation-handler (fn [name] (keyword name)))

View File

@ -6,14 +6,14 @@
[status-im.chat.events.requests :as requests-events] [status-im.chat.events.requests :as requests-events]
[status-im.chat.models :as chat-model] [status-im.chat.models :as chat-model]
[status-im.chat.models.commands :as commands-model] [status-im.chat.models.commands :as commands-model]
[status-im.utils.clocks :as clocks-utils])) [status-im.utils.clocks :as clocks-utils]
[status-im.utils.handlers :as handlers]
(defn- get-current-account [status-im.transport.utils :as transport.utils]
[{:accounts/keys [accounts current-account-id]}] [status-im.transport.message.core :as transport]
(get accounts current-account-id)) [status-im.transport.message.v1.protocol :as protocol]))
(def receive-interceptors (def receive-interceptors
[(re-frame/inject-cofx :get-stored-message) (re-frame/inject-cofx :get-stored-chat) [(re-frame/inject-cofx :data-store/get-message) (re-frame/inject-cofx :data-store/get-chat)
(re-frame/inject-cofx :random-id) re-frame/trim-v]) (re-frame/inject-cofx :random-id) re-frame/trim-v])
(defn- lookup-response-ref (defn- lookup-response-ref
@ -25,71 +25,133 @@
contacts)] contacts)]
(:ref (get available-commands-responses response-name)))) (:ref (get available-commands-responses response-name))))
(defn add-message-to-db (defn- add-message
[db chat-id {:keys [message-id clock-value] :as message} current-chat?] [chat-id {:keys [message-id from-clock-value to-clock-value] :as message} current-chat? {:keys [db]}]
(let [prepared-message (cond-> (assoc message (let [prepared-message (cond-> (assoc message :appearing? true)
:chat-id chat-id
:appearing? true)
(not current-chat?) (not current-chat?)
(assoc :appearing? false))] (assoc :appearing? false))]
(cond-> (-> db {:db (cond-> (-> db
(update-in [:chats chat-id :messages] assoc message-id prepared-message) (update-in [:chats chat-id :messages] dissoc from-clock-value)
(update-in [:chats chat-id :last-clock-value] (fnil max 0) clock-value)) (update-in [:chats chat-id :messages] assoc message-id prepared-message)
(not current-chat?) (update-in [:chats chat-id :last-from-clock-value] max from-clock-value)
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id)))) (update-in [:chats chat-id :last-to-clock-value] max to-clock-value))
(not current-chat?)
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
:data-store/save-message prepared-message}))
;; We start with [0 0] ([from-clock-value to-clock-value]) for each participant of 1-1 chat (local perspective on each device).
;; Now for sending, we always increment the to-clock-value and include it in message payload being sent (so only to-clock-value is present in network message).
;; Locally, the sent message always replicates the latest-from-clock-value of the chat.
;; Upon receiving message, receiver reads the to-clock-value of the received message and sets that to be the from-clock-value locally
;; (this will be also the new latest-from-clock-value of the chat), to-clock-value for the message is the latest-to-clock-value of the 1-1 chat`.
;; All this ensures, that there will be no [from-clock-value to-clock-value] duplicate in chat message on each device + the local order will appear consistent,
;; even if its possible it wont be the same on both devices (for example user A sends 5 messages, during the sending,
;; he receives the message from user B, so his local order will be A1, A2, B, A3, A4, A5, but his messages will take a long time to reach user B,
;; for some reason, so user B will see it as B, A1, A2, A3, A4, A5).
;; I dont think thats very problematic and I dont think we can do much about it without single source of truth where order received messages are serialised
;; and definite order is established (server), it is the case even in the current implementation.
(defn- prepare-chat [chat-id {:keys [db] :as cofx}]
(if (get-in db [:chats chat-id])
(chat-model/upsert-chat {:chat-id chat-id} cofx)
(chat-model/add-chat chat-id cofx)))
(defn- get-current-account [{:accounts/keys [accounts current-account-id]}]
(get accounts current-account-id))
(defn- send-message-seen [chat-id message-id send-seen? cofx]
(when send-seen?
(transport/send (protocol/map->MessagesSeen {:message-ids #{message-id}}) chat-id cofx)))
(defn- placeholder-message [chat-id from timestamp temp-id to-clock]
{:message-id temp-id
:outgoing false
:chat-id chat-id
:from from
:to "me"
:content "Waiting for message to arrive..."
:content-type constants/content-type-placeholder
:show? true
:from-clock-value temp-id
:to-clock-value to-clock
:timestamp timestamp})
(defn- add-placeholder-messages [chat-id from timestamp old-from-clock to-clock new-from-clock {:keys [db]}]
(when (> (- new-from-clock old-from-clock) 1)
{:db (reduce (fn [db temp-id]
(assoc-in db [:chats chat-id :messages temp-id] (placeholder-message chat-id from timestamp temp-id to-clock)))
db
(range (inc old-from-clock) new-from-clock))}))
(defn- add-received-message
[{:keys [from message-id chat-id content content-type timestamp to-clock-value] :as message}
{:keys [db now] :as cofx}]
(let [{:keys [current-chat-id
view-id
access-scope->commands-responses]
:contacts/keys [contacts]} db
{:keys [public-key] :as current-account} (get-current-account db)
current-chat? (and (= :chat view-id) (= current-chat-id chat-id))
{:keys [last-from-clock-value
last-to-clock-value] :as chat} (get-in db [:chats chat-id])
request-command (:request-command content)
command-request? (and (= content-type constants/content-type-command-request)
request-command)
new-from-clock-value (or to-clock-value (inc last-from-clock-value))
new-timestamp (or timestamp now)]
(handlers/merge-fx cofx
(add-message chat-id
(cond-> (assoc message
:timestamp new-timestamp
:show? true
:from-clock-value new-from-clock-value
:to-clock-value last-to-clock-value)
public-key
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
command-request?
(assoc-in [:content :request-command-ref]
(lookup-response-ref access-scope->commands-responses
current-account chat contacts request-command)))
current-chat?)
(send-message-seen chat-id message-id (and public-key
current-chat?
(not (chat-model/bot-only-chat? db chat-id))
(not (= constants/system from))))
(add-placeholder-messages chat-id from new-timestamp last-from-clock-value last-to-clock-value new-from-clock-value))))
(defn receive (defn receive
[{:keys [db now] :as cofx} [{:keys [chat-id message-id] :as message} cofx]
{:keys [from group-id chat-id content-type content message-id timestamp clock-value] (handlers/merge-fx cofx
:as message}] (prepare-chat chat-id)
(let [{:keys [current-chat-id view-id (add-received-message message)
access-scope->commands-responses] :contacts/keys [contacts]} db (requests-events/add-request chat-id message-id)))
{:keys [public-key] :as current-account} (get-current-account db)
chat-identifier (or group-id chat-id from) (defn system-message [chat-id message-id timestamp content]
current-chat? (and (= :chat view-id) {:message-id message-id
(= current-chat-id chat-identifier)) :outgoing false
fx (if (get-in db [:chats chat-identifier]) :chat-id chat-id
(chat-model/upsert-chat cofx {:chat-id chat-identifier :from constants/system
:group-chat (boolean group-id)}) :username constants/system
(chat-model/add-chat cofx chat-identifier)) :timestamp timestamp
chat (get-in fx [:db :chats chat-identifier]) :show? true
command-request? (= content-type constants/content-type-command-request) :content content
command (:command content) :content-type constants/text-content-type})
enriched-message (cond-> (assoc message
:chat-id chat-identifier (defn group-message? [{:keys [message-type]}]
:timestamp (or timestamp now) (#{:group-user-message :public-group-user-message} message-type))
:show? true
:clock-value (clocks-utils/receive
clock-value
(:last-clock-value chat)))
public-key
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
(and command command-request?)
(assoc-in [:content :content-command-ref]
(lookup-response-ref access-scope->commands-responses
current-account
(get-in fx [:db :chats chat-identifier])
contacts
command)))]
(cond-> (-> fx
(update :db add-message-to-db chat-identifier enriched-message current-chat?)
(assoc :save-message (dissoc enriched-message :new?)))
command-request?
(requests-events/add-request chat-identifier enriched-message))))
(defn add-to-chat? (defn add-to-chat?
[{:keys [db get-stored-message]} {:keys [group-id chat-id from message-id]}] [{:keys [db get-stored-message]} {:keys [chat-id from message-id] :as message}]
(let [chat-identifier (or group-id chat-id from) (let [{:keys [chats deleted-chats current-public-key]} db
{:keys [chats deleted-chats current-public-key]} db {:keys [messages not-loaded-message-ids]} (get chats chat-id)]
{:keys [messages not-loaded-message-ids]} (get chats chat-identifier)]
(when (not= from current-public-key) (when (not= from current-public-key)
(if group-id (if (group-message? message)
(not (or (get deleted-chats chat-identifier) (not (or (get deleted-chats chat-id)
(get messages message-id) (get messages message-id)
(get not-loaded-message-ids message-id))) (get not-loaded-message-ids message-id)))
(not (or (get messages message-id) (not (or (get messages message-id)
(get not-loaded-message-ids message-id) (get not-loaded-message-ids message-id)
(and (get deleted-chats chat-identifier) (and (get deleted-chats chat-id)
(get-stored-message message-id)))))))) (get-stored-message message-id))))))))
(defn message-seen-by? [message user-pk] (defn message-seen-by? [message user-pk]
@ -99,7 +161,7 @@
(def send-interceptors (def send-interceptors
[(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :random-id-seq) [(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :random-id-seq)
(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]) (re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v])
(defn- handle-message-from-bot [{:keys [random-id] :as cofx} {:keys [message chat-id]}] (defn- handle-message-from-bot [{:keys [random-id] :as cofx} {:keys [message chat-id]}]
(when-let [message (cond (when-let [message (cond
@ -120,15 +182,12 @@
:chat-id chat-id :chat-id chat-id
:from chat-id :from chat-id
:to "me"})] :to "me"})]
(receive cofx message))) (receive message cofx)))
(defn- send-dapp-message! (defn- send-dapp-message!
[{{:accounts/keys [current-account-id] :as db} :db :as cofx} [{{:accounts/keys [current-account-id] :as db} :db :as cofx} chat-id {:keys [content-type] :as message}]
{{:keys [message-type] (if (= content-type constants/content-type-command)
:as message} :message (when-let [text-message (get-in message [:content :handler-data :text-message])]
:keys [chat-id command] :as args}]
(if command
(when-let [text-message (get-in command [:content :handler-data :text-message])]
(handle-message-from-bot cofx {:message text-message (handle-message-from-bot cofx {:message text-message
:chat-id chat-id})) :chat-id chat-id}))
(let [data (get-in db [:local-storage chat-id])] (let [data (get-in db [:local-storage chat-id])]
@ -138,146 +197,107 @@
:context {:data data :context {:data data
:from current-account-id}}}))) :from current-account-id}}})))
(defn- generate-message (defn- send
[{:keys [network-status]} chat-id message] [chat-id send-record {{:contacts/keys [contacts]} :db :as cofx}]
(assoc (select-keys message [:from :message-id])
:payload (cond-> (select-keys message [:content :content-type :clock-value :timestamp :show?])
(= :offline network-status)
(assoc :show? false))))
(defn send
[{{:keys [web3 chats]
:accounts/keys [accounts current-account-id]
:contacts/keys [contacts] :as db} :db :as cofx}
{:keys [chat-id command message] :as args}]
(let [{:keys [dapp? fcm-token]} (get contacts chat-id)] (let [{:keys [dapp? fcm-token]} (get contacts chat-id)]
(if dapp? (if dapp?
(send-dapp-message! cofx args) (send-dapp-message! cofx chat-id send-record)
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id]) (if fcm-token
options {:web3 web3 (handlers/merge-fx cofx
:message (generate-message db chat-id (or command message))}] {:send-notification {:message "message"
(cond :payload {:title "Status" :body "You have a new message"}
(and group-chat (not public?)) :tokens [fcm-token]}}
(let [{:keys [public-key private-key]} (get chats chat-id)] (transport/send send-record chat-id))
{:send-group-message (assoc options (transport/send send-record chat-id cofx)))))
:group-id chat-id
:keypair {:public public-key
:private private-key})})
(and group-chat public?) (defn add-message-type [message {:keys [chat-id group-chat public?]}]
{:send-public-group-message (assoc options :group-id chat-id (cond-> message
:username (get-in accounts [current-account-id :name]))} (not group-chat)
(assoc :message-type :user-message)
(and group-chat public?)
(assoc :message-type :public-group-user-message)
(and group-chat (not public?))
(assoc :message-type :group-user-message)))
:else (defn- prepare-plain-message [{:keys [identity message-text]}
(merge {:send-message (assoc-in options [:message :to] chat-id)} {:keys [chat-id last-to-clock-value last-from-clock-value] :as chat} now]
(when fcm-token {:send-notification {:message "message" (add-message-type {:chat-id chat-id
:payload {:title "Status" :body "You have a new message"} :content message-text
:tokens [fcm-token]}}))))))) :from identity
:content-type constants/text-content-type
:outgoing true
:timestamp now
:to-clock-value (inc last-to-clock-value)
:from-clock-value last-from-clock-value
:show? true}
chat))
(defn- prepare-message [params chat now random-id] (def ^:private transport-keys [:content :content-type :message-type :to-clock-value :timestamp])
(let [{:keys [chat-id identity message-text]} params
{:keys [group-chat public? last-clock-value]} chat (defn- upsert-and-send [{:keys [chat-id] :as message} cofx]
message {:message-id random-id (let [send-record (protocol/map->Message (select-keys message transport-keys))
:chat-id chat-id message-with-id (assoc message :message-id (transport.utils/message-id send-record))]
:content message-text (handlers/merge-fx cofx
:from identity (chat-model/upsert-chat {:chat-id chat-id})
:content-type constants/text-content-type (add-message chat-id message-with-id true)
:outgoing true (send chat-id send-record))))
:timestamp now
:clock-value (clocks-utils/send last-clock-value)
:show? true}]
(cond-> message
(not group-chat)
(assoc :message-type :user-message
:to chat-id)
group-chat
(assoc :group-id chat-id)
(and group-chat public?)
(assoc :message-type :public-group-user-message)
(and group-chat (not public?))
(assoc :message-type :group-user-message)
(not group-chat)
(assoc :to chat-id :message-type :user-message))))
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}] (defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
(let [chat (get-in db [:chats chat-id]) (upsert-and-send (prepare-plain-message params (get-in db [:chats chat-id]) now) cofx))
message (prepare-message params chat now random-id)
params' (assoc params :message message)
fx (-> (chat-model/upsert-chat cofx {:chat-id chat-id})
(update :db add-message-to-db chat-id message true)
(assoc :save-message message))]
(merge fx (send cofx params'))))
(defn- prepare-command (defn- prepare-command-message
[identity chat-id now clock-value [identity
{:keys [last-to-clock-value last-from-clock-value chat-id] :as chat}
now
{request-params :params {request-params :params
request-command :command request-command :command
:keys [prefill prefillBotDb] :keys [prefill prefillBotDb]
:as request} :as request}
{:keys [id params command to-message handler-data content-type]}] {:keys [params command handler-data content-type]}]
(let [content (if request (let [content (if request
{:command request-command {:request-command request-command
:params (assoc request-params :bot-db (:bot-db params)) ;; TODO janherich this is technically not correct, but works for now
:prefill prefill :request-command-ref (:ref command)
:prefill-bot-db prefillBotDb} :params (assoc request-params :bot-db (:bot-db params))
{:command (:name command) :prefill prefill
:scope (:scope command) :prefill-bot-db prefillBotDb}
:params params}) {:params params})
content' (assoc content :handler-data handler-data content' (assoc content
:type (name (:type command)) :command (:name command)
:content-command (:name command) :handler-data handler-data
:content-command-scope-bitmask (:scope-bitmask command) :type (name (:type command))
:content-command-ref (:ref command) :command-scope-bitmask (:scope-bitmask command)
:preview (:preview command) :command-ref (:ref command)
:short-preview (:short-preview command) :preview (:preview command)
:bot (or (:bot command) :short-preview (:short-preview command)
(:owner-id command)))] :bot (:owner-id command))]
{:message-id id (add-message-type {:chat-id chat-id
:from identity :from identity
:to chat-id :timestamp now
:timestamp now :content content'
:content content' :content-type (or content-type
:content-type (or content-type (if request
(if request constants/content-type-command-request
constants/content-type-command-request constants/content-type-command))
constants/content-type-command)) :outgoing true
:outgoing true :to-clock-value (inc last-to-clock-value)
:to-message to-message :from-clock-value last-from-clock-value
:type (:type command) :show? true}
:has-handler (:has-handler command) chat)))
:clock-value (clocks-utils/send clock-value)
:show? true})) (defn- add-console-responses
[command handler-data {:keys [random-id-seq]}]
{:dispatch-n (->> (console-events/console-respond-command-messages command handler-data random-id-seq)
(mapv (partial vector :chat-received-message/add)))})
(defn send-command (defn send-command
[{{:keys [current-public-key chats]} :db :keys [now random-id-seq] :as cofx} params] [{{:keys [current-public-key chats] :as db} :db :keys [now] :as cofx} params]
(let [{{:keys [handler-data (let [{{:keys [handler-data to-message command] :as content} :command chat-id :chat-id} params
command] request (:request handler-data)]
:as content} :command (handlers/merge-fx cofx
chat-id :chat-id} params (upsert-and-send (prepare-command-message current-public-key (get chats chat-id) now request content))
request (:request handler-data) (add-console-responses command handler-data)
last-clock-value (get-in chats [chat-id :last-clock-value]) (requests-events/request-answered chat-id to-message))))
hidden-params (->> (:params command)
(filter :hidden)
(map :name))
command' (prepare-command current-public-key chat-id now last-clock-value request content)
params' (assoc params :command command')
fx (-> (chat-model/upsert-chat cofx {:chat-id chat-id})
(update :db add-message-to-db chat-id command' true)
(assoc :save-message (-> command'
(assoc :chat-id chat-id)
(update-in [:content :params]
#(apply dissoc % hidden-params))
(dissoc :to-message :has-handler :raw-input))))]
(cond-> (merge fx (send cofx params'))
(:to-message command')
(requests-events/request-answered chat-id (:to-message command'))
(= constants/console-chat-id chat-id)
(as-> fx'
(let [messages (console-events/console-respond-command-messages params' random-id-seq)
events (mapv #(vector :chat-received-message/add %) messages)]
(update fx' :dispatch-n into events))))))
(defn invoke-console-command-handler (defn invoke-console-command-handler
[{:keys [db] :as cofx} {:keys [command] :as command-params}] [{:keys [db] :as cofx} {:keys [command] :as command-params}]
@ -311,9 +331,9 @@
{:call-jail {:jail-id identity {:call-jail {:jail-id identity
:path [handler-type [name scope-bitmask] :handler] :path [handler-type [name scope-bitmask] :handler]
:params jail-params :params jail-params
:callback-events-creator (fn [jail-response] :callback-event-creator (fn [jail-response]
(when-not (:async-handler command) (when-not (:async-handler command)
[[:command-handler! chat-id orig-params jail-response]]))}})) [:command-handler! chat-id orig-params jail-response]))}}))
(defn process-command (defn process-command
[cofx {:keys [command chat-id] :as params}] [cofx {:keys [command chat-id] :as params}]

View File

@ -35,13 +35,11 @@
[react/view style/action [react/view style/action
[vector-icons/icon :icons/dots-horizontal]]]) [vector-icons/icon :icons/dots-horizontal]]])
(defview add-contact-bar [] (defview add-contact-bar [contact-identity]
(letsubs [chat-id [:get-current-chat-id] (letsubs [{:keys [pending?] :as contact} [:contact-by-identity contact-identity]]
pending-contact? [:current-contact :pending?]] (when (or pending? (not contact)) ;; contact is pending or not in contact list at all
(when (or (nil? pending-contact?) ; user not in contact list
pending-contact?)
[react/touchable-highlight [react/touchable-highlight
{:on-press #(re-frame/dispatch [:add-contact chat-id]) {:on-press #(re-frame/dispatch [:add-contact contact-identity])
:accessibility-label :add-to-contacts-button} :accessibility-label :add-to-contacts-button}
[react/view style/add-contact [react/view style/add-contact
[react/text {:style style/add-contact-text} [react/text {:style style/add-contact-text}
@ -52,7 +50,7 @@
:options (actions/actions group-chat? chat-id public?)})) :options (actions/actions group-chat? chat-id public?)}))
(defview chat-toolbar [public?] (defview chat-toolbar [public?]
(letsubs [{:keys [group-chat name chat-id]} [:get-current-chat]] (letsubs [{:keys [group-chat name chat-id contacts]} [:get-current-chat]]
[react/view [react/view
[status-bar/status-bar] [status-bar/status-bar]
[toolbar/platform-agnostic-toolbar {} [toolbar/platform-agnostic-toolbar {}
@ -62,7 +60,7 @@
:icon-opts {:color :black :icon-opts {:color :black
:accessibility-label :chat-menu-button} :accessibility-label :chat-menu-button}
:handler #(on-options chat-id name group-chat public?)}]]] :handler #(on-options chat-id name group-chat public?)}]]]
(when-not (or public? group-chat) [add-contact-bar])])) (when-not (or public? group-chat) [add-contact-bar (-> contacts first :identity)])]))
(defmulti message-row (fn [{{:keys [type]} :row}] type)) (defmulti message-row (fn [{{:keys [type]} :row}] type))

View File

@ -93,10 +93,11 @@
(defn message-datemark-groups (defn message-datemark-groups
"Transforms map of messages into sequence of `[datemark messages]` tuples, where "Transforms map of messages into sequence of `[datemark messages]` tuples, where
messages with particular datemark are sorted according to their `:clock-value` and messages with particular datemark are sorted according to their `:clock-values` and
tuples themeselves are sorted according to the highest `:clock-value` in the messages." tuples themeselves are sorted according to the highest `:clock-values` in the messages."
[id->messages] [id->messages]
(let [datemark->messages (transduce (comp (map second) (let [clock-sorter (juxt :from-clock-value :to-clock-value)
datemark->messages (transduce (comp (map second)
(filter :show?) (filter :show?)
(map (fn [{:keys [timestamp] :as msg}] (map (fn [{:keys [timestamp] :as msg}]
(assoc msg :datemark (time/day-relative timestamp))))) (assoc msg :datemark (time/day-relative timestamp)))))
@ -106,8 +107,9 @@
id->messages)] id->messages)]
(->> datemark->messages (->> datemark->messages
(map (fn [[datemark messages]] (map (fn [[datemark messages]]
[datemark (sort-by :clock-value > messages)])) [datemark (->> messages (sort-by clock-sorter) reverse)]))
(sort-by (comp :clock-value first second) >)))) (sort-by (comp clock-sorter first second))
reverse)))
(reg-sub (reg-sub
:get-chat-message-datemark-groups :get-chat-message-datemark-groups
@ -330,4 +332,4 @@
:get-chats-unread-messages-number :get-chats-unread-messages-number
:<- [:get-active-chats] :<- [:get-active-chats]
(fn [chats _] (fn [chats _]
(apply + (map #(count (:unviewed-messages %)) (vals chats))))) (apply + (map #(count (:unviewed-messages %)) (vals chats)))))

View File

@ -16,7 +16,7 @@
(defn- delete-chat [chat-id group?] (defn- delete-chat [chat-id group?]
{:label (i18n/label :t/delete-chat) {:label (i18n/label :t/delete-chat)
:action #(re-frame/dispatch [:delete-chat? chat-id group?])}) :action #(re-frame/dispatch [:remove-chat-and-navigate-home? chat-id group?])})
(defn- leave-group-chat [chat-id public?] (defn- leave-group-chat [chat-id public?]
{:label (i18n/label (if public? :t/leave-public-chat :t/leave-group-chat)) {:label (i18n/label (if public? :t/leave-public-chat :t/leave-group-chat))

View File

@ -17,8 +17,7 @@
[status-im.utils.utils :as utils])) [status-im.utils.utils :as utils]))
(defview basic-text-input [{:keys [set-layout-height-fn set-container-width-fn height single-line-input?]}] (defview basic-text-input [{:keys [set-layout-height-fn set-container-width-fn height single-line-input?]}]
(letsubs [input-text [:chat :input-text] (letsubs [input-text [:chat :input-text]
command [:selected-chat-command]
input-focused? [:get-current-chat-ui-prop :input-focused?] input-focused? [:get-current-chat-ui-prop :input-focused?]
input-ref (atom nil)] input-ref (atom nil)]
[react/text-input [react/text-input
@ -49,10 +48,7 @@
content-size) content-size)
(set-layout-height-fn (.-height content-size))) (set-layout-height-fn (.-height content-size)))
(when (not= text input-text) (when (not= text input-text)
(re-frame/dispatch [:set-chat-input-text text]) (re-frame/dispatch [:set-chat-input-text text]))))
(when command
(re-frame/dispatch [:load-chat-parameter-box (:command command)]))
(re-frame/dispatch [:update-input-data]))))
:on-content-size-change (when (and (not input-focused?) :on-content-size-change (when (and (not input-focused?)
(not single-line-input?)) (not single-line-input?))
#(let [s (.-contentSize (.-nativeEvent %)) #(let [s (.-contentSize (.-nativeEvent %))
@ -120,8 +116,7 @@
[react/text-input (merge {:ref #(re-frame/dispatch [:set-chat-ui-props {:seq-input-ref %}]) [react/text-input (merge {:ref #(re-frame/dispatch [:set-chat-ui-props {:seq-input-ref %}])
:style (style/seq-input-text command-width container-width) :style (style/seq-input-text command-width container-width)
:default-value (or seq-arg-input-text "") :default-value (or seq-arg-input-text "")
:on-change-text #(do (re-frame/dispatch [:set-chat-seq-arg-input-text %]) :on-change-text #(do (re-frame/dispatch [:set-chat-seq-arg-input-text %])
(re-frame/dispatch [:load-chat-parameter-box (:command command)])
(re-frame/dispatch [:set-chat-ui-props {:validation-messages nil}])) (re-frame/dispatch [:set-chat-ui-props {:validation-messages nil}]))
:placeholder placeholder :placeholder placeholder
:accessibility-label :chat-request-input :accessibility-label :chat-request-input

View File

@ -58,7 +58,7 @@
(defview message-content-command (defview message-content-command
[{:keys [content params] :as message}] [{:keys [content params] :as message}]
(letsubs [command [:get-command (:content-command-ref content)]] (letsubs [command [:get-command (:command-ref content)]]
(let [preview (:preview content) (let [preview (:preview content)
{:keys [color] icon-path :icon} command] {:keys [color] icon-path :icon} command]
[react/view style/content-command-view [react/view style/content-command-view
@ -119,7 +119,7 @@
(fn [text-seq] (fn [text-seq]
(map (fn [text] {:text text :url? false}) text-seq)))) (map (fn [text] {:text text :url? false}) text-seq))))
(defn- autolink [string on-press] (defn- autolink [string event-on-press]
(->> (parse-url string) (->> (parse-url string)
(map-indexed (fn [idx {:keys [text url?]}] (map-indexed (fn [idx {:keys [text url?]}]
(if url? (if url?
@ -127,7 +127,7 @@
[react/text [react/text
{:key idx {:key idx
:style {:color colors/blue} :style {:color colors/blue}
:on-press #(on-press url)} :on-press #(re-frame/dispatch [event-on-press url])}
url]) url])
text))) text)))
vec)) vec))
@ -142,7 +142,7 @@
replacements)) replacements))
;; todo rewrite this, naive implementation ;; todo rewrite this, naive implementation
(defn- parse-text [string url-on-press] (defn- parse-text [string event-on-press]
(parse-str-regx string (parse-str-regx string
regx-styled regx-styled
(fn [text-seq] (fn [text-seq]
@ -157,15 +157,22 @@
(map-indexed (fn [idx string] (map-indexed (fn [idx string]
(apply react/text (apply react/text
{:key (str idx "_" string)} {:key (str idx "_" string)}
(autolink string url-on-press))) (autolink string event-on-press)))
text-seq)))) text-seq))))
(def cached-parse-text (memoize parse-text))
(defn text-message (defn text-message
[{:keys [content] :as message}] [{:keys [content] :as message}]
[message-view message [message-view message
(let [parsed-text (parse-text content #(re-frame/dispatch [:browse-link-from-message %]))] (let [parsed-text (cached-parse-text content :browse-link-from-message)]
[react/text {:style (style/text-message message)} parsed-text])]) [react/text {:style (style/text-message message)} parsed-text])])
(defn placeholder-message
[{:keys [content] :as message}]
[message-view message
[react/text {:style (style/text-message message)} content]])
(defmulti message-content (fn [_ message _] (message :content-type))) (defmulti message-content (fn [_ message _] (message :content-type)))
(defmethod message-content constants/content-type-command-request (defmethod message-content constants/content-type-command-request
@ -190,6 +197,10 @@
[wrapper message [wrapper message
[message-view message [message-content-command message]]]) [message-view message [message-content-command message]]])
(defmethod message-content constants/content-type-placeholder
[wrapper message]
[wrapper message [placeholder-message message]])
(defmethod message-content :default (defmethod message-content :default
[wrapper {:keys [content-type content] :as message}] [wrapper {:keys [content-type content] :as message}]
[wrapper message [wrapper message
@ -203,7 +214,7 @@
:font :default} :font :default}
(i18n/message-status-label status)]]) (i18n/message-status-label status)]])
(defview group-message-delivery-status [{:keys [message-id group-id current-public-key user-statuses] :as msg}] (defview group-message-delivery-status [{:keys [message-id current-public-key user-statuses] :as msg}]
(letsubs [{participants :contacts} [:get-current-chat] (letsubs [{participants :contacts} [:get-current-chat]
contacts [:get-contacts]] contacts [:get-contacts]]
(let [outgoing-status (or (get user-statuses current-public-key) :sending) (let [outgoing-status (or (get user-statuses current-public-key) :sending)
@ -250,14 +261,14 @@
(->> (string/replace photo-path #"contacts://" "") (->> (string/replace photo-path #"contacts://" "")
(keyword) (keyword)
(get resources/contacts)) (get resources/contacts))
{:uri (if (string/blank? photo-path) {:uri photo-path})
(identicon/identicon from)
photo-path)})
:style style/photo}]]) :style style/photo}]])
(defview member-photo [from] (defview member-photo [from]
(letsubs [photo-path [:get-photo-path from]] (letsubs [photo-path [:get-photo-path from]]
(photo from photo-path))) (photo from (if (string/blank? photo-path)
(identicon/identicon from)
photo-path))))
(defview my-photo [from] (defview my-photo [from]
(letsubs [{:keys [photo-path]} [:get-current-account]] (letsubs [{:keys [photo-path]} [:get-current-account]]
@ -286,8 +297,7 @@
content]] content]]
(when last-outgoing? (when last-outgoing?
[react/view style/delivery-status [react/view style/delivery-status
(if (or (= (keyword message-type) :group-user-message) (if (= message-type :group-user-message)
group-chat)
[group-message-delivery-status message] [group-message-delivery-status message]
[message-delivery-status message])])]) [message-delivery-status message])])])
@ -296,11 +306,11 @@
(let [to-value @to-value] (let [to-value @to-value]
(when (pos? to-value) (when (pos? to-value)
(animation/start (animation/start
(animation/timing val {:toValue to-value (animation/timing val {:toValue to-value
:duration 250}) :duration 250})
(fn [arg] (fn [arg]
(when (.-finished arg) (when (.-finished arg)
(callback)))))))) (callback))))))))
(defn message-container [message & children] (defn message-container [message & children]
(if (:appearing? message) (if (:appearing? message)
@ -327,27 +337,14 @@
children)])})) children)])}))
(into [react/view] children))) (into [react/view] children)))
(defn chat-message [{:keys [outgoing message-id chat-id from current-public-key] :as message}] (defn chat-message [{:keys [outgoing group-chat current-public-key content-type content] :as message}]
(reagent/create-class [message-container message
{:display-name [react/touchable-highlight {:on-press #(when platform/ios?
"chat-message" (react/dismiss-keyboard!))
:component-did-mount :on-long-press #(when (= content-type constants/text-content-type)
;; send `:seen` signal when we have signed-in user, message not from us and we didn't sent it already (list-selection/share content (i18n/label :t/message)))}
#(when (and current-public-key message-id chat-id (not outgoing) [react/view {:accessibility-label :chat-item}
(not (models.message/message-seen-by? message current-public-key))) (let [incoming-group (and group-chat (not outgoing))]
(re-frame/dispatch [:send-seen! {:chat-id chat-id [message-content message-body (merge message
:from from {:current-public-key current-public-key
:me current-public-key :incoming-group incoming-group})])]]])
:message-id message-id}]))
:reagent-render
(fn [{:keys [outgoing group-chat content-type content] :as message}]
[message-container message
[react/touchable-highlight {:on-press #(when platform/ios?
(react/dismiss-keyboard!))
:on-long-press #(when (= content-type constants/text-content-type)
(list-selection/share content (i18n/label :t/message)))}
[react/view {:accessibility-label :chat-item}
(let [incoming-group (and group-chat (not outgoing))]
[message-content message-body (merge message
{:current-public-key current-public-key
:incoming-group incoming-group})])]]])}))

View File

@ -75,7 +75,7 @@
(defview message-content-command-request (defview message-content-command-request
[{:keys [message-id content] :as message}] [{:keys [message-id content] :as message}]
(letsubs [command [:get-command (:content-command-ref content)] (letsubs [command [:get-command (:request-command-ref content)]
answered? [:is-request-answered? message-id] answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?]] status-initialized? [:get :status-module-initialized?]]
(let [{:keys [prefill prefill-bot-db prefillBotDb params preview] (let [{:keys [prefill prefill-bot-db prefillBotDb params preview]

View File

@ -8,16 +8,9 @@
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.data-store.local-storage :as local-storage]
[status-im.bots.events :as bots-events] [status-im.bots.events :as bots-events]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
;; COFX
(re-frame/reg-cofx
:get-local-storage-data
(fn [cofx]
(assoc cofx :get-local-storage-data local-storage/get-data)))
;; FX ;; FX
(re-frame/reg-fx (re-frame/reg-fx
::evaluate-jail-n ::evaluate-jail-n
@ -26,9 +19,9 @@
(status/parse-jail (status/parse-jail
jail-id jail-resource jail-id jail-resource
(fn [jail-response] (fn [jail-response]
(let [converted (types/json->clj jail-response)] (let [converted (types/json->clj jail-response)]
(re-frame/dispatch [::proceed-loading jail-id (if config/jsc-enabled? (re-frame/dispatch [::proceed-loading jail-id (if config/jsc-enabled?
(update converted :result types/json->clj) (update converted :result types/json->clj)
converted)]))))))) converted)])))))))
(re-frame/reg-fx (re-frame/reg-fx
@ -79,7 +72,7 @@
(reduce conj (reduce conj
(disj scopes-set scope) (disj scopes-set scope)
(map (partial conj (s/difference scope exclusive-match)) (map (partial conj (s/difference scope exclusive-match))
exclusive-match)) exclusive-match))
scopes-set))) scopes-set)))
scopes-set scopes-set
scopes-set)) scopes-set))
@ -138,7 +131,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
::evaluate-commands-in-jail ::evaluate-commands-in-jail
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] [re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [cofx [commands-resource whisper-identity]] (fn [cofx [commands-resource whisper-identity]]
(evaluate-commands-in-jail cofx commands-resource whisper-identity))) (evaluate-commands-in-jail cofx commands-resource whisper-identity)))

View File

@ -2,8 +2,8 @@
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.commands.events.loading :as loading-events] [status-im.commands.events.loading :as loading-events]
[status-im.data-store.accounts :as accounts]
[status-im.data-store.messages :as messages] [status-im.data-store.messages :as messages]
[status-im.data-store.accounts :as accounts]
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.types :as types] [status-im.utils.types :as types]
@ -69,14 +69,14 @@
[{:keys [chats]} {:keys [whisper-identity]}] [{:keys [chats]} {:keys [whisper-identity]}]
(if (get chats whisper-identity) (if (get chats whisper-identity)
(if (get-in chats [whisper-identity :debug?]) (if (get-in chats [whisper-identity :debug?])
(do (re-frame/dispatch [:remove-chat whisper-identity]) (do (re-frame/dispatch [:remove-chat-and-navigate-home whisper-identity])
(respond {:type :ok (respond {:type :ok
:text "The DApp or bot has been removed."})) :text "The DApp or bot has been removed."}))
(respond {:type :error (respond {:type :error
:text "Your DApp or bot should be debuggable."})) :text "Your DApp or bot should be debuggable."}))
(respond {:type :error (respond {:type :error
:text "There is no such DApp or bot."})) :text "There is no such DApp or bot."}))
(re-frame/dispatch [:remove-contact whisper-identity #(and (:dapp? %) (:debug? %))])) (re-frame/dispatch [:remove-contact whisper-identity]))
(defn contact-changed (defn contact-changed
[{:keys [webview-bridge current-chat-id] [{:keys [webview-bridge current-chat-id]
@ -177,7 +177,6 @@
;; TODO(janherich) once `contact-changed` fn is refactored, get rid of this unnecessary event ;; TODO(janherich) once `contact-changed` fn is refactored, get rid of this unnecessary event
(handlers/register-handler-fx (handlers/register-handler-fx
::load-commands ::load-commands
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] [re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [cofx [contact]] (fn [cofx [contact]]
(loading-events/load-commands cofx {} contact))) (loading-events/load-commands cofx {} contact)))

View File

@ -7,8 +7,7 @@
[status-im.commands.utils :refer [reg-handler]] [status-im.commands.utils :refer [reg-handler]]
[status-im.constants :refer [console-chat-id]] [status-im.constants :refer [console-chat-id]]
[status-im.i18n :refer [get-contact-translated]] [status-im.i18n :refer [get-contact-translated]]
[taoensso.timbre :as log] [taoensso.timbre :as log]))
[status-im.data-store.local-storage :as local-storage]))
(defn command-handler! (defn command-handler!
[_ [chat-id [_ [chat-id
@ -47,7 +46,7 @@
(defn suggestions-events-handler! (defn suggestions-events-handler!
[{:keys [bot-db]} [bot-id [n & data] val]] [{:keys [bot-db]} [bot-id [n & data] val]]
(log/debug "Suggestion event: " n (first data) val) (log/debug "Suggestion event: " n (first data) val)
(case (keyword n) (case (keyword n)
:set-command-argument :set-command-argument
(let [[index value move-to-next?] (first data)] (let [[index value move-to-next?] (first data)]
@ -102,8 +101,9 @@
{:result result {:result result
:chat-id chat-id}]))))) :chat-id chat-id}])))))
(reg-handler :set-local-storage (handlers/register-handler-fx
(handlers/side-effect! :set-local-storage
(fn [_ [{:keys [data chat-id]}]] [trim-v]
(local-storage/set-data {:chat-id chat-id (fn [_ [{:keys [data chat-id]}]]
:data data})))) {:data-store/set-local-storage-data {:chat-id chat-id
:data data}}))

View File

@ -15,6 +15,7 @@
(def content-type-command "command") (def content-type-command "command")
(def content-type-command-request "command-request") (def content-type-command-request "command-request")
(def content-type-status "status") (def content-type-status "status")
(def content-type-placeholder "placeholder")
(def min-password-length 6) (def min-password-length 6)
(def max-chat-name-length 20) (def max-chat-name-length 20)
@ -28,6 +29,8 @@
(def contract-address "0x0000000000000000000000000000000000000000") (def contract-address "0x0000000000000000000000000000000000000000")
(def system "system")
(def default-wallet-transactions (def default-wallet-transactions
{:filters {:filters
{:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true} {:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true}
@ -82,6 +85,7 @@
:DataDir "/ethereum/rinkeby_rpc" :DataDir "/ethereum/rinkeby_rpc"
:UpstreamConfig {:Enabled true :UpstreamConfig {:Enabled true
:URL "https://rinkeby.infura.io/z6GCTmjdP3FETEJmMBI4"}}}}) :URL "https://rinkeby.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
(def default-networks (def default-networks
(transform-config (transform-config
(merge testnet-networks (merge testnet-networks
@ -102,6 +106,9 @@
(def inbox-topic "0xaabb11ee") (def inbox-topic "0xaabb11ee")
(def inbox-password "status-offline-inbox") (def inbox-password "status-offline-inbox")
;; Used to generate topic for contact discoveries
(def contact-discovery "contact-discovery")
(def ^:const send-transaction-no-error-code "0") (def ^:const send-transaction-no-error-code "0")
(def ^:const send-transaction-default-error-code "1") (def ^:const send-transaction-default-error-code "1")
(def ^:const send-transaction-password-error-code "2") (def ^:const send-transaction-password-error-code "2")

View File

@ -1,11 +1,24 @@
(ns status-im.data-store.accounts (ns status-im.data-store.accounts
(:require [status-im.data-store.realm.accounts :as data-store])) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
(defn get-all [] [status-im.data-store.realm.core :as core]
(data-store/get-all-as-list)) [status-im.data-store.realm.accounts :as data-store]))
;; TODO janherich: define as cofx once debug handlers are refactored
(defn get-by-address [address] (defn get-by-address [address]
(data-store/get-by-address address)) (data-store/get-by-address address))
(defn save [account update?] (re-frame/reg-cofx
(data-store/save account update?)) :data-store/get-all-accounts
(fn [coeffects _]
(assoc coeffects :all-accounts (data-store/get-all-as-list))))
(re-frame/reg-fx
:data-store/save-account
(fn [{:keys [after-update-event] :as account}]
(let [account-to-save (dissoc account :after-update-event)]
(async/go (async/>! core/realm-queue #(if after-update-event
(do (data-store/save account-to-save true)
(re-frame/dispatch after-update-event))
(data-store/save account-to-save true)))))))

View File

@ -1,23 +1,21 @@
(ns status-im.data-store.browser (ns status-im.data-store.browser
(:require [status-im.data-store.realm.browser :as data-store]) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.browser :as data-store])
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn get-all (re-frame/reg-cofx
[] :data-store/all-browsers
(data-store/get-all)) (fn [cofx _]
(assoc cofx :all-stored-browsers (data-store/get-all))))
(defn get-by-id (re-frame/reg-fx
[id] :data-store/save-browser
(data-store/get-by-id id)) (fn [{:keys [browser-id] :as browser}]
(async/go (async/>! core/realm-queue #(data-store/save browser (data-store/exists? browser-id))))))
(defn exists? (re-frame/reg-fx
[browser-id] :data-store/remove-browser
(data-store/exists? browser-id)) (fn [browser-id]
(async/go (async/>! core/realm-queue #(data-store/delete browser-id)))))
(defn save
[{:keys [browser-id] :as browser}]
(data-store/save browser (exists? browser-id)))
(defn delete
[browser-id]
(data-store/delete browser-id))

View File

@ -1,63 +1,51 @@
(ns status-im.data-store.chats (ns status-im.data-store.chats
(:require [status-im.data-store.realm.chats :as data-store]) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.chats :as data-store])
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn get-all (re-frame/reg-cofx
[] :data-store/all-chats
(data-store/get-all-active)) (fn [cofx _]
(assoc cofx :all-stored-chats (data-store/get-all-active))))
(defn get-inactive-ids (re-frame/reg-cofx
[] :data-store/inactive-chat-ids
(data-store/get-inactive-ids)) (fn [cofx _]
(assoc cofx :inactive-chat-ids (data-store/get-inactive-ids))))
(defn get-by-id (re-frame/reg-cofx
[id] :data-store/get-chat
(data-store/get-by-id id)) (fn [cofx _]
(assoc cofx :get-stored-chat data-store/get-by-id)))
(defn exists? (re-frame/reg-fx
[chat-id] :data-store/save-chat
(data-store/exists? chat-id)) (fn [{:keys [chat-id] :as chat}]
(async/go (async/>! core/realm-queue #(data-store/save chat (data-store/exists? chat-id))))))
(defn save (re-frame/reg-fx
[{:keys [chat-id] :as chat}] :data-store/delete-chat
(data-store/save chat (data-store/exists? chat-id))) (fn [chat-id]
(async/go (async/>! core/realm-queue #(data-store/delete chat-id)))))
(defn delete (re-frame/reg-fx
[chat-id] :data-store/deactivate-chat
(data-store/delete chat-id)) (fn [chat-id]
(async/go (async/>! core/realm-queue #(data-store/set-inactive chat-id)))))
(defn set-inactive (re-frame/reg-fx
[chat-id] :data-store/add-chat-contacts
(data-store/set-inactive chat-id)) (fn [[chat-id contacts]]
(async/go (async/>! core/realm-queue #(data-store/add-contacts chat-id contacts)))))
(defn get-contacts (re-frame/reg-fx
[chat-id] :data-store/remove-chat-contacts
(data-store/get-contacts chat-id)) (fn [[chat-id contacts]]
(async/go (async/>! core/realm-queue #(data-store/remove-contacts chat-id contacts)))))
(defn add-contacts (re-frame/reg-fx
[chat-id identities] :data-store/save-chat-property
(data-store/add-contacts chat-id identities)) (fn [[chat-id prop value]]
(async/go (async/>! core/realm-queue #(data-store/save-property chat-id prop value)))))
(defn remove-contacts
[chat-id identities]
(data-store/remove-contacts chat-id identities))
(defn save-property
[chat-id property-name value]
(data-store/save-property chat-id property-name value))
(defn get-property
[chat-id property-name]
(data-store/get-property chat-id property-name))
(defn removed-at
[chat-id]
(get-property chat-id :removed-at))
(defn get-active-group-chats
[]
(data-store/get-active-group-chats))
(defn set-active
[chat-id active?]
(save-property chat-id :is-active active?))

View File

@ -1,32 +1,38 @@
(ns status-im.data-store.contact-groups (ns status-im.data-store.contact-groups
(:require [status-im.data-store.realm.contact-groups :as data-store]) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.contact-groups :as data-store])
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn- normalize-contacts (defn- normalize-contacts
[item] [item]
(update item :contacts vals)) (update item :contacts vals))
(defn get-all (re-frame/reg-cofx
[] :data-store/get-all-contact-groups
(map normalize-contacts (data-store/get-all-as-list))) (fn [cofx _]
(assoc cofx :all-contact-groups (into {}
(map (comp (juxt :group-id identity) normalize-contacts))
(data-store/get-all-as-list)))))
(defn save (re-frame/reg-fx
[{:keys [group-id] :as group}] :data-store/save-contact-group
(data-store/save group (data-store/exists? group-id))) (fn [{:keys [group-id] :as group}]
(async/go (async/>! core/realm-queue #(data-store/save group (data-store/exists? group-id))))))
(defn save-all (re-frame/reg-fx
[groups] :data-store/save-contact-groups
(mapv save groups)) (fn [groups]
(doseq [{:keys [group-id] :as group} groups]
(async/go (async/>! core/realm-queue #(data-store/save group (data-store/exists? group-id)))))))
(defn save-property (re-frame/reg-fx
[group-id property-name value] :data-store/save-contact-group-property
(data-store/save-property group-id property-name value)) (fn [[group-id property-name value]]
(async/go (async/>! core/realm-queue #(data-store/save-property group-id property-name value)))))
(defn delete
[group-id]
(data-store/delete group-id))
(defn add-contacts
[group-id identities]
(data-store/add-contacts group-id identities))
(re-frame/reg-fx
:data-store/add-contacts-to-contact-group
(fn [[group-id contacts]]
(async/go (async/>! core/realm-queue #(data-store/add-contacts group-id contacts)))))

View File

@ -1,16 +1,20 @@
(ns status-im.data-store.contacts (ns status-im.data-store.contacts
(:require [status-im.data-store.realm.contacts :as data-store]) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.contacts :as data-store])
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn get-all (re-frame/reg-cofx
[] :data-store/get-all-contacts
(data-store/get-all-as-list)) (fn [coeffects _]
(assoc coeffects :all-contacts (data-store/get-all-as-list))))
(defn get-by-id (defn- get-by-id
[whisper-identity] [whisper-identity]
(data-store/get-by-id-cljs whisper-identity)) (data-store/get-by-id-cljs whisper-identity))
(defn save (defn- save
[{:keys [whisper-identity pending?] :as contact}] [{:keys [whisper-identity pending?] :as contact}]
(let [{pending-db? :pending? (let [{pending-db? :pending?
:as contact-db} (get-by-id whisper-identity) :as contact-db} (get-by-id whisper-identity)
@ -21,13 +25,18 @@
(dissoc :command :response :subscriptions :jail-loaded-events))] (dissoc :command :response :subscriptions :jail-loaded-events))]
(data-store/save contact' (boolean contact-db)))) (data-store/save contact' (boolean contact-db))))
(defn save-all (re-frame/reg-fx
[contacts] :data-store/save-contact
(mapv save contacts)) (fn [contact]
(async/go (async/>! core/realm-queue #(save contact)))))
(defn delete [contact] (re-frame/reg-fx
(data-store/delete contact)) :data-store/save-contacts
(fn [contacts]
(doseq [contact contacts]
(async/go (async/>! core/realm-queue #(save contact))))))
(defn exists? (re-frame/reg-fx
[whisper-identity] :data-store/delete-contact
(data-store/exists? whisper-identity)) (fn [contact]
(async/go (async/>! core/realm-queue #(data-store/delete contact)))))

View File

@ -1,5 +1,14 @@
(ns status-im.data-store.core (ns status-im.data-store.core
(:require [status-im.data-store.realm.core :as data-source] (:require status-im.data-store.chats
status-im.data-store.messages
status-im.data-store.contacts
status-im.data-store.transport
status-im.data-store.browser
status-im.data-store.accounts
status-im.data-store.local-storage
status-im.data-store.contact-groups
status-im.data-store.requests
[status-im.data-store.realm.core :as data-source]
[status-im.utils.handlers :as handlers])) [status-im.utils.handlers :as handlers]))

View File

@ -10,7 +10,7 @@
;; also deletes the oldest queries if the number of discovers stored is ;; also deletes the oldest queries if the number of discovers stored is
;; above maximum-number-of-discoveries ;; above maximum-number-of-discoveries
(re-frame/reg-fx (re-frame/reg-fx
:data-store.discover/save-all :data-store/save-all-discoveries
(fn [[discovers maximum-number-of-discoveries]] (fn [[discovers maximum-number-of-discoveries]]
(data-store/save-all (mapv #(dissoc % :tags) discovers)) (data-store/save-all (mapv #(dissoc % :tags) discovers))
(data-store/delete :created-at :asc maximum-number-of-discoveries))) (data-store/delete :created-at :asc maximum-number-of-discoveries)))

View File

@ -1,9 +1,15 @@
(ns status-im.data-store.local-storage (ns status-im.data-store.local-storage
(:require [status-im.data-store.realm.local-storage :as data-store])) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.local-storage :as data-store]))
(re-frame/reg-cofx
:data-store/get-local-storage-data
(fn [cofx _]
(assoc cofx :get-local-storage-data (comp :data data-store/get-by-chat-id))))
(defn get-data [chat-id] (re-frame/reg-fx
(:data (data-store/get-by-chat-id chat-id))) :data-store/set-local-storage-data
(fn [data]
(defn set-data [local-storage] (async/go (async/>! core/realm-queue #(data-store/save data)))))
(data-store/save local-storage))

View File

@ -1,12 +1,21 @@
(ns status-im.data-store.messages (ns status-im.data-store.messages
(:refer-clojure :exclude [exists?])
(:require [cljs.reader :as reader] (:require [cljs.reader :as reader]
[cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.messages :as data-store] [status-im.data-store.realm.messages :as data-store]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.utils.core :as utils] [status-im.utils.core :as utils]
[status-im.utils.datetime :as datetime])) [status-im.utils.datetime :as datetime]))
;; TODO janherich: define as cofx once debug handlers are refactored
(defn get-log-messages
[chat-id]
(->> (data-store/get-by-chat-id chat-id 0 100)
(filter #(= (:content-type %) constants/content-type-log-message))
(map #(select-keys % [:content :timestamp]))))
(defn- command-type? (defn- command-type?
[type] [type]
(contains? (contains?
@ -17,9 +26,10 @@
{:outgoing false {:outgoing false
:to nil}) :to nil})
(defn get-by-id (re-frame/reg-cofx
[message-id] :data-store/get-message
(data-store/get-by-id message-id)) (fn [cofx _]
(assoc cofx :get-stored-message data-store/get-by-id)))
(defn get-by-chat-id (defn get-by-chat-id
([chat-id] ([chat-id]
@ -31,22 +41,25 @@
(update message :content reader/read-string) (update message :content reader/read-string)
message)))))) message))))))
(defn get-stored-message-ids (re-frame/reg-cofx
[] :data-store/get-messages
(data-store/get-stored-message-ids)) (fn [cofx _]
(assoc cofx :get-stored-messages get-by-chat-id)))
(defn get-log-messages (re-frame/reg-cofx
[chat-id] :data-store/message-ids
(->> (data-store/get-by-chat-id chat-id 0 100) (fn [cofx _]
(filter #(= (:content-type %) constants/content-type-log-message)) (assoc cofx :stored-message-ids (data-store/get-stored-message-ids))))
(map #(select-keys % [:content :timestamp]))))
(defn get-unviewed (re-frame/reg-cofx
[current-public-key] :data-store/unviewed-messages
(into {} (fn [{:keys [db] :as cofx} _]
(map (fn [[chat-id user-statuses]] (assoc cofx
[chat-id (into #{} (map :message-id) user-statuses)])) :stored-unviewed-messages
(group-by :chat-id (data-store/get-unviewed current-public-key)))) (into {}
(map (fn [[chat-id user-statuses]]
[chat-id (into #{} (map :message-id) user-statuses)]))
(group-by :chat-id (data-store/get-unviewed (:current-public-key db)))))))
(defn- prepare-content [content] (defn- prepare-content [content]
(if (string? content) (if (string? content)
@ -79,10 +92,35 @@
{:from (or from "anonymous") {:from (or from "anonymous")
:timestamp (datetime/timestamp)}))))) :timestamp (datetime/timestamp)})))))
(re-frame/reg-fx
:data-store/save-message
(fn [message]
(async/go (async/>! core/realm-queue #(save message)))))
(defn update-message (defn update-message
[{:keys [message-id] :as message}] [{:keys [message-id] :as message}]
(when-let [{:keys [chat-id]} (data-store/get-by-id message-id)] (when-let [{:keys [chat-id]} (data-store/get-by-id message-id)]
(data-store/save (prepare-message (assoc message :chat-id chat-id))))) (data-store/save (prepare-message (assoc message :chat-id chat-id)))))
(defn delete-by-chat-id [chat-id] (re-frame/reg-fx
(data-store/delete-by-chat-id chat-id)) :data-store/update-message
(fn [message]
(async/go (async/>! core/realm-queue #(update-message message)))))
(re-frame/reg-fx
:data-store/update-messages
(fn [messages]
(doseq [message messages]
(async/go (async/>! core/realm-queue #(update-message message))))))
(re-frame/reg-fx
:data-store/delete-messages
(fn [chat-id]
(async/go (async/>! core/realm-queue #(data-store/delete-by-chat-id chat-id)))))
(re-frame/reg-fx
:data-store/hide-messages
(fn [chat-id]
(async/go (async/>! core/realm-queue #(doseq [message-id (data-store/get-message-ids-by-chat-id chat-id)]
(data-store/save {:message-id message-id
:show? false}))))))

View File

@ -1,22 +0,0 @@
(ns status-im.data-store.networks
(:require [status-im.data-store.realm.networks :as data-store]))
(defn get-all
[]
(data-store/get-all-as-list))
(defn save
[{:keys [id] :as network}]
(data-store/save network (data-store/exists? id)))
(defn save-all
[networks]
(mapv save networks))
(defn save-property
[id property-name value]
(data-store/save-property id property-name value))
(defn delete
[id]
(data-store/delete id))

View File

@ -1,49 +0,0 @@
(ns status-im.data-store.pending-messages
(:require [status-im.data-store.realm.pending-messages :as data-store]
[status-im.utils.hex :as i]))
(defn- get-id
[message-id to]
(let [to' (i/normalize-hex to)
to'' (when to' (subs to' 0 7))
id' (if to''
(str message-id "-" (subs to'' 0 7))
message-id)]
id'))
(defn get-all
[]
(data-store/get-all-as-list))
(defn get-by-chat-id
[chat-id]
(data-store/get-by-chat-id chat-id))
(defn get-by-message-id
[message-id]
(data-store/get-by-message-id message-id))
(defn save
[{:keys [id to group-id message] :as pending-message}]
(let [{:keys [sig sym-key-password pubKey topic payload]} message
id' (get-id id to)
chat-id (or group-id to)
message' (-> pending-message
(assoc :id id'
:sig sig
:sym-key-password sym-key-password
:pub-key pubKey
:message-id id
:chat-id chat-id
:payload payload
:topic topic)
(dissoc :message))]
(data-store/save message')))
(defn delete
[message-id]
(data-store/delete message-id))
(defn delete-all-by-chat-id
[chat-id]
(data-store/delete-all-by-chat-id chat-id))

View File

@ -1,14 +0,0 @@
(ns status-im.data-store.processed-messages
(:require [status-im.data-store.realm.processed-messages :as data-store])
(:refer-clojure :exclude [exists?]))
(defn get-filtered
[condition]
(data-store/get-filtered-as-list condition))
(defn save
[processed-message]
(data-store/save processed-message))
(defn delete [condition]
(data-store/delete condition))

View File

@ -7,10 +7,12 @@
(:refer-clojure :exclude [exists?])) (:refer-clojure :exclude [exists?]))
(defn- normalize-chat [{:keys [chat-id] :as chat}] (defn- normalize-chat [{:keys [chat-id] :as chat}]
(let [last-message (messages/get-last-message chat-id)] (let [last-to-clock-value (messages/get-last-clock-value chat-id :to-clock-value)
last-from-clock-value (messages/get-last-clock-value chat-id :from-clock-value)]
(-> chat (-> chat
(realm/fix-map->vec :contacts) (realm/fix-map->vec :contacts)
(assoc :last-clock-value (or (:clock-value last-message) 0))))) (merge {:last-to-clock-value (or last-to-clock-value 0)
:last-from-clock-value (or last-from-clock-value 0)}))))
(defn get-all-active (defn get-all-active
[] []

View File

@ -4,6 +4,7 @@
[status-im.data-store.realm.schemas.base.core :as base] [status-im.data-store.realm.schemas.base.core :as base]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.utils.fs :as fs] [status-im.utils.fs :as fs]
[status-im.utils.async :as utils.async]
[clojure.string :as str] [clojure.string :as str]
[goog.string :as gstr] [goog.string :as gstr]
[cognitect.transit :as transit] [cognitect.transit :as transit]
@ -43,6 +44,8 @@
(def account-realm (atom (open-migrated-realm new-account-filename account/schemas))) (def account-realm (atom (open-migrated-realm new-account-filename account/schemas)))
(def realm-queue (utils.async/task-queue 2000))
(defn close-account-realm [] (defn close-account-realm []
(close @account-realm) (close @account-realm)
(reset! account-realm nil)) (reset! account-realm nil))

View File

@ -32,6 +32,12 @@
realm/js-object->clj)] realm/js-object->clj)]
(mapv transform-message messages)))) (mapv transform-message messages))))
(defn get-message-ids-by-chat-id
[chat-id]
(.map (realm/get-by-field @realm/account-realm :message :chat-id chat-id)
(fn [msg _ _]
(aget msg "message-id"))))
(defn get-stored-message-ids (defn get-stored-message-ids
[] []
(let [chat-id->message-id (volatile! {})] (let [chat-id->message-id (volatile! {})]
@ -52,11 +58,12 @@
(realm/page from (+ from number-of-messages)) (realm/page from (+ from number-of-messages))
realm/js-object->clj)) realm/js-object->clj))
(defn get-last-message (defn get-last-clock-value
[chat-id] [chat-id clock-prop]
(-> (realm/get-by-field @realm/account-realm :message :chat-id chat-id) (-> (realm/get-by-field @realm/account-realm :message :chat-id chat-id)
(realm/sorted :clock-value :desc) (realm/sorted clock-prop :desc)
(realm/single-clj))) (realm/single-clj)
(get clock-prop)))
(defn get-unviewed (defn get-unviewed
[current-public-key] [current-public-key]

View File

@ -1,33 +0,0 @@
(ns status-im.data-store.realm.networks
(:require [status-im.data-store.realm.core :as realm])
(:refer-clojure :exclude [exists?]))
(defn get-all
[]
(-> @realm/account-realm
(realm/get-all :network)))
(defn get-all-as-list
[]
(realm/js-object->clj (get-all)))
(defn save
[network update?]
(realm/save @realm/account-realm :network network update?))
(defn save-property
[id property-name value]
(realm/write @realm/account-realm
(fn []
(-> @realm/account-realm
(realm/get-one-by-field :network :id id)
(aset (name property-name) value)))))
(defn exists?
[id]
(realm/exists? @realm/account-realm :network {:id id}))
(defn delete
[id]
(when-let [network (realm/get-one-by-field @realm/account-realm :network :id id)]
(realm/delete @realm/account-realm network)))

View File

@ -1,31 +0,0 @@
(ns status-im.data-store.realm.pending-messages
(:require [status-im.data-store.realm.core :as realm]
[cljs.reader :refer [read-string]]))
(defn get-all
[]
(realm/get-all @realm/account-realm :pending-message))
(defn get-all-as-list
[]
(realm/js-object->clj (get-all)))
(defn get-by-message-id
[message-id]
(realm/get-by-field @realm/account-realm :pending-message :message-id message-id))
(defn get-by-chat-id
[chat-id]
(realm/get-by-field @realm/account-realm :pending-message :chat-id chat-id))
(defn save
[pending-message]
(realm/save @realm/account-realm :pending-message pending-message true))
(defn delete
[message-id]
(realm/delete @realm/account-realm (get-by-message-id message-id)))
(defn delete-all-by-chat-id
[chat-id]
(realm/delete @realm/account-realm (get-by-chat-id chat-id)))

View File

@ -1,25 +0,0 @@
(ns status-im.data-store.realm.processed-messages
(:require [status-im.data-store.realm.core :as realm])
(:refer-clojure :exclude [exists?]))
(defn get-all
[]
(-> @realm/account-realm
(realm/get-all :processed-message)
(realm/sorted :ttl :asc)))
(defn get-filtered
[condition]
(realm/filtered (get-all) condition))
(defn get-filtered-as-list
[condition]
(realm/js-object->clj (get-filtered condition)))
(defn save
[processed-message]
(realm/save @realm/account-realm :processed-message processed-message))
(defn delete
[condition]
(realm/delete @realm/account-realm (get-filtered condition)))

View File

@ -20,7 +20,8 @@
[status-im.data-store.realm.schemas.account.v19.core :as v19] [status-im.data-store.realm.schemas.account.v19.core :as v19]
[status-im.data-store.realm.schemas.account.v20.core :as v20] [status-im.data-store.realm.schemas.account.v20.core :as v20]
[status-im.data-store.realm.schemas.account.v21.core :as v21] [status-im.data-store.realm.schemas.account.v21.core :as v21]
[status-im.data-store.realm.schemas.account.v22.core :as v22])) [status-im.data-store.realm.schemas.account.v22.core :as v22]
[status-im.data-store.realm.schemas.account.v23.core :as v23]))
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas. ;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
@ -91,5 +92,7 @@
:migration v21/migration} :migration v21/migration}
{:schema v22/schema {:schema v22/schema
:schemaVersion 22 :schemaVersion 22
:migration v22/migration}]) :migration v22/migration}
{:schema v23/schema
:schemaVersion 23
:migration v23/migration}])

View File

@ -1,5 +1,6 @@
(ns status-im.data-store.realm.schemas.account.v22.core (ns status-im.data-store.realm.schemas.account.v22.core
(:require [status-im.data-store.realm.schemas.account.v22.chat :as chat] (:require [status-im.data-store.realm.schemas.account.v22.chat :as chat]
[status-im.data-store.realm.schemas.account.v22.transport :as transport]
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact] [status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v19.contact :as contact] [status-im.data-store.realm.schemas.account.v19.contact :as contact]
[status-im.data-store.realm.schemas.account.v20.discover :as discover] [status-im.data-store.realm.schemas.account.v20.discover :as discover]
@ -18,6 +19,7 @@
(def schema [chat/schema (def schema [chat/schema
chat-contact/schema chat-contact/schema
transport/schema
contact/schema contact/schema
discover/schema discover/schema
message/schema message/schema

View File

@ -0,0 +1,15 @@
(ns status-im.data-store.realm.schemas.account.v22.transport)
(def schema {:name :transport
:primaryKey :chat-id
:properties {:chat-id :string
:ack :string
:seen :string
:pending-ack :string
:pending-send :string
:topic :string
:sym-key-id {:type :string
:optional true}
;;TODO (yenda) remove once go implements persistence
:sym-key {:type :string
:optional true}}})

View File

@ -0,0 +1,62 @@
(ns status-im.data-store.realm.schemas.account.v23.core
(:require [status-im.data-store.realm.schemas.account.v22.chat :as chat]
[status-im.data-store.realm.schemas.account.v22.transport :as transport]
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v19.contact :as contact]
[status-im.data-store.realm.schemas.account.v20.discover :as discover]
[status-im.data-store.realm.schemas.account.v23.message :as message]
[status-im.data-store.realm.schemas.account.v19.request :as request]
[status-im.data-store.realm.schemas.account.v19.user-status :as user-status]
[status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group]
[status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact]
[status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage]
[status-im.data-store.realm.schemas.account.v21.browser :as browser]
[goog.object :as object]
[taoensso.timbre :as log]
[cljs.reader :as reader]
[clojure.string :as string]))
(def schema [chat/schema
chat-contact/schema
transport/schema
contact/schema
discover/schema
message/schema
request/schema
user-status/schema
contact-group/schema
group-contact/schema
local-storage/schema
browser/schema])
(defn update-new-message [new-realm message-id to-clock-value from-clock-value]
(when-let [message (some-> new-realm
(.objects "message")
(.filtered (str "message-id = \"" message-id "\""))
(aget 0))]
(aset message "to-clock-value" to-clock-value)
(aset message "from-clock-value" from-clock-value)))
(defn update-chat-messages [old-realm new-realm chat-id]
(let [from-clock-value (atom 0)
to-clock-value (atom 0)]
(some-> old-realm
(.objects "message")
(.filtered (str "chat-id = \"" chat-id "\""))
(.sorted "clock-value" false)
(.map (fn [message _ _]
(let [message-id (object/get message "message-id")
outgoing? (boolean (object/get message "outgoing"))]
(if outgoing?
(update-new-message new-realm message-id (swap! to-clock-value inc) @from-clock-value)
(update-new-message new-realm message-id @to-clock-value (swap! from-clock-value inc)))))))))
(defn update-chats [old-realm new-realm]
(some-> new-realm
(.objects "chat")
(.map (fn [chat _ _]
(update-chat-messages old-realm new-realm (object/get chat "chat-id"))))))
(defn migration [old-realm new-realm]
(log/debug "migrating v23 account database: " old-realm new-realm)
(update-chats old-realm new-realm))

View File

@ -0,0 +1,30 @@
(ns status-im.data-store.realm.schemas.account.v23.message)
(def schema {:name :message
:primaryKey :message-id
:properties {:message-id :string
:from :string
:to {:type :string
:optional true}
:content :string ; TODO make it ArrayBuffer
:content-type :string
:username {:type :string
:optional true}
:timestamp :int
:chat-id {:type :string
:indexed true}
:outgoing :bool
:retry-count {:type :int
:default 0}
:message-type {:type :string
:optional true}
:message-status {:type :string
:optional true}
:user-statuses {:type :list
:objectType :user-status}
:from-clock-value {:type :int
:default 0}
:to-clock-value {:type :int
:default 0}
:show? {:type :bool
:default true}}})

View File

@ -0,0 +1,21 @@
(ns status-im.data-store.realm.transport
(:require [status-im.data-store.realm.core :as realm])
(:refer-clojure :exclude [exists?]))
(defn get-all
[]
(-> (realm/get-all @realm/account-realm :transport)
realm/js-object->clj))
(defn exists?
[chat-id]
(realm/exists? @realm/account-realm :transport {:chat-id chat-id}))
(defn save
[{:keys [chat-id] :as chat}]
(realm/save @realm/account-realm :transport chat (exists? chat-id)))
(defn delete
[chat-id]
(when-let [chat (realm/get-by-field @realm/account-realm :transport :chat-id chat-id)]
(realm/delete @realm/account-realm chat)))

View File

@ -1,14 +1,20 @@
(ns status-im.data-store.requests (ns status-im.data-store.requests
(:require [status-im.data-store.realm.requests :as data-store])) (:require [cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]
[status-im.data-store.realm.requests :as data-store]))
(defn get-all-unanswered (re-frame/reg-cofx
[] :data-store/get-unanswered-requests
(data-store/get-all-unanswered)) (fn [cofx _]
(assoc cofx :stored-unanswered-requests (data-store/get-all-unanswered))))
(defn save (re-frame/reg-fx
[request] :data-store/save-request
(data-store/save request)) (fn [request]
(async/go (async/>! core/realm-queue #(data-store/save request)))))
(defn mark-as-answered (re-frame/reg-fx
[chat-id message-id] :data-store/mark-request-as-answered
(data-store/mark-as-answered chat-id message-id)) (fn [{:keys [chat-id message-id]}]
(async/go (async/>! core/realm-queue #(data-store/mark-as-answered chat-id message-id)))))

View File

@ -0,0 +1,44 @@
(ns status-im.data-store.transport
(:require [cljs.tools.reader.edn :as edn]
[cljs.core.async :as async]
[re-frame.core :as re-frame]
[status-im.data-store.realm.transport :as data-store]
[status-im.data-store.realm.core :as core]))
(defn deserialize-chat [serialized-chat]
(-> serialized-chat
(dissoc :chat-id)
(update :ack edn/read-string)
(update :seen edn/read-string)
(update :pending-ack edn/read-string)
(update :pending-send edn/read-string)))
(re-frame/reg-cofx
:data-store/transport
(fn [cofx _]
(assoc cofx
:data-store/transport
(reduce (fn [acc {:keys [chat-id] :as chat}]
(assoc acc chat-id (deserialize-chat chat)))
{}
(data-store/get-all)))))
(defn save [chat-id chat]
(let [serialized-chat (-> chat
(assoc :chat-id chat-id)
(update :ack pr-str)
(update :seen pr-str)
(update :pending-ack pr-str)
(update :pending-send pr-str))]
(data-store/save serialized-chat)))
(re-frame/reg-fx
:data-store.transport/save
(fn [{:keys [chat-id chat]}]
(async/go (async/>! core/realm-queue #(save chat-id chat)))))
(re-frame/reg-fx
:data-store.transport/delete
(fn [chat-id]
(async/go (async/>! core/realm-queue #(data-store/delete chat-id)))))

View File

@ -1,24 +0,0 @@
(ns status-im.protocol.ack
(:require [status-im.protocol.web3.delivery :as d]
[status-im.protocol.web3.filtering :as f]
[status-im.utils.random :as random]))
(defn check-ack!
[web3
from
{:keys [type requires-ack? message-id ack? group-id ack-of-message]}
identity]
(when (and requires-ack? (not ack?))
(let [message {:from identity
:to from
:message-id (random/id)
:topics [f/status-topic]
:type type
:ack? true
:payload {:type type
:ack? true
:ack-of-message message-id
:group-id group-id}}]
(d/add-pending-message! web3 message)))
(when ack?
(d/remove-pending-message! web3 ack-of-message from)))

View File

@ -1,43 +0,0 @@
(ns status-im.protocol.chat
(:require [cljs.spec.alpha :as s]
[status-im.protocol.web3.filtering :as f]
[status-im.protocol.web3.delivery :as d]
[taoensso.timbre :refer-macros [debug] :as log]
[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 [topics (f/get-topics (:to message))
message' (assoc message
:topics topics
: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)))))

View File

@ -1,125 +0,0 @@
(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]
[status-im.protocol.web3.inbox :as inbox]
[taoensso.timbre :refer-macros [debug] :as log]
[status-im.protocol.validation :refer-macros [valid?]]
[status-im.protocol.web3.keys :as shh-keys]
[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.alpha :as s]
[status-im.utils.config :as config]
[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 send-public-group-message! group/send-to-public-group!)
(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 stop-watching-user! discoveries/stop-watching-user!)
(def contact-request! discoveries/contact-request!)
(def broadcast-profile! discoveries/broadcast-profile!)
(def send-status! discoveries/send-status!)
(def send-discoveries-request! discoveries/send-discoveries-request!)
(def send-discoveries-response! discoveries/send-discoveries-response!)
(def update-keys! discoveries/update-keys!)
(def message-pending? d/message-pending?)
;; initialization
(s/def ::identity string?)
(s/def :message/chat-id string?)
(s/def ::public? (s/and boolean? true?))
(s/def ::group-id :message/chat-id)
(s/def ::group (s/or
:group (s/keys :req-un [::group-id :message/keypair])
:public-group (s/keys :req-un [::group-id ::public?])))
(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 [::identity ::groups ::profile-keypair
::callback :discoveries/hashtags ::contacts])
::d/delivery-options))
(def stop-watching-all! f/remove-all-filters!)
(def reset-all-pending-messages! d/reset-all-pending-messages!)
(def reset-keys! shh-keys/reset-keys!)
(defn stop-whisper! []
(stop-watching-all!)
(reset-all-pending-messages!)
(reset-keys!))
(defn init-whisper!
[{:keys [identity groups callback web3
contacts profile-keypair pending-messages]
:as options}]
{:pre [(valid? ::options options)]}
(debug :init-whisper)
(stop-whisper!)
(let [listener-options {:web3 web3
:identity identity
:callback callback}]
;; start listening to groups
(doseq [group groups]
(let [options (merge listener-options group)]
(group/start-watching-group! options)))
;; start listening to user's inbox
(if config/offline-inbox-enabled?
(do (log/info "offline inbox: flag enabled")
(f/add-filter!
web3
{:key identity
:allowP2P true
:topics (f/get-topics identity)}
(l/message-listener listener-options))
(inbox/initialize! web3))
(f/add-filter!
web3
{:key identity
:topics (f/get-topics identity)}
(l/message-listener listener-options)))
;; 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-prepared-pending-message! web3 pending-message))))

View File

@ -1,182 +0,0 @@
(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.alpha :as s]
[status-im.protocol.validation :refer-macros [valid?]]
[status-im.utils.random :as random]
[status-im.protocol.web3.keys :as shh-keys]
[status-im.utils.datetime :as datetime]))
(def discover-topic-prefix "status-discover-")
(def discover-topic "0xbeefdead")
(defn- make-discover-topic [identity]
(str discover-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]))
(def discovery-key-password "status-discovery")
(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
:key-password discovery-key-password
:payload {:content {:timestamp (datetime/timestamp)}}
:topics [f/status-topic]})]
(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)]}
(shh-keys/get-sym-key
web3
discovery-key-password
(fn [key-id]
(f/add-filter!
web3
{:sig identity
:topics [f/status-topic]
:key key-id
:type :sym}
(l/message-listener (dissoc options :identity))))))
(defn stop-watching-user!
[{:keys [web3 identity]}]
(shh-keys/get-sym-key
web3
discovery-key-password
(fn [key-id]
(f/remove-filter!
web3
{:sig identity
:topics [f/status-topic]
:key key-id
:type :sym}))))
(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)]}
(debug :send-command-request!)
(d/add-pending-message!
web3
(assoc message
:type :contact-request
:requires-ack? true
:topics [f/status-topic])))
(s/def :discoveries/hashtags (s/every string? :kind-of set?))
(s/def ::callback fn?)
(s/def :watch-hashtags/options
(s/keys :req-un [:options/web3 :discoveries/hashtags ::callback]))
(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 broadcast-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 [f/status-topic]
:key-password discovery-key-password)
(assoc-in [:payload :timestamp] (datetime/timestamp))
(assoc-in [:payload :content :profile]
(get-in message [:payload :profile]))
(update :payload dissoc :profile))))
(s/def ::public string?)
(s/def ::private string?)
(s/def ::keypair (s/keys :req-un [::public ::private]))
(s/def :update-keys/payload
(s/keys :req-un [::keypair]))
(s/def :update-keys/message
(s/merge :protocol/message (s/keys :req-un [:update-keys/payload])))
(s/def :update-keys/options
(s/keys :req-un [:update-keys/message :options/web3]))
(defn update-keys!
[{:keys [web3 message] :as options}]
{:pre [(valid? :update-keys/options options)]}
(let [message (-> message
(assoc :type :update-keys
:requires-ack? false
:key-password discovery-key-password
:topics [f/status-topic])
(assoc-in [:payload :timestamp] (datetime/timestamp)))]
(d/add-pending-message! web3 message)))
(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 send-status!
[{:keys [web3 message]}]
(debug :broadcasting-status)
(let [message (assoc message :type :discover
:key-password discovery-key-password
:topics [f/status-topic])]
(d/add-pending-message! web3 message)))
(defn send-discoveries-request!
[{:keys [web3 message]}]
(debug :sending-discoveries-request)
(d/add-pending-message!
web3
(assoc message :type :discoveries-request
:key-password discovery-key-password
:topics [f/status-topic])))
(defn send-discoveries-response!
[{:keys [web3 discoveries message]}]
(debug :sending-discoveries-response)
(doseq [portion (->> discoveries
(take 100)
(partition 10 10 nil))]
(d/add-pending-message!
web3
(assoc message :type :discoveries-response
:key-password discovery-key-password
:topics [f/status-topic]
:message-id (random/id)
:payload {:data (into [] portion)}))))

View File

@ -1,21 +0,0 @@
(ns status-im.protocol.encryption
(:require [status-im.js-dependencies :as dependencies]))
(def default-curve 384)
(defn new-keypair!
"Returns {:private \"private key\" :public \"public key\""
[]
(let [{:keys [enc dec]}
(-> dependencies/eccjs
(.generate (.-ENC_DEC dependencies/eccjs) default-curve)
(js->clj :keywordize-keys true))]
{:private dec
:public enc}))
(defn encrypt [public-key content]
(.encrypt dependencies/eccjs public-key content))
(defn decrypt [private-key content]
(.decrypt dependencies/eccjs private-key content))

View File

@ -1,153 +0,0 @@
(ns status-im.protocol.group
(:require
[status-im.protocol.web3.delivery :as d]
[status-im.protocol.web3.utils :as u]
[status-im.utils.config :as config]
[cljs.spec.alpha :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]
[clojure.string :as str]
[status-im.protocol.web3.keys :as shh-keys]
[status-im.utils.datetime :as datetime]))
(defn prepare-mesage
[{:keys [message group-id keypair new-keypair type username requires-ack?]}]
(let [message' (-> message
(update :payload assoc
:username username
:group-id group-id
:type type
:timestamp (datetime/timestamp))
(assoc :topics [f/status-topic]
:key-password group-id
:requires-ack? (or (nil? requires-ack?) requires-ack?)
:type type))]
(cond-> message'
keypair (assoc :keypair keypair)
new-keypair (assoc :new-keypair keypair))))
(defn- send-group-message!
[{:keys [web3 group-id] :as opts} type]
(let [message (-> opts
(assoc :type type
:key-password group-id)
(prepare-mesage))]
(debug :send-group-message message)
(d/add-pending-message! web3 message)))
(s/def ::message
(s/merge :protocol/message (s/keys :req-un [:chat-message/payload])))
(s/def :public-group/username (s/and string? (complement str/blank?)))
(s/def :public-group/message
(s/merge ::message (s/keys :username :public-group/username)))
(defn send!
[{:keys [keypair message] :as options}]
{:pre [(valid? :message/keypair keypair)
(valid? ::message message)]}
(send-group-message! options :group-message))
(defn send-to-public-group!
[{:keys [message] :as options}]
{:pre [(valid? :public-group/message message)]}
(send-group-message! (assoc options :requires-ack? false)
:public-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 ::identities (s/* string?))
(s/def ::name string?)
(s/def ::id string?)
(s/def ::admin string?)
(s/def ::contacts (s/* string?))
(s/def ::group
(s/keys :req-un
[::name ::id ::contacts :message/keypair ::admin]))
(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 (datetime/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)]}
(shh-keys/get-sym-key
web3
group-id
(fn [key-id]
(f/remove-filter!
web3
{:topics [f/status-topic]
:key key-id
:type :sym}))))
(defn start-watching-group!
[{:keys [web3 group-id keypair callback identity]}]
(shh-keys/get-sym-key
web3
group-id
(fn [key-id]
(f/add-filter!
web3
(if (and config/offline-inbox-enabled?
config/offline-inbox-many-enabled?)
{:topics [f/status-topic]
:key key-id
:allowP2P true
:type :sym}
{:topics [f/status-topic]
:key key-id
:type :sym})
(l/message-listener {:web3 web3
:identity identity
:callback callback
:keypair keypair})))))

View File

@ -1,93 +1,21 @@
(ns status-im.protocol.handlers (ns status-im.protocol.handlers
(:require [re-frame.core :as re-frame] (:require [cljs.core.async :as async]
[cljs.core.async :as async] [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.data-store.contacts :as contacts]
[status-im.data-store.messages :as messages]
[status-im.data-store.pending-messages :as pending-messages]
[status-im.data-store.processed-messages :as processed-messages]
[status-im.data-store.chats :as chats]
[status-im.protocol.core :as protocol]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.utils.random :as random]
[status-im.utils.async :as async-utils]
[status-im.protocol.message-cache :as cache]
[status-im.protocol.listeners :as listeners]
[status-im.chat.models.message :as models.message]
[status-im.chat.models :as chat]
[status-im.protocol.web3.inbox :as inbox]
[status-im.protocol.web3.keys :as web3.keys]
[status-im.utils.datetime :as datetime]
[taoensso.timbre :as log]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[clojure.string :as string] [status-im.transport.message-cache :as message-cache]
[status-im.utils.web3-provider :as web3-provider] [status-im.utils.datetime :as datetime]
[status-im.utils.ethereum.core :as utils] [status-im.utils.ethereum.core :as utils]
[status-im.utils.config :as config])) [status-im.utils.handlers :as handlers]
[status-im.utils.web3-provider :as web3-provider]
[status-im.transport.core :as transport]))
;;;; COFX ;;;; COFX
(re-frame/reg-cofx (re-frame/reg-cofx
::get-web3 ::get-web3
(fn [coeffects _] (fn [coeffects _]
(assoc coeffects :web3 (web3-provider/make-web3)))) (assoc coeffects :web3 (web3-provider/make-web3))))
(re-frame/reg-cofx
::get-chat-groups
(fn [coeffects _]
(assoc coeffects :groups (chats/get-active-group-chats))))
(re-frame/reg-cofx
::get-pending-messages
(fn [coeffects _]
(assoc coeffects :pending-messages (pending-messages/get-all))))
(re-frame/reg-cofx
::message-get-by-id
(fn [coeffects _]
(let [[{{:keys [message-id]} :payload}] (:event coeffects)]
(assoc coeffects :message-by-id (messages/get-by-id message-id)))))
;;;; FX
(def ^:private protocol-realm-queue (async-utils/task-queue 2000))
(re-frame/reg-fx
:stop-whisper
(fn [] (protocol/stop-whisper!)))
(re-frame/reg-fx
::init-whisper
(fn [{:keys [web3 public-key groups updates-public-key updates-private-key status contacts pending-messages]}]
(protocol/init-whisper!
{:web3 web3
:identity public-key
:groups groups
:callback #(re-frame/dispatch [:incoming-message %1 %2])
:ack-not-received-s-interval 125
:default-ttl 120
:send-online-s-interval 180
:ttl-config {:public-group-message 2400}
:max-attempts-number 3
:delivery-loop-ms-interval 500
:profile-keypair {:public updates-public-key
:private updates-private-key}
:hashtags (mapv name (handlers/get-hashtags status))
:pending-messages 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)
:post-error-callback #(re-frame/dispatch [::post-error %])
:pow-target config/pow-target
:pow-time config/pow-time})))
(re-frame/reg-fx (re-frame/reg-fx
::web3-get-syncing ::web3-get-syncing
(fn [web3] (fn [web3]
@ -97,314 +25,24 @@
(fn [error sync] (fn [error sync]
(re-frame/dispatch [:update-sync-state error sync])))))) (re-frame/dispatch [:update-sync-state error sync]))))))
(re-frame/reg-fx
::save-processed-messages
(fn [processed-message]
(async/go (async/>! protocol-realm-queue #(processed-messages/save processed-message)))))
(defn system-message [message-id timestamp content]
{:from "system"
:message-id message-id
:timestamp timestamp
:content content
:content-type constants/text-content-type})
(re-frame/reg-fx
::participant-removed-from-group-message
(fn [{:keys [identity from message-id timestamp group-id contacts]}]
(let [remover-name (get-in contacts [from :name])
removed-name (get-in contacts [identity :name])
message (->> [(or remover-name from) (i18n/label :t/removed) (or removed-name identity)]
(string/join " ")
(system-message message-id timestamp))
message' (assoc message :group-id group-id)]
(re-frame/dispatch [:chat-received-message/add message']))))
(re-frame/reg-fx
::chats-add-contact
(fn [[group-id identity]]
(chats/add-contacts group-id [identity])))
(re-frame/reg-fx
::chats-remove-contact
(fn [[group-id identity]]
(chats/remove-contacts group-id [identity])))
(re-frame/reg-fx
::you-removed-from-group-message
(fn [{:keys [from message-id timestamp group-id contacts]}]
(let [remover-name (get-in contacts [from :name])
message (->> [(or remover-name from) (i18n/label :t/removed-from-chat)]
(string/join " ")
(system-message message-id timestamp))
message' (assoc message :group-id group-id)]
(re-frame/dispatch [:chat-received-message/add message']))))
(re-frame/reg-fx
::stop-watching-group!
(fn [params]
(protocol/stop-watching-group! params)))
(re-frame/reg-fx
::participant-left-group-message
(fn [{:keys [chat-id from message-id timestamp contacts]}]
(let [left-name (get-in contacts [from :name])
message-text (str (or left-name from) " " (i18n/label :t/left))]
(-> (system-message message-id timestamp message-text)
(assoc :chat-id chat-id)
(messages/save)))))
(re-frame/reg-fx
::participant-invited-to-group-message
(fn [{:keys [group-id current-identity identity from message-id timestamp contacts]}]
(let [inviter-name (get-in contacts [from :name])
invitee-name (if (= identity current-identity)
(i18n/label :t/You)
(get-in contacts [identity :name]))]
(re-frame/dispatch
[:chat-received-message/add
{:from "system"
:group-id group-id
:timestamp timestamp
:message-id message-id
:content (str (or inviter-name from) " " (i18n/label :t/invited) " " (or invitee-name identity))
:content-type constants/text-content-type}]))))
(re-frame/reg-fx
::pending-messages-delete
(fn [message-id]
(async/go (async/>! protocol-realm-queue #(pending-messages/delete message-id)))))
(re-frame/reg-fx
::pending-messages-save
(fn [pending-message]
(async/go (async/>! protocol-realm-queue #(pending-messages/save pending-message)))))
(re-frame/reg-fx (re-frame/reg-fx
::status-init-jail ::status-init-jail
(fn [] (fn []
(status/init-jail))) (status/init-jail)))
(re-frame/reg-fx
::load-processed-messages!
(fn []
(let [now (datetime/timestamp)
messages (processed-messages/get-filtered (str "ttl > " now))]
(cache/init! messages)
(processed-messages/delete (str "ttl <=" now)))))
(re-frame/reg-fx
::add-peer
(fn [{:keys [wnode web3]}]
(inbox/add-peer wnode
#(re-frame/dispatch [::add-peer-success web3 %])
#(re-frame/dispatch [::add-peer-error %]))))
(re-frame/reg-fx
::fetch-peers
(fn [{:keys [wnode web3 retries]}]
;; Run immediately on first run, add delay before retry
(let [delay (cond
(zero? retries) 0
(< retries 3) 300
(< retries 10) 1000
:else 5000)]
(if (> retries 100)
(log/error "Number of retries for fetching peers exceed" wnode)
(js/setTimeout
(fn [] (inbox/fetch-peers #(re-frame/dispatch [::fetch-peers-success web3 % retries])
#(re-frame/dispatch [::fetch-peers-error %])))
delay)))))
(re-frame/reg-fx
::mark-trusted-peer
(fn [{:keys [wnode web3 peers]}]
(inbox/mark-trusted-peer web3
wnode
peers
#(re-frame/dispatch [::mark-trusted-peer-success web3 %])
#(re-frame/dispatch [::mark-trusted-peer-error %]))))
(re-frame/reg-fx
::get-sym-key
(fn [{:keys [web3 password]}]
(web3.keys/get-sym-key web3
password
#(re-frame/dispatch [::get-sym-key-success web3 %])
#(re-frame/dispatch [::get-sym-key-error %]))))
(re-frame/reg-fx
::request-messages
(fn [{:keys [wnode topic sym-key-id web3]}]
(inbox/request-messages web3
wnode
topic
sym-key-id
#(re-frame/dispatch [::request-messages-success %])
#(re-frame/dispatch [::request-messages-error %]))))
(re-frame/reg-fx
::handle-whisper-message
listeners/handle-whisper-message)
;;;; Handlers
(defn get-wnode [db]
(let [wnode-id (get db :inbox/wnode)]
(get-in db [:inbox/wnodes wnode-id :address])))
(defn connectivity-check [peers {:keys [db] :as cofx}]
(let [wnode (get-wnode db)
peers-count (count peers)
mailserver-connected? (inbox/registered-peer? peers wnode)]
{:db (cond-> db
mailserver-connected? (dissoc :mailserver-status)
(not mailserver-connected?) (assoc :mailserver-status :disconnected)
:always (assoc :peers-count peers-count))}))
(re-frame/reg-fx
:connectivity/fetch-peers
(fn [{:keys [wnode web3 retries]}]
(inbox/fetch-peers #(re-frame/dispatch [:connectivity-check-success %])
#(log/error :connectivity/fetch-peers %))))
(handlers/register-handler-fx
:connectivity-check
(fn [{:keys [db]} _]
(let [web3 (:web3 db)
wnode (get-wnode db)]
{:connectivity/fetch-peers {:wnode wnode
:web3 web3}})))
(handlers/register-handler-fx
:connectivity-check-success
(fn [{:keys [db] :as cofx} [_ peers]]
(handlers/merge-fx cofx
{:dispatch-later [{:ms 30000 :dispatch [:connectivity-check]}]}
(connectivity-check peers))))
;; NOTE(dmitryn): events chain
;; add-peer -> fetch-peers -> mark-trusted-peer -> get-sym-key -> request-messages
(handlers/register-handler-fx
:initialize-offline-inbox
(fn [{:keys [db]} [_ web3]]
(log/info "offline inbox: initialize")
(let [wnode (get-wnode db)]
{::add-peer {:wnode wnode
:web3 web3}})))
(handlers/register-handler-fx
::add-peer-success
(fn [{:keys [db]} [_ web3 response]]
(let [wnode (get-wnode db)]
(log/info "offline inbox: add-peer response" wnode response)
{::fetch-peers {:wnode wnode
:web3 web3
:retries 0}})))
(handlers/register-handler-fx
::fetch-peers-success
(fn [{:keys [db] :as cofx} [_ web3 peers retries]]
(let [wnode (get-wnode db)]
(log/info "offline inbox: fetch-peers response" peers)
(if (inbox/registered-peer? peers wnode)
(handlers/merge-fx cofx
{::mark-trusted-peer {:wnode wnode
:web3 web3
:peers peers}
:dispatch-later [{:ms 30000 :dispatch [:connectivity-check]}]}
(connectivity-check peers))
(do
(log/info "Peer" wnode "is not registered. Retrying fetch peers.")
(handlers/merge-fx cofx
{::fetch-peers {:wnode wnode
:web3 web3
:retries (inc retries)}}
(connectivity-check peers)))))))
(handlers/register-handler-fx
::mark-trusted-peer-success
(fn [{:keys [db]} [_ web3 response]]
(let [wnode (get-wnode db)
password (:inbox/password db)]
(log/info "offline inbox: mark-trusted-peer response" wnode response)
{::get-sym-key {:password password
:web3 web3}})))
(handlers/register-handler-fx
::get-sym-key-success
(fn [{:keys [db]} [_ web3 sym-key-id]]
(log/info "offline inbox: get-sym-key response" sym-key-id)
(let [wnode (get-wnode db)
topic (:inbox/topic db)]
{::request-messages {:wnode wnode
:topic topic
:sym-key-id sym-key-id
:web3 web3}})))
(handlers/register-handler-fx
::request-messages-success
(fn [_ [_ response]]
(log/info "offline inbox: request-messages response" response)))
(handlers/register-handler-fx
::add-peer-error
(fn [_ [_ error]]
(log/error "offline inbox: add-peer error" error)))
(handlers/register-handler-fx
::fetch-peers-error
(fn [_ [_ error]]
(log/error "offline inbox: fetch-peers error" error)))
(handlers/register-handler-fx
::mark-trusted-peer-error
(fn [_ [_ error]]
(log/error "offline inbox: mark-trusted-peer error" error)))
(handlers/register-handler-fx
::get-sym-key-error
(fn [_ [_ error]]
(log/error "offline inbox: get-sym-key error" error)))
(handlers/register-handler-fx
::request-messages-error
(fn [_ [_ error]]
(log/error "offline inbox: request-messages error" error)))
(handlers/register-handler-fx
:handle-whisper-message
(fn [_ [_ error msg options]]
{::handle-whisper-message {:error error
:msg msg
:options options}}))
;;; INITIALIZE PROTOCOL ;;; INITIALIZE PROTOCOL
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-protocol :initialize-protocol
[re-frame/trim-v [re-frame/trim-v
(re-frame/inject-cofx ::get-web3) (re-frame/inject-cofx ::get-web3)
(re-frame/inject-cofx ::get-chat-groups) (re-frame/inject-cofx :data-store/transport)]
(re-frame/inject-cofx ::get-pending-messages) (fn [{:data-store/keys [transport] :keys [db web3] :as cofx} [current-account-id ethereum-rpc-url]]
(re-frame/inject-cofx :get-all-contacts)] (handlers/merge-fx cofx
(fn [{:keys [db web3 groups all-contacts pending-messages]} [current-account-id ethereum-rpc-url]] {:db (assoc db
(let [{:keys [public-key status updates-public-key :web3 web3
updates-private-key]} :rpc-url (or ethereum-rpc-url constants/ethereum-rpc-url)
(get-in db [:accounts/accounts current-account-id])] :transport/chats transport)}
(when public-key (transport/init-whisper current-account-id))))
{::init-whisper {:web3 web3 :public-key public-key :groups groups :pending-messages pending-messages
:updates-public-key updates-public-key :updates-private-key updates-private-key
:status status :contacts all-contacts}
:db (assoc db :web3 web3
:rpc-url (or ethereum-rpc-url constants/ethereum-rpc-url))}))))
(handlers/register-handler-fx
:load-processed-messages
(fn [_ _]
{::load-processed-messages! nil}))
;;; NODE SYNC STATE ;;; NODE SYNC STATE
@ -433,216 +71,12 @@
:check-sync :check-sync
(fn [{{:keys [web3]} :db} _] (fn [{{:keys [web3]} :db} _]
{::web3-get-syncing web3 {::web3-get-syncing web3
:dispatch-later [{:ms 10000 :dispatch [:check-sync]}]})) :dispatch-later [{:ms 10000 :dispatch [:check-sync]}]}))
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-sync-listener :initialize-sync-listener
(fn [{{:keys [sync-listening-started network networks/networks] :as db} :db} _] (fn [{{:keys [sync-listening-started network networks/networks] :as db} :db} _]
(when (and (not sync-listening-started) (when (and (not sync-listening-started)
(not (utils/network-with-upstream-rpc? networks network))) (not (utils/network-with-upstream-rpc? networks network)))
{:db (assoc db :sync-listening-started true) {:db (assoc db :sync-listening-started true)
:dispatch [:check-sync]}))) :dispatch [:check-sync]})))
;;; MESSAGES
(defn- transform-protocol-message [{:keys [from to payload]}]
(merge payload {:from from
:to to
:chat-id (or (:group-id payload) from)}))
(defn- message-from-self [{:keys [current-public-key]} {:keys [id to group-id]}]
{:from to
:sent-from current-public-key
:payload {:message-id id
:group-id group-id}})
(defn- get-message-id [{:keys [message-id ack-of-message]}]
(or ack-of-message message-id))
(handlers/register-handler-fx
:incoming-message
(fn [{:keys [db]} [_ type {:keys [payload ttl id] :as message}]]
(let [message-id (or id (:message-id payload))]
(when-not (cache/exists? message-id type)
(let [ttl-s (* 1000 (or ttl 120))
processed-message {:id (random/id)
:message-id message-id
:type type
:ttl (+ (datetime/timestamp) ttl-s)}
chat-message (#{:message :group-message} (:type payload))
route-fx (case type
(:message
:group-message
:public-group-message) {:dispatch [:pre-received-message (transform-protocol-message message)]}
:pending (cond-> {::pending-messages-save message}
chat-message
(assoc :dispatch
[:update-message-status (message-from-self db message) :pending]))
:sent {:dispatch [:update-message-status (message-from-self db message) :sent]}
:ack (cond-> {::pending-messages-delete (get-message-id payload)}
chat-message
(assoc :dispatch [:update-message-status message :delivered]))
:seen {:dispatch [:update-message-status message :seen]}
:group-invitation {:dispatch [:group-chat-invite-received message]}
:update-group {:dispatch [:update-group-message message]}
:add-group-identity {:dispatch [:participant-invited-to-group message]}
:remove-group-identity {:dispatch [:participant-removed-from-group message]}
:leave-group {:dispatch [:participant-left-group message]}
:contact-request {:dispatch [:contact-request-received message]}
:discover {:dispatch [:status-received message]}
:discoveries-request {:dispatch [:discoveries-request-received message]}
:discoveries-response {:dispatch [:discoveries-response-received message]}
:profile {:dispatch [:contact-update-received message]}
:update-keys {:dispatch [:update-keys-received message]}
:online {:dispatch [:contact-online-received message]}
nil)]
(when (nil? route-fx) (log/debug "Unknown message type" type))
(cache/add! processed-message)
(merge
{::save-processed-messages processed-message}
route-fx))))))
(handlers/register-handler-fx
:update-message-status
[re-frame/trim-v (re-frame/inject-cofx :get-stored-message)]
(fn [{:keys [db get-stored-message]} [{:keys [from sent-from payload]} status]]
(let [message-identifier (get-message-id payload)
chat-identifier (or (:group-id payload) from)
message-db-path [:chats chat-identifier :messages message-identifier]
from-id (or sent-from from)
message (or (get-in db message-db-path)
(and (get (:not-loaded-message-ids db) message-identifier)
(get-stored-message message-identifier)))]
;; proceed with updating status if chat is in db, status is not the same and message was not already seen
(when (and message
(get-in db [:chats chat-identifier])
(not= status (get-in message [:user-statuses from-id]))
(not (models.message/message-seen-by? message from-id)))
(let [statuses (assoc (:user-statuses message) from-id status)]
(cond-> {:update-message {:message-id message-identifier
:user-statuses statuses}}
(get-in db message-db-path)
(assoc :db (assoc-in db (conj message-db-path :user-statuses) statuses))))))))
(handlers/register-handler-fx
:contact-request-received
(fn [{{:contacts/keys [contacts]} :db}
[_ {:keys [from payload timestamp]}]]
(when from
(let [{{:keys [name profile-image address status fcm-token]} :contact
{:keys [public private]} :keypair} payload
existing-contact (get contacts from)
contact {:whisper-identity from
:public-key public
:private-key private
:address address
:status status
:photo-path profile-image
:name name
:fcm-token fcm-token}
chat {:name name
:chat-id from
:contact-info (prn-str contact)}
prev-last-updated (get-in contacts [from :last-updated] 0)
;; NOTE(dmitryn) Workaround for old messages not having "payload.timestamp" attribute.
;; Get timestamp from message root level.
;; Root level "timestamp" is a unix ts in seconds.
timestamp' (or (:payload timestamp)
(* 1000 timestamp))]
(if-not existing-contact
(let [contact (assoc contact :pending? true)]
{:dispatch-n [[:add-contacts [contact]]
[:add-chat from chat]]})
(when-not (:pending? existing-contact)
(cond-> {:dispatch-n [[:update-chat! chat]
[:watch-contact contact]]}
(<= prev-last-updated timestamp') (update :dispatch-n concat [[:update-contact! contact]]))))))))
;;GROUP
(defn- has-contact? [{:keys [contacts]} identity]
(let [identities (set (map :identity contacts))]
(contains? identities identity)))
(handlers/register-handler-fx
:participant-invited-to-group
[re-frame/trim-v]
(fn [{{:keys [current-public-key chats contacts/contacts] :as db} :db}
[{:keys [from]
{:keys [group-id identity message-id timestamp]} :payload}]]
(let [chat (get-in db [:chats group-id])
admin (:group-admin chats)]
(when (= from admin)
(merge {::participant-invited-to-group-message {:group-id group-id :current-public-key current-public-key
:identity identity :from from :message-id message-id
:timestamp timestamp :contacts contacts}}
(when-not (and (= current-public-key identity) (has-contact? chat identity))
{:db (update-in db [:chats group-id :contacts] conj {:identity identity})
::chats-add-contact [group-id identity]}))))))
(handlers/register-handler-fx
::you-removed-from-group
[re-frame/trim-v]
(fn [{{:keys [web3 contacts/contacts] :as db} :db}
[{:keys [from]
{:keys [group-id timestamp message-id]} :payload}]]
(let [chat (get-in db [:chats group-id])
new-update? (chat/new-update? chat timestamp)]
(when new-update?
{::you-removed-from-group-message {:from from
:message-id message-id
:timestamp timestamp
:group-id group-id
:contacts contacts}
::stop-watching-group! {:web3 web3
:group-id group-id}
:dispatch [:update-chat! {:chat-id group-id
:removed-from-at timestamp
:is-active false}]}))))
(handlers/register-handler-fx
:participant-removed-from-group
[re-frame/trim-v
(re-frame/inject-cofx ::message-get-by-id)]
(fn [{{:keys [current-public-key chats contacts/contacts]} :db message-by-id :message-by-id}
[{:keys [from]
{:keys [group-id identity message-id timestamp]} :payload
:as message}]]
(when-not message-by-id
(let [admin (get-in chats [group-id :group-admin])]
(when (= admin from)
(if (= current-public-key identity)
{:dispatch [::you-removed-from-group message]}
{::participant-removed-from-group-message {:identity identity :from from :message-id message-id
:timestamp timestamp :group-id group-id :contacts contacts}
::chats-remove-contact [group-id identity]}))))))
(handlers/register-handler-fx
:participant-left-group
[re-frame/trim-v]
(fn [{{:keys [current-public-key contacts/contacts] :as db} :db}
[{:keys [from]
{:keys [group-id timestamp message-id]} :payload}]]
(let [chat (get-in db [:chats group-id])
{chat-is-active :is-active chat-timestamp :timestamp} chat]
(when (and (not= current-public-key from)
chat-is-active
(> timestamp chat-timestamp))
{::participant-left-group-message {:chat-id group-id
:from from
:message-id message-id
:timestamp timestamp
:contacts contacts}
::chats-remove-contact [group-id from]
:db (update-in db [:chats group-id :contacts]
#(remove (fn [{:keys [identity]}]
(= identity from)) %))}))))
;;ERROR
(handlers/register-handler-fx
::post-error
(fn [_ [_ error]]
(let [android-error? (re-find (re-pattern "Failed to connect") (.-message error))]
(when android-error?
{::status-init-jail nil}))))

View File

@ -1,100 +0,0 @@
(ns status-im.protocol.listeners
(:require [cljs.reader :as r]
[re-frame.core :as re-frame]
[status-im.protocol.ack :as ack]
[status-im.protocol.web3.utils :as u]
[status-im.protocol.encryption :as e]
[taoensso.timbre :as log]
[status-im.utils.hex :as i]))
(defn empty-public-key? [public-key]
(or (= "0x0" public-key)
(= "" public-key)
(nil? public-key)))
(defn create-error [step description]
(when (not= description :silent)
(log/debug step description))
{:error description})
(defn init-scope [js-error js-message options]
(if js-error
(create-error :init-scope-error (-> js-error js->clj str))
{:message (js->clj js-message :keywordize-keys true)
:options options}))
(defn parse-payload [{:keys [message error options] :as scope}]
(log/debug :parse-payload)
(if error
scope
(try
;; todo figure why we sometimes have to call to-utf8 twice and sometimes only once
(let [payload (:payload message)
payload' (u/to-utf8 payload)
payload'' (r/read-string payload')
payload''' (if (map? payload'')
payload''
(r/read-string (u/to-utf8 payload')))]
(if (map? payload''')
{:message (assoc message :payload payload''')
:options options}
(create-error :parse-payload-error (str "Invalid payload type " (type payload''')))))
(catch :default err
(create-error :parse-payload-error err)))))
(defn filter-messages-from-same-user [{:keys [message error options] :as scope}]
(if error
scope
(if (or (not= (i/normalize-hex (:identity options))
(i/normalize-hex (:sig message)))
;; allow user to receive his own discoveries
(= type :discover))
scope
(create-error :filter-messages-error :silent))))
(defn parse-content [{:keys [message error options] :as scope}]
(if error
scope
(try
(let [to (:recipientPublicKey message)
from (:sig message)
key (get-in options [:keypair :private])
raw-content (get-in message [:payload :content])
encrypted? (and (empty-public-key? to) key raw-content)
content (if encrypted?
(r/read-string (e/decrypt key raw-content))
raw-content)]
(log/debug :parse-content
"Key exists:" (not (nil? key))
"Content exists:" (not (nil? raw-content)))
{:message (-> message
(assoc-in [:payload :content] content)
(assoc :to to
:from from))
:options options})
(catch :default err
(create-error :parse-content-error err)))))
(defn handle-message [{:keys [message error options] :as scope}]
(if error
scope
(let [{:keys [web3 identity callback]} options
{:keys [payload sig]} message
ack? (get-in message [:payload :ack?])]
(log/debug :handle-message message)
(callback (if ack? :ack (:type payload)) message)
(ack/check-ack! web3 sig payload identity))))
(defn- handle-whisper-message [{:keys [error msg options]}]
(-> (init-scope error msg options)
parse-payload
filter-messages-from-same-user
parse-content
handle-message))
(defn message-listener
"Valid options are: web3, identity, callback, keypair"
[options]
(fn [js-error js-message]
(re-frame/dispatch [:handle-whisper-message js-error js-message options])))

View File

@ -1,52 +0,0 @@
(ns status-im.protocol.message
(:require [cljs.spec.alpha :as s]))
(s/def :message/ttl (s/and int? pos?))
(s/def :message/from string?)
(s/def :message/sig :message/from)
(s/def :message/privateKeyID (s/nilable string?))
(s/def :message/pub-key (s/nilable string?))
(s/def :message/sym-key-password (s/nilable string?))
(s/def :message/topic string?)
(s/def :message/to (s/nilable 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
#{:public-group-message :group-message :group-invitation :add-group-identity
:remove-group-identity :leave-group :update-group})
(s/def :discover-message/type #{:online :status :discover :contact-request :update-keys})
(s/def :message/type
(s/or :group :group-message/type
:discover :discover-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? %)))

View File

@ -1,22 +0,0 @@
(ns status-im.protocol.message-cache
(:refer-clojure :exclude [exists?]))
(defonce messages-set (atom #{}))
(defonce messages-map (atom {}))
(defn init!
[messages]
(reset! messages-set (set messages))
(reset! messages-map (->> messages
(map (fn [{:keys [message-id type] :as message}]
[[message-id type] message]))
(into {}))))
(defn add!
[{:keys [message-id type] :as message}]
(swap! messages-set conj message)
(swap! messages-map conj [[message-id type] message]))
(defn exists?
[message-id type]
(get @messages-map [message-id type]))

View File

@ -1,12 +0,0 @@
(ns status-im.protocol.validation)
(defn- fline [and-form] (:line (meta and-form)))
(defmacro valid? [spec x]
`(let [v?# (cljs.spec.alpha/valid? ~spec ~x)]
(when-not v?#
(let [explanation# (cljs.spec.alpha/explain-str ~spec ~x)]
(taoensso.timbre/log! :error :p
[explanation#]
~{:?line (fline &form)})))
v?#))

View File

@ -1,2 +0,0 @@
(ns status-im.protocol.validation
(:require-macros [status-im.protocol.validation :as macros]))

View File

@ -1,271 +0,0 @@
(ns status-im.protocol.web3.delivery
(:require [cljs.core.async :as async]
[status-im.protocol.web3.transport :as t]
[status-im.protocol.web3.utils :as u]
[status-im.protocol.encryption :as e]
[cljs.spec.alpha :as s]
[taoensso.timbre :refer-macros [debug] :as log]
[status-im.protocol.validation :refer-macros [valid?]]
[clojure.set :as set]
[status-im.protocol.web3.keys :as shh-keys]
[status-im.utils.async :refer [timeout]]
[status-im.utils.datetime :as datetime]))
(defonce loop-state (atom nil))
(defonce messages (atom {}))
(defn prepare-message
[web3 {:keys [payload keypair to from topics ttl key-password]
:as message}
callback]
(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 :clock-value])
(merge payload)
(assoc :content content')
prn-str
u/from-utf8)
sym-key-password (or key-password shh-keys/status-key-password)]
(shh-keys/get-sym-key
web3
sym-key-password
(fn [status-key-id]
(callback
(merge
(select-keys message [:ttl])
(let [type (if to :asym :sym)]
(cond-> {:sig from
:topic (first topics)
:payload payload'}
to (assoc :pubKey to)
(not to) (assoc :symKeyID status-key-id
:sym-key-password sym-key-password)))))))))
(s/def :shh/pending-message
(s/keys :req-un [:message/sig :shh/payload :message/topic]
:opt-un [:message/ttl :message/pubKey :message/symKeyID]))
(defonce pending-message-callback (atom nil))
(defonce recipient->pending-message (atom {}))
;; Buffer needs to be big enough to not block even with many outbound messages
(def ^:private pending-message-queue (async/chan 2000))
(async/go-loop [[web3 {:keys [type message-id requires-ack? to ack?] :as message}]
(async/<! pending-message-queue)]
(when message
(prepare-message
web3 message
(fn [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-message-callback requires-ack?)
(@pending-message-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]}))))))
(recur (async/<! pending-message-queue))))
(defn set-pending-mesage-callback!
[callback]
(reset! pending-message-callback callback))
(defn add-pending-message!
[web3 message]
{:pre [(valid? :protocol/message message)]}
;; encryption can take some time, better to run asynchronously
(async/go (async/>! pending-message-queue [web3 message])))
(s/def :delivery/pending-message
(s/keys :req-un [:message/sig :message/to :shh/payload :payload/ack? ::id
:message/requires-ack? :message/topic ::attempts ::was-sent?]
:opt-un [:message/pub-key :message/sym-key-password]))
(defn- do-add-pending-message!
[web3 {:keys [message-id to pub-key sym-key-id] :as pending-message}]
(let [message (select-keys pending-message [:sig :topic :payload])
message' (if sym-key-id
(assoc message :symKeyId sym-key-id)
(assoc message :pubKey pub-key))
pending-message' (assoc pending-message :message message'
:id message-id)]
(swap! messages assoc-in [web3 message-id to] pending-message')
(when to
(swap! recipient->pending-message
update to set/union #{[web3 message-id to]}))))
(defn add-prepared-pending-message!
[web3 {:keys [sym-key-password] :as pending-message}]
{:pre [(valid? :delivery/pending-message pending-message)]}
(debug :add-prepared-pending-message!)
(if sym-key-password
(shh-keys/get-sym-key
web3
sym-key-password
(fn [sym-key-id]
(do-add-pending-message! web3 (assoc pending-message :sym-key-id sym-key-id))))
(do-add-pending-message! web3 pending-message)))
(defn remove-pending-message! [web3 id to]
(swap! messages update web3
(fn [messages]
(when messages
(let [message (messages id)
;; Message that is send without specified "from" option
;; is stored in pending "messages" map as
;; {message-id {nil message}}.
;; When we receive the first ack for such message it is
;; removed from pending messages adding of the nil key
;; to the next dissoc form
;; todo rewrite handling of ack message in more clear way
message' (dissoc message to nil)]
(if (seq message')
(assoc messages id message')
(dissoc messages id))))))
(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 web3
(fn [messages]
(let [message (get-in messages [id to])
message' (when message
(assoc message :was-sent? true
:attempts 1))]
(if message'
(assoc-in messages [id to] message')
messages))))]
(when @pending-message-callback
(let [message (get-in messages' [web3 id to])]
(when message
(@pending-message-callback :sent message))))))
(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 (datetime/timestamp)))))
(defn delivery-callback
[web3 post-error-callback {:keys [id requires-ack? to]} message]
(fn [error _]
(when error
(log/warn :shh-post-error error message)
(when post-error-callback
(post-error-callback 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 ::post-error-callback 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
::post-error-callback]
: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 less then max-attempts-number times
;; continue attempts
(<= attempts max-attempts-number)
;; check retransmission interval
(<= (+ last-attempt (* 1000 ack-not-received-s-interval)) (datetime/timestamp)))))
(defn- check-ttl
[message message-type ttl-config default-ttl]
(update message :ttl #(or % ((keyword message-type) ttl-config) default-ttl)))
(defn message-pending?
[web3 required-type required-to]
(some (fn [[_ messages]]
(some (fn [[_ {:keys [type to]}]]
(and (= type required-type)
(= to required-to)))
messages))
(@messages web3)))
(defn run-delivery-loop!
[web3 {:keys [delivery-loop-ms-interval default-ttl ttl-config
send-online-s-interval online-message post-error-callback
pow-target pow-time]
: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)
(async/go-loop [_ nil]
(doseq [[_ messages] (@messages web3)]
(doseq [[_ {:keys [id message to type] :as data}] messages]
;; check each message asynchronously
(when (should-be-retransmitted? options data)
(try
(let [message' (-> message
(check-ttl type ttl-config default-ttl)
(assoc :powTarget pow-target
:powTime pow-time))
callback (delivery-callback web3 post-error-callback data message')]
(t/post-message! web3 message' callback))
(catch :default err
(log/error :post-message-error err))
(finally
(attempt-was-made! web3 id to))))))
(when-not @stop?
(recur (async/<! (timeout delivery-loop-ms-interval)))))
(async/go-loop [_ nil]
(when-not @stop?
(online-message)
(recur (async/<! (timeout (* 1000 send-online-s-interval))))))))
(defn reset-pending-messages! [to]
(doseq [key (@recipient->pending-message to)]
(when (get-in @messages key)
(swap! messages #(update-in % key assoc
:last-attempt 0
:attempts 0)))))
(defn reset-all-pending-messages! []
(reset! messages {}))

View File

@ -1,61 +0,0 @@
(ns status-im.protocol.web3.filtering
(:require [status-im.protocol.web3.utils :as u]
[status-im.utils.config :as config]
[cljs.spec.alpha :as s]
[taoensso.timbre :as log]))
;; XXX(oskarth): Perf issue to have one topic
;; See https://github.com/status-im/ideas/issues/55#issuecomment-355511183
(def status-topic "0xaabb11ee")
(defonce filters (atom {}))
;; NOTE(oskarth): This has concerns for upgradability and chatting cross
;; versions. How can we do this breaking change gradually?
;; NOTE(oskarth): Due to perf we don't want a single topic for all messages,
;; instead we want many. We need a way for user A and B to agree on which topics
;; to use. By using first 10 characters of the pub-key, we construct a topic
;; that requires no coordination for 1-1 chats.
(defn identity->topic [identity]
(apply str (take 10 identity)))
(defn get-topics [& [identity]]
(if config/many-whisper-topics-enabled?
(do (log/info "FLAG: many-whisper-topics-enabled ON")
[(identity->topic identity)])
(do (log/info "FLAG: many-whisper-topics-enabled OFF")
[status-topic])))
(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
(fn [error _]
(when error
(log/warn :remove-filter-error options error))))
(log/debug :stop-watching options)
(swap! filters update web3 dissoc options)))
(defn add-shh-filter!
[web3 {:keys [key type] :as options} callback]
(let [type (or type :asym)
options' (cond-> (dissoc options :key)
(= type :asym) (assoc :privateKeyID key)
(= type :sym) (assoc :symKeyID key))
filter (.newMessageFilter (u/shh web3) (clj->js options')
callback
#(log/warn :add-filter-error (.stringify js/JSON (clj->js options')) %))]
(swap! filters assoc-in [web3 options] filter)))
(defn add-filter!
[web3 {:keys [topics to] :as options} callback]
(remove-filter! web3 options)
(log/debug :add-filter options)
(add-shh-filter! web3 options callback))
(defn remove-all-filters! []
(doseq [[web3 filters] @filters]
(doseq [options (keys filters)]
(remove-filter! web3 options))))

View File

@ -1,81 +0,0 @@
(ns status-im.protocol.web3.inbox
(:require [re-frame.core :as re-frame]
[status-im.native-module.core :as status]
[status-im.protocol.web3.utils :as web3.utils]
[taoensso.timbre :as log]))
(def peers (atom #{}))
(def trusted-peers (atom #{}))
;; NOTE(dmitryn) Expects JSON response like:
;; {"error": "msg"} or {"result": true}
(defn- parse-json [s]
(try
(let [res (-> s
js/JSON.parse
(js->clj :keywordize-keys true))]
;; NOTE(dmitryn): AddPeer() may return {"error": ""}
;; assuming empty error is a success response
;; by transforming {"error": ""} to {:result true}
(if (and (:error res)
(= (:error res) ""))
{:result true}
res))
(catch :default e
{:error (.-message e)})))
(defn- response-handler [error-fn success-fn]
(fn handle-response
([response]
(let [{:keys [error result]} (parse-json response)]
(handle-response error result)))
([error result]
(if error
(error-fn error)
(success-fn result)))))
(defn add-peer [enode success-fn error-fn]
(if (@peers enode)
(success-fn true)
(status/add-peer enode
(response-handler error-fn (fn [result]
(swap! peers conj enode)
(success-fn result))))))
(defn registered-peer? [peers enode]
(let [peer-ids (set (map :id peers))
enode-id (web3.utils/extract-enode-id enode)]
(contains? peer-ids enode-id)))
(defn mark-trusted-peer [web3 enode peers success-fn error-fn]
(if (@trusted-peers enode)
(success-fn true)
(.markTrustedPeer (web3.utils/shh web3)
enode
(response-handler error-fn (fn [result]
(swap! trusted-peers conj enode)
(success-fn result))))))
;; TODO(dmitryn): use web3 instead of rpc call
(defn fetch-peers [success-fn error-fn]
(let [args {:jsonrpc "2.0"
:id 2
:method "admin_peers"
:params []}
payload (.stringify js/JSON (clj->js args))]
(status/call-web3 payload
(response-handler error-fn success-fn))))
(defn request-messages [web3 wnode topic sym-key-id success-fn error-fn]
(log/info "offline inbox: sym-key-id" sym-key-id)
(let [opts {:mailServerPeer wnode
:topic topic
:symKeyID sym-key-id}]
(log/info "offline inbox: request-messages request")
(log/info "offline inbox: request-messages args" (pr-str opts))
(.requestMessages (web3.utils/shh web3)
(clj->js opts)
(response-handler error-fn success-fn))))
(defn initialize! [web3]
(re-frame/dispatch [:initialize-offline-inbox web3]))

View File

@ -1,33 +0,0 @@
(ns status-im.protocol.web3.keys
(:require [taoensso.timbre :as log]))
(def status-key-password "status-key-password")
(def status-group-key-password "status-public-group-key-password")
(defonce password->keys (atom {}))
(defn- add-sym-key-from-password
[web3 password callback]
(.. web3
-shh
(generateSymKeyFromPassword password callback)))
(defn get-sym-key
"Memoizes expensive calls by password."
([web3 password success-fn]
;; TODO:(dmitryn) add proper error handling
;; to other usages of get-sym-key fn
(get-sym-key web3 password success-fn #(log/error %)))
([web3 password success-fn error-fn]
(if-let [key-id (get @password->keys password)]
(success-fn key-id)
(add-sym-key-from-password
web3 password
(fn [err res]
(if err
(error-fn err)
(do (swap! password->keys assoc password res)
(success-fn res))))))))
(defn reset-keys! []
(reset! password->keys {}))

View File

@ -1,19 +0,0 @@
(ns status-im.protocol.web3.transport
(:require [status-im.protocol.web3.utils :as u]
[cljs.spec.alpha :as s]
[status-im.protocol.validation :refer-macros [valid?]]
[taoensso.timbre :refer-macros [debug]]))
(s/def :shh/payload string?)
(s/def :shh/powTarget number?)
(s/def :shh/powTime number?)
(s/def :shh/message
(s/keys
:req-un [:shh/payload :message/ttl :message/sig :message/topic
:shh/powTarget :shh/powTime]))
(defn post-message!
[web3 message callback]
{:pre [(valid? :shh/message message)]}
(let [shh (u/shh web3)]
(.post shh (clj->js message) callback)))

View File

@ -0,0 +1,80 @@
(ns ^{:doc "API to init and stop whisper messaging"}
status-im.transport.core
(:require [cljs.spec.alpha :as spec]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.transport.message.core :as message]
[status-im.transport.filters :as filters]
[status-im.transport.utils :as transport.utils]
[taoensso.timbre :as log]
[status-im.transport.inbox :as inbox]
[status-im.utils.handlers :as handlers]
[status-im.transport.db :as transport.db]))
(defn init-whisper
"Initialises whisper protocol by:
- adding fixed shh discovery filter
- restoring existing symetric keys along with their unique filters
- (optionally) initializing offline inboxing"
[current-account-id {:keys [db web3] :as cofx}]
(log/debug :init-whisper)
(when-let [public-key (get-in db [:accounts/accounts current-account-id :public-key])]
(let [sym-key-added-callback (fn [chat-id sym-key sym-key-id]
(re-frame/dispatch [::sym-key-added {:chat-id chat-id
:sym-key sym-key
:sym-key-id sym-key-id}]))
topic (transport.utils/get-topic constants/contact-discovery)]
(handlers/merge-fx cofx
{:shh/add-discovery-filter {:web3 web3
:private-key-id public-key
:topic topic}
:shh/restore-sym-keys {:web3 web3
:transport (:transport/chats db)
:on-success sym-key-added-callback}}
(inbox/initialize-offline-inbox)))))
;;TODO (yenda) remove once go implements persistence
;;Since symkeys are not persisted, we restore them via add sym-keys,
;;this is the callback that is called when a key has been restored for a particular chat.
;;it saves the sym-key-id in app-db to send messages later
;;and starts a filter to receive messages
(handlers/register-handler-fx
::sym-key-added
(fn [{:keys [db]} [_ {:keys [chat-id sym-key sym-key-id]}]]
(let [web3 (:web3 db)
{:keys [topic] :as chat} (get-in db [:transport/chats chat-id])]
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:data-store.transport/save {:chat-id chat-id
:chat (assoc chat :sym-key-id sym-key-id)}
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic topic
:chat-id chat-id}})))
;;TODO (yenda) uncomment and rework once go implements persistence
#_(doseq [[chat-id {:keys [sym-key-id topic] :as chat}] transport]
(when sym-key-id
(filters/add-filter! web3
{:symKeyID sym-key-id
:topics [topic]}
(fn [js-error js-message]
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message chat-id])))))
(defn unsubscribe-from-chat
"Unsubscribe from chat on transport layer"
[chat-id {:keys [db]}]
(let [filter (get-in db [:transport/chats chat-id :filter])]
{:db (update db :transport/chats dissoc chat-id)
:data-store.transport/delete chat-id
:shh/remove-filter filter}))
(defn stop-whisper
"Stops whisper protocol by removing all existing shh filters
It is necessary to remove the filters because status-go there isn't currently a logout feature in status-go
to clean-up after logout. When logging out of account A and logging in account B, account B would receive
account A messages without this."
[{:keys [db]}]
(let [{:transport/keys [chats discovery-filter]} db
chat-filters (mapv :filter (vals chats))
all-filters (conj chat-filters discovery-filter)]
{:shh/remove-filters all-filters}))

View File

@ -0,0 +1,34 @@
(ns ^{:doc "DB spec and utils for the transport layer"}
status-im.transport.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as spec]))
;; required
(spec/def ::ack (spec/coll-of string? :kind vector?))
(spec/def ::seen (spec/coll-of string? :kind vector?))
(spec/def ::pending-ack (spec/coll-of string? :kind vector?))
(spec/def ::pending-send (spec/coll-of string? :kind vector?))
(spec/def ::topic string?)
;; optional
(spec/def ::sym-key-id string?)
;;TODO (yenda) remove once go implements persistence
(spec/def ::sym-key string?)
(spec/def ::filter any?)
(spec/def :transport/chat (allowed-keys :req-un [::ack ::seen ::pending-ack ::pending-send ::topic]
:opt-un [::sym-key-id ::sym-key ::filter]))
(spec/def :transport/chats (spec/map-of :global/not-empty-string :transport/chat))
(spec/def :transport/discovery-filter (spec/nilable any?))
(defn create-chat
"Initialize datastructure for chat representation at the transport level
Currently only :topic is actually used"
[topic]
{:ack []
:seen []
:pending-ack []
:pending-send []
:topic topic})

View File

@ -0,0 +1,71 @@
(ns ^{:doc "API for whisper filters"}
status-im.transport.filters
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.transport.utils :as utils]
[status-im.utils.config :as config]
[taoensso.timbre :as log]))
(defn remove-filter! [filter]
(.stopWatching filter
(fn [error _]
(when error
(log/warn :remove-filter-error filter error))))
(log/debug :stop-watching filter))
(defn add-shh-filter!
[web3 options callback]
(.newMessageFilter (utils/shh web3) (clj->js options)
callback
#(log/warn :add-filter-error (.stringify js/JSON (clj->js options)) %)))
(defn add-filter!
[web3 {:keys [topics to] :as options} callback]
(let [options (if config/offline-inbox-enabled?
(assoc options :allowP2P true)
options)]
(log/debug :add-filter options)
(add-shh-filter! web3 options callback)))
(re-frame/reg-fx
:shh/add-filter
(fn [{:keys [web3 sym-key-id topic chat-id]}]
(when-let [filter (add-filter! web3
{:topics [topic]
:symKeyID sym-key-id}
(fn [js-error js-message]
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message chat-id])))]
(re-frame/dispatch [::filter-added chat-id filter]))))
(handlers/register-handler-db
::filter-added
[re-frame/trim-v]
(fn [db [chat-id filter]]
(assoc-in db [:transport/chats chat-id :filter] filter)))
(re-frame/reg-fx
:shh/add-discovery-filter
(fn [{:keys [web3 private-key-id topic]}]
(when-let [filter (add-filter! web3
{:topics [topic]
:privateKeyID private-key-id}
(fn [js-error js-message]
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message])))]
(re-frame/dispatch [::discovery-filter-added filter]))))
(handlers/register-handler-db
::discovery-filter-added
[re-frame/trim-v]
(fn [db [filter]]
(assoc db :transport/discovery-filter filter)))
(re-frame/reg-fx
:shh/remove-filter
(fn [filter]
(when filter (remove-filter! filter))))
(re-frame/reg-fx
:shh/remove-filters
(fn [filters]
(doseq [filter filters]
(remove-filter! filter))))

View File

@ -0,0 +1,37 @@
(ns ^{:doc "Events for message handling"}
status-im.transport.handlers
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.transport.message.core :as message]
[status-im.transport.core :as transport]
[status-im.chat.models :as models.chat]
[status-im.utils.datetime :as datetime]
[taoensso.timbre :as log]
[status-im.transport.utils :as transport.utils]
[cljs.reader :as reader]
[status-im.transport.message.transit :as transit]
[status-im.transport.shh :as shh]
[status-im.transport.filters :as filters]))
(handlers/register-handler-fx
:protocol/receive-whisper-message
[re-frame/trim-v]
(fn [cofx [js-error js-message chat-id]]
(let [{:keys [payload sig]} (js->clj js-message :keywordize-keys true)
status-message (-> payload
transport.utils/to-utf8
transit/deserialize)]
(when (and sig status-message)
(message/receive status-message (or chat-id sig) sig cofx)))))
(handlers/register-handler-fx
:protocol/send-status-message-success
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [_ resp]]
(log/debug :send-status-message-success resp)))
(handlers/register-handler-fx
:protocol/send-status-message-error
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [err]]
(log/error :send-status-message-error err)))

View File

@ -0,0 +1,191 @@
(ns ^{:doc "Offline inboxing events and API"}
status-im.transport.inbox
(:require [re-frame.core :as re-frame]
[status-im.native-module.core :as status]
[status-im.utils.handlers :as handlers]
[status-im.transport.utils :as web3.utils]
[status-im.utils.config :as config]
[taoensso.timbre :as log]))
(defn- parse-json
;; NOTE(dmitryn) Expects JSON response like:
;; {"error": "msg"} or {"result": true}
[s]
(try
(let [res (-> s
js/JSON.parse
(js->clj :keywordize-keys true))]
;; NOTE(dmitryn): AddPeer() may return {"error": ""}
;; assuming empty error is a success response
;; by transforming {"error": ""} to {:result true}
(if (and (:error res)
(= (:error res) ""))
{:result true}
res))
(catch :default e
{:error (.-message e)})))
(defn- response-handler [error-fn success-fn]
(fn handle-response
([response]
(let [{:keys [error result]} (parse-json response)]
(handle-response error result)))
([error result]
(if error
(error-fn error)
(success-fn result)))))
(defn initialize-offline-inbox
"Initialises offline inbox by producing `::add-peer` effect if inboxing enabled in config,
then the event chan is:
add-peer -> fetch-peers -> check-peer-added -> mark-trusted-peer -> get-sym-key -> request-messages"
[{:keys [db]}]
(when config/offline-inbox-enabled?
(let [wnode-id (get db :inbox/wnode)
wnode (get-in db [:inbox/wnodes wnode-id :address])]
(log/info "offline inbox: initialize")
{::add-peer {:wnode wnode}})))
(defn add-peer [enode success-fn error-fn]
(status/add-peer enode (response-handler error-fn success-fn)))
(defn fetch-peers
;; https://github.com/ethereum/go-ethereum/wiki/Management-APIs#admin_peers
;; retrieves all the information known about the connected remote nodes
;; TODO(dmitryn): use web3 instead of rpc call
[success-fn error-fn]
(let [args {:jsonrpc "2.0"
:id 2
:method "admin_peers"
:params []}
payload (.stringify js/JSON (clj->js args))]
(status/call-web3 payload (response-handler error-fn success-fn))))
(defn registered-peer? [peers enode]
(let [peer-ids (into #{} (map :id) peers)
enode-id (web3.utils/extract-enode-id enode)]
(contains? peer-ids enode-id)))
(defn mark-trusted-peer [web3 enode peers success-fn error-fn]
(.markTrustedPeer (web3.utils/shh web3)
enode
(fn [err resp]
(if-not err
(success-fn resp)
(error-fn err)))))
(defn request-messages [web3 wnode topics to from sym-key-id success-fn error-fn]
(log/info "offline inbox: sym-key-id" sym-key-id)
(let [opts (merge {:mailServerPeer wnode
:symKeyID sym-key-id}
(when from {:from from})
(when to {:to to}))]
(log/info "offline inbox: request-messages request for topics " topics)
(doseq [topic topics]
(let [opts (assoc opts :topic topic)]
(log/info "offline inbox: request-messages args" (pr-str opts))
(.requestMessages (web3.utils/shh web3)
(clj->js opts)
(fn [err resp]
(if-not err
(success-fn resp)
(error-fn err topic))))))))
(defn get-wnode [db]
(let [wnode-id (get db :inbox/wnode)]
(get-in db [:inbox/wnodes wnode-id :address])))
(re-frame/reg-fx
::add-peer
(fn [{:keys [wnode]}]
(add-peer wnode
#(re-frame/dispatch [::fetch-peers %])
#(log/error "offline inbox: add-peer error" %))))
(re-frame/reg-fx
::fetch-peers
(fn [retries]
(fetch-peers #(re-frame/dispatch [::check-peer-added % retries])
#(log/error "offline inbox: fetch-peers error" %))))
(re-frame/reg-fx
::mark-trusted-peer
(fn [{:keys [wnode web3 peers]}]
(mark-trusted-peer web3
wnode
peers
#(re-frame/dispatch [::get-sym-key %])
#(log/error "offline inbox: mark-trusted-peer error" % wnode))))
(re-frame/reg-fx
::request-messages
(fn [{:keys [wnode topics to from sym-key-id web3]}]
(request-messages web3
wnode
topics
to
from
sym-key-id
#(log/info "offline inbox: request-messages response" %)
#(log/error "offline inbox: request-messages error" %1 %2 to from))))
;;;; Handlers
(handlers/register-handler-fx
::fetch-peers
;; This event fetches the list of peers
;; We want it to check if the node has been added
(fn [_ [_ retries]]
{::fetch-peers (or retries 0)}))
(handlers/register-handler-fx
::check-peer-added
;; We check if the wnode is part of the peers list
;; if not we dispatch a new fetch-peer event for later
(fn [{:keys [db]} [_ peers retries]]
(let [web3 (:web3 db)
wnode (get-wnode db)]
(log/info "offline inbox: fetch-peers response" peers)
(if (registered-peer? peers wnode)
{::mark-trusted-peer {:web3 web3
:wnode wnode
:peers peers}}
(do
(log/info "Peer" wnode "is not registered. Retrying fetch peers.")
(let [delay (if (< retries 3) 300 5000)]
(if (> retries 10)
(log/error "Could not connect to mailserver")
{:dispatch-later [{:ms delay :dispatch [::fetch-peers (inc retries)]}]})))))))
(handlers/register-handler-fx
::get-sym-key
;; TODO(yenda): using core async flow this event can be done in parallel
;; with add-peer
(fn [{:keys [db]} [_ response]]
(let [web3 (:web3 db)
wnode (get-wnode db)
password (:inbox/password db)]
(log/info "offline inbox: mark-trusted-peer response" wnode response)
{:shh/generate-sym-key-from-password {:password password
:web3 web3
:on-success (fn [_ sym-key-id]
(re-frame/dispatch [::request-messages sym-key-id]))
:on-error #(log/error "offline inbox: get-sym-key error" %)}})))
(handlers/register-handler-fx
::request-messages
;; TODO(yenda): we want to request-message once per topic and for specific timespan so
;; we want a plural version of this function that does the right thing
(fn [{:keys [db]} [_ sym-key-id]]
(log/info "offline inbox: get-sym-key response") sym-key-id
(let [web3 (:web3 db)
wnode (get-wnode db)
topics (map #(:topic %) (vals (:transport/chats db)))
to nil
from nil]
{::request-messages {:wnode wnode
:topics topics
:to to
:from from
:sym-key-id sym-key-id
:web3 web3}})))

View File

@ -0,0 +1,7 @@
(ns ^{:doc "Definition of the StatusMessage protocol"}
status-im.transport.message.core)
(defprotocol StatusMessage
"Protocol for the messages that are sent through the transport layer"
(send [this chat-id cofx] "Method producing all effects necessary for sending the message record")
(receive [this chat-id signature cofx] "Method producing all effects necessary for receiving the message record"))

View File

@ -0,0 +1,123 @@
(ns ^{:doc "Transit custom readers and writers, required when adding a new record implementing StatusMessage protocol"}
status-im.transport.message.transit
(:require [status-im.transport.message.v1.contact :as v1.contact]
[status-im.transport.message.v1.protocol :as v1.protocol]
[status-im.transport.message.v1.group-chat :as v1.group-chat]
[cognitect.transit :as transit]))
;; When adding a new reccord implenting the StatusMessage protocol it is required to implement:
;; - a handler that will turn the clojure record into a javascript datastructure.
;; - a reader that will turn the javascript datastructure back into a clojure record.
;; Use the existing types as exemples of how this is done
;;
;; Writer handlers
;;
;; Each writer defines a tag and a representation
;; The tag will determine which reader is used to recreate the clojure record
;; When migrating a particular record, it is important to use a different type and still handle the previous
;; gracefully for compatibility
(deftype NewContactKeyHandler []
Object
(tag [this v] "c1")
(rep [this {:keys [sym-key topic message]}]
#js [sym-key topic message]))
(deftype ContactRequestHandler []
Object
(tag [this v] "c2")
(rep [this {:keys [name profile-image address fcm-token]}]
#js [name profile-image address fcm-token]))
(deftype ContactRequestConfirmedHandler []
Object
(tag [this v] "c3")
(rep [this {:keys [name profile-image address fcm-token]}]
#js [name profile-image address fcm-token]))
(deftype ContactUpdateHandler []
Object
(tag [this v] "c6")
(rep [this {:keys [name profile-image]}]
#js [name profile-image]))
(deftype MessageHandler []
Object
(tag [this v] "c4")
(rep [this {:keys [content content-type message-type to-clock-value timestamp]}]
#js [content content-type message-type to-clock-value timestamp]))
(deftype MessagesSeenHandler []
Object
(tag [this v] "c5")
(rep [this {:keys [message-ids]}]
(clj->js message-ids)))
(deftype NewGroupKeyHandler []
Object
(tag [this v] "g1")
(rep [this {:keys [chat-id sym-key message]}]
#js [chat-id sym-key message]))
(deftype GroupAdminUpdateHandler []
Object
(tag [this v] "g2")
(rep [this {:keys [chat-name participants]}]
#js [chat-name participants]))
(deftype GroupLeaveHandler []
Object
(tag [this v] "g3")
(rep [this _]
(clj->js nil)))
(def writer (transit/writer :json
{:handlers
{v1.contact/NewContactKey (NewContactKeyHandler.)
v1.contact/ContactRequest (ContactRequestHandler.)
v1.contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.)
v1.contact/ContactUpdate (ContactUpdateHandler.)
v1.protocol/Message (MessageHandler.)
v1.protocol/MessagesSeen (MessagesSeenHandler.)
v1.group-chat/NewGroupKey (NewGroupKeyHandler.)
v1.group-chat/GroupAdminUpdate (GroupAdminUpdateHandler.)
v1.group-chat/GroupLeave (GroupLeaveHandler.)}}))
;;
;; Reader handlers
;;
;; Here we only need to call the record with the arguments parsed from the clojure datastructures
(def reader (transit/reader :json
{:handlers
{"c1" (fn [[sym-key topic message]]
(v1.contact/NewContactKey. sym-key topic message))
"c2" (fn [[name profile-image address fcm-token]]
(v1.contact/ContactRequest. name profile-image address fcm-token))
"c3" (fn [[name profile-image address fcm-token]]
(v1.contact/ContactRequestConfirmed. name profile-image address fcm-token))
"c4" (fn [[content content-type message-type to-clock-value timestamp]]
(v1.protocol/Message. content content-type message-type to-clock-value timestamp))
"c5" (fn [message-ids]
(v1.protocol/MessagesSeen. message-ids))
"c6" (fn [[name profile-image]]
(v1.contact/ContactUpdate. name profile-image))
"g1" (fn [[chat-id sym-key message]]
(v1.group-chat/NewGroupKey. chat-id sym-key message))
"g2" (fn [[chat-name participants]]
(v1.group-chat/GroupAdminUpdate. chat-name participants))
"g3" (fn [_]
(v1.group-chat/GroupLeave.))}}))
(defn serialize
"Serializes a record implementing the StatusMessage protocol using the custom writers"
[o]
(transit/write writer o))
(defn deserialize
"Deserializes a record implementing the StatusMessage protocol using the custom readers"
[o]
(try (transit/read reader o) (catch :default e nil)))

View File

@ -0,0 +1,134 @@
(ns ^{:doc "Contact request and update API"}
status-im.transport.message.v1.contact
(:require [re-frame.core :as re-frame]
[status-im.transport.message.core :as message]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.utils :as transport.utils]
[status-im.ui.screens.contacts.core :as contacts]
[status-im.utils.handlers :as handlers]))
(defrecord NewContactKey [sym-key topic message]
message/StatusMessage
(send [this chat-id cofx]
(protocol/send-with-pubkey {:chat-id chat-id
:payload this}
cofx))
(receive [this chat-id signature cofx]
(let [on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::add-new-sym-key {:sym-key-id sym-key-id
:sym-key sym-key
:chat-id chat-id
:topic topic
:message message}]))]
(handlers/merge-fx cofx
{:shh/add-new-sym-key {:web3 (get-in cofx [:db :web3])
:sym-key sym-key
:on-success on-success}}
(protocol/init-chat chat-id topic)))))
(defrecord ContactRequest [name profile-image address fcm-token]
message/StatusMessage
(send [this chat-id {:keys [db random-id] :as cofx}]
(let [message-id (transport.utils/message-id this)
topic (transport.utils/get-topic random-id)
on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::send-new-sym-key {:sym-key-id sym-key-id
:sym-key sym-key
:chat-id chat-id
:topic topic
:message this}]))]
(handlers/merge-fx cofx
{:shh/get-new-sym-key {:web3 (:web3 db)
:on-success on-success}}
(protocol/init-chat chat-id topic)
#_(protocol/requires-ack message-id chat-id))))
(receive [this chat-id signature {:keys [db] :as cofx}]
(let [message-id (transport.utils/message-id this)]
(when (protocol/is-new? message-id)
(handlers/merge-fx cofx
#_(protocol/ack message-id chat-id)
(contacts/receive-contact-request signature
this))))))
(defrecord ContactRequestConfirmed [name profile-image address fcm-token]
message/StatusMessage
(send [this chat-id cofx]
(let [message-id (transport.utils/message-id this)]
(handlers/merge-fx cofx
#_(protocol/requires-ack message-id chat-id)
(protocol/send {:chat-id chat-id
:payload this}))))
(receive [this chat-id signature cofx]
(let [message-id (transport.utils/message-id this)]
(when (protocol/is-new? message-id)
(handlers/merge-fx cofx
#_(protocol/ack message-id chat-id)
(contacts/receive-contact-request-confirmation signature
this))))))
(defrecord ContactUpdate [name profile-image]
message/StatusMessage
(send [this _ {:keys [db] :as cofx}]
(let [message-id (transport.utils/message-id this)
public-keys (remove nil? (map :public-key (vals (:contacts/contacts db))))]
(handlers/merge-fx cofx
(protocol/multi-send-with-pubkey {:public-keys public-keys
:payload this}))))
(receive [this chat-id signature cofx]
(let [message-id (transport.utils/message-id this)]
(when (protocol/is-new? message-id)
(handlers/merge-fx cofx
(contacts/receive-contact-update chat-id
signature
this))))))
(handlers/register-handler-fx
::send-new-sym-key
(fn [{:keys [db random-id] :as cofx} [_ {:keys [chat-id topic message sym-key sym-key-id]}]]
(let [{:keys [web3 current-public-key]} db
chat-transport-info (-> (get-in db [:transport/chats chat-id])
(assoc :sym-key-id sym-key-id)
;;TODO (yenda) remove once go implements persistence
(assoc :sym-key sym-key))]
(handlers/merge-fx cofx
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic topic
:chat-id chat-id}
:data-store.transport/save {:chat-id chat-id
:chat chat-transport-info}}
(message/send (NewContactKey. sym-key topic message)
chat-id)))))
(handlers/register-handler-fx
::add-new-sym-key
(fn [{:keys [db] :as cofx} [_ {:keys [sym-key-id sym-key chat-id topic message]}]]
(let [{:keys [web3 current-public-key]} db
chat-transport-info (-> (get-in db [:transport/chats chat-id])
(assoc :sym-key-id sym-key-id)
;;TODO (yenda) remove once go implements persistence
(assoc :sym-key sym-key))]
(handlers/merge-fx cofx
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic topic
:chat-id chat-id}
:data-store.transport/save {:chat-id chat-id
:chat chat-transport-info}}
(message/receive message chat-id chat-id)))))
#_(handlers/register-handler-fx
:send-test-message
(fn [cofx [this timer chat-id n]]
(if (zero? n)
(println "Time: " (str (- (inst-ms (js/Date.)) @timer)))
(handlers/merge-fx cofx
{:dispatch [this timer chat-id (dec n)]}
(message/send (protocol/map->Message {:content (str n)
:content-type "text/plain"
:message-type :user-message
:clock-value n
:timestamp (str (inst-ms (js/Date.)))})
chat-id)))))

View File

@ -0,0 +1,179 @@
(ns ^{:doc "Group chat API"}
status-im.transport.message.v1.group-chat
(:require [clojure.set :as set]
[re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.transport.message.core :as message]
[status-im.i18n :as i18n]
[status-im.ui.screens.group.core :as group]
[status-im.chat.models :as models.chat]
[status-im.chat.models.message :as models.message]
[status-im.transport.core :as transport]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.utils :as transport.utils]))
;; NOTE: We ignore the chat-id from the send and receive method.
;; The chat-id is usually deduced from the filter the message comes from but not in that case because it is sent
;; individually to each participant of the group.
;; In order to be able to determine which group the message belongs to the chat-id is therefore
;; passed in the message itself
(defrecord NewGroupKey [chat-id sym-key message]
message/StatusMessage
(send [this _ cofx]
(let [public-keys (map :identity (get-in cofx [:db :chats chat-id :contacts]))]
(protocol/multi-send-with-pubkey {:public-keys public-keys
:chat-id chat-id
:payload this}
cofx)))
(receive [this _ signature {:keys [db] :as cofx}]
(handlers/merge-fx cofx
{:shh/add-new-sym-key {:web3 (:web3 db)
:sym-key sym-key
:on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::add-new-sym-key {:chat-id chat-id
:sym-key sym-key
:sym-key-id sym-key-id
:message message}]))}}
(protocol/init-chat chat-id))))
(defn- user-is-group-admin? [chat-id cofx]
(= (get-in cofx [:db :chats chat-id :group-admin])
(get-in cofx [:db :current-public-key])))
(defn- send-new-group-key [message chat-id cofx]
(when (user-is-group-admin? chat-id cofx)
{:shh/get-new-sym-key {:web3 (get-in cofx [:db :web3])
:on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::send-new-sym-key {:chat-id chat-id
:sym-key sym-key
:sym-key-id sym-key-id
:message message}]))}}))
(defn- prepare-system-message [admin-name added-participants removed-participants contacts]
(let [added-participants-names (map #(get-in contacts [% :name] %) added-participants)
removed-participants-names (map #(get-in contacts [% :name] %) removed-participants)]
(cond
(and added-participants removed-participants)
(str admin-name " "
(i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names))
" and "
(i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names)))
added-participants
(str admin-name " " (i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names)))
removed-participants
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
(defn- init-chat-if-new [chat-id cofx]
(if (nil? (get-in cofx [:db :transport/chats chat-id]))
(protocol/init-chat chat-id cofx)))
(defn- participants-diff [existing-participants-set new-participants-set]
{:removed (set/difference existing-participants-set new-participants-set)
:added (set/difference new-participants-set existing-participants-set)})
(defrecord GroupAdminUpdate [chat-name participants]
message/StatusMessage
(send [this chat-id cofx]
(handlers/merge-fx cofx
(init-chat-if-new chat-id)
(send-new-group-key this chat-id)))
(receive [this chat-id signature {:keys [now db] :as cofx}]
(let [me (:current-public-key db)]
;; we have to check if we already have a chat, or it's a new one
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
;; update for existing group chat
(when (= signature group-admin) ;; make sure that admin is the one making changes
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
admin-name (or (get-in cofx [db :contacts/contacts group-admin :name])
group-admin)
message-id (transport.utils/message-id this)]
(if (removed me) ;; we were removed
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(str admin-name " " (i18n/label :t/removed-from-chat))))
(models.chat/update-chat {:chat-id chat-id
:removed-from-at now
:is-active false})
(transport/unsubscribe-from-chat chat-id))
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(prepare-system-message admin-name
added
removed
(:contacts/contacts db))))
(group/participants-added chat-id added)
(group/participants-removed chat-id removed)))))
;; first time we hear about chat -> create it if we are among participants
(when (get (set participants) me)
(models.chat/add-group-chat chat-id chat-name signature participants cofx))))))
(defrecord GroupLeave []
message/StatusMessage
(send [this chat-id cofx]
(protocol/send {:chat-id chat-id
:payload this
:success-event [::unsubscribe-from-chat chat-id]}
cofx))
(receive [this chat-id signature {:keys [db now] :as cofx}]
(let [message-id (transport.utils/message-id this)
me (:current-public-key db)
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
signature)]
(when-not (= me signature)
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(str participant-leaving-name " " (i18n/label :t/left))))
(group/participants-removed chat-id #{signature})
(send-new-group-key nil chat-id))))))
(handlers/register-handler-fx
::unsubscribe-from-chat
[re-frame/trim-v]
(fn [cofx [chat-id]]
(transport/unsubscribe-from-chat chat-id cofx)))
(handlers/register-handler-fx
::send-new-sym-key
[re-frame/trim-v]
;; this is the event that is called when we want to send a message that required first
;; some async operations
(fn [{:keys [db] :as cofx} [{:keys [chat-id message sym-key sym-key-id]}]]
(let [{:keys [web3]} db]
(handlers/merge-fx cofx
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic (transport.utils/get-topic chat-id)
:chat-id chat-id}
:data-store.transport/save {:chat-id chat-id
:chat (-> (get-in db [:transport/chats chat-id])
(assoc :sym-key-id sym-key-id)
;;TODO (yenda) remove once go implements persistence
(assoc :sym-key sym-key))}}
(message/send (NewGroupKey. chat-id sym-key message) chat-id)))))
(handlers/register-handler-fx
::add-new-sym-key
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [{:keys [sym-key-id sym-key chat-id message]}]]
(let [{:keys [web3 current-public-key]} db
fx {:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic (transport.utils/get-topic chat-id)
:chat-id chat-id}
:data-store.transport/save {:chat-id chat-id
:chat (-> (get-in db [:transport/chats chat-id])
(assoc :sym-key-id sym-key-id)
;;TODO (yenda) remove once go implements persistence
(assoc :sym-key sym-key))}}]
;; if new sym-key is wrapping some message, call receive on it as well, if not just update the transport layer
(if message
(handlers/merge-fx cofx fx (message/receive message chat-id chat-id))
fx))))

View File

@ -0,0 +1,116 @@
(ns ^{:doc "Protocol API and protocol utils"}
status-im.transport.message.v1.protocol
(:require [status-im.utils.config :as config]
[status-im.constants :as constants]
[status-im.chat.core :as chat]
[status-im.transport.message-cache :as message-cache]
[status-im.transport.db :as transport.db]
[status-im.transport.core :as transport]
[status-im.transport.message.core :as message]
[status-im.transport.utils :as transport.utils]))
(def ^:private whisper-opts
{:ttl 10 ;; ttl of 10 sec
:powTarget config/pow-target
:powTime config/pow-time})
(defn init-chat
"Initialises chat on protocol layer.
2 arities are provided, 2arg arity initialises the chat with topic derived from first argument (`chat-id`),
using the 3arg arity, you can specify the topic as well (second argument)"
([chat-id cofx]
(init-chat chat-id (transport.utils/get-topic chat-id) cofx))
([chat-id topic {:keys [db] :as cofx}]
{:db (assoc-in db [:transport/chats chat-id] (transport.db/create-chat topic))}))
(defn is-new?
"Determines if given message-id already exists in in-memory message cache"
[message-id]
(when-not (message-cache/exists? message-id)
(message-cache/add! message-id)))
#_(defn requires-ack [message-id chat-id {:keys [db] :as cofx}]
{:db (update-in db [:transport/chats chat-id :pending-ack] conj message-id)})
#_(defn ack [message-id chat-id {:keys [db] :as cofx}]
{:db (update-in db [:transport/chats chat-id :ack] conj message-id)})
(defn send
"Sends the payload using symetric key and topic from db (looked up by `chat-id`)"
[{:keys [payload chat-id success-event]} {:keys [db] :as cofx}]
;; we assume that the chat contains the contact public-key
(let [{:keys [current-public-key web3]} db
{:keys [sym-key-id topic]} (get-in db [:transport/chats chat-id])]
{:shh/post {:web3 web3
:success-event success-event
:message (merge {:sig current-public-key
:symKeyID sym-key-id
:payload payload
:topic topic}
whisper-opts)}}))
(defn send-with-pubkey
"Sends the payload using asymetric key (`:current-public-key` in db) and fixed discovery topic"
[{:keys [payload chat-id success-event]} {:keys [db] :as cofx}]
(let [{:keys [current-public-key web3]} db]
{:shh/post {:web3 web3
:success-event success-event
:message (merge {:sig current-public-key
:pubKey chat-id
:payload payload
:topic (transport.utils/get-topic constants/contact-discovery)}
whisper-opts)}}))
(defn- prepare-recipients [public-keys db]
(map (fn [public-key]
(select-keys (get-in db [:transport/chats public-key]) [:topic :sym-key-id]))
public-keys))
(defn multi-send-with-pubkey
"Sends payload to multiple participants selected by `:public-keys` key."
[{:keys [payload public-keys success-event]} {:keys [db] :as cofx}]
(let [{:keys [current-public-key web3]} db
recipients (prepare-recipients public-keys db)]
{:shh/multi-post {:web3 web3
:success-event success-event
:recipients recipients
:message (merge {:sig current-public-key
:payload payload}
whisper-opts)}}))
;; TODO currently not used
(defrecord Ack [message-ids]
message/StatusMessage
(send [this cofx chat-id])
(receive [this db chat-id sig]))
(defrecord Seen [message-ids]
message/StatusMessage
(send [this cofx chat-id])
(receive [this cofx chat-id sig]))
(defrecord Message [content content-type message-type to-clock-value timestamp]
message/StatusMessage
(send [this chat-id cofx]
(send {:chat-id chat-id
:payload this
:success-event [:update-message-status
chat-id
(transport.utils/message-id this)
(get-in cofx [:db :current-public-key])
:sent]}
cofx))
(receive [this chat-id signature cofx]
{:dispatch [:chat-received-message/add (assoc (into {} this)
:message-id (transport.utils/message-id this)
:chat-id chat-id
:from signature)]}))
(defrecord MessagesSeen [message-ids]
message/StatusMessage
(send [this chat-id cofx]
(send {:chat-id chat-id
:payload this}
cofx))
(receive [this chat-id signature cofx]
(chat/receive-seen chat-id signature (:message-ids this) cofx)))

View File

@ -0,0 +1,36 @@
(ns ^{:doc "Public chat API"}
status-im.transport.message.v1.public-chat
(:require [re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.transport.message.core :as message]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.utils :as transport.utils]))
(defn join-public-chat
"Function producing all protocol level effects necessary for joining public chat identified by chat-id"
[chat-id {:keys [db] :as cofx}]
(let [on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::add-new-sym-key {:chat-id chat-id
:sym-key sym-key
:sym-key-id sym-key-id}]))]
(handlers/merge-fx cofx
{:shh/generate-sym-key-from-password {:web3 (:web3 db)
:password chat-id
:on-success on-success}}
(protocol/init-chat chat-id))))
(handlers/register-handler-fx
::add-new-sym-key
(fn [{:keys [db] :as cofx} [_ {:keys [sym-key-id sym-key chat-id]}]]
(let [{:keys [web3]} db]
(handlers/merge-fx cofx
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
:sym-key-id sym-key-id
:topic (transport.utils/get-topic chat-id)
:chat-id chat-id}
:data-store.transport/save {:chat-id chat-id
:chat (-> (get-in db [:transport/chats chat-id])
(assoc :sym-key-id sym-key-id)
;;TODO (yenda) remove once go implements persistence
(assoc :sym-key sym-key))}}))))

View File

@ -0,0 +1,19 @@
(ns ^{:doc "Message caching API for message deduplication"}
status-im.transport.message-cache
(:refer-clojure :exclude [exists?]))
(defonce messages-set (atom #{}))
(defn init!
[messages]
(reset! messages-set (set (map :message-id messages))))
(defn add!
[message-id]
(when message-id
(swap! messages-set conj message-id)))
(defn exists?
[message-id]
(when message-id
(@messages-set message-id)))

View File

@ -0,0 +1,169 @@
(ns ^{:doc "Whisper API and events for managing keys and posting messages"}
status-im.transport.shh
(:require [taoensso.timbre :as log]
[re-frame.core :as re-frame]
[status-im.transport.utils :as transport.utils]
[status-im.transport.message.transit :as transit]))
(defn get-new-key-pair [{:keys [web3 on-success on-error]}]
(if web3
(.. web3
-shh
(newKeyPair (fn [err resp]
(if-not err
(on-success resp)
(on-error err)))))
(on-error "web3 not available.")))
(re-frame/reg-fx
:shh/get-new-key-pair
(fn [{:keys [web3 success-event error-event]}]
(get-new-key-pair {:web3 web3
:on-success #(re-frame/dispatch [success-event %])
:on-error #(re-frame/dispatch [error-event %])})))
(defn get-public-key [{:keys [web3 key-pair-id on-success on-error]}]
(if (and web3 key-pair-id)
(.. web3
-shh
(getPublicKey key-pair-id (fn [err resp]
(if-not err
(on-success resp)
(on-error err)))))
(on-error "web3 or key-pair id not available.")))
(re-frame/reg-fx
:shh/get-public-key
(fn [{:keys [web3 key-pair-id success-event error-event]}]
(get-public-key {:web3 web3
:key-pair-id key-pair-id
:on-success #(re-frame/dispatch [success-event %])
:on-error #(re-frame/dispatch [error-event %])})))
(defn generate-sym-key-from-password
[{:keys [web3 password on-success on-error]}]
(.. web3
-shh
(generateSymKeyFromPassword password (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(defn post-message
[{:keys [web3 whisper-message on-success on-error]}]
(.. web3
-shh
(post (clj->js whisper-message) (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(re-frame/reg-fx
:shh/post
(fn [{:keys [web3 message success-event error-event]
:or {error-event :protocol/send-status-message-error}}]
(post-message {:web3 web3
:whisper-message (update message :payload (comp transport.utils/from-utf8
transit/serialize))
:on-success (if success-event
#(re-frame/dispatch success-event)
#(log/debug :shh/post-success))
:on-error #(re-frame/dispatch [error-event %])})))
;; This event params contain a recipients key because it's a vector of map with public-key and topic keys.
;; the :shh/post event has public-key and topic keys at the top level of the args map.
;; This event is used to send messages to multiple recipients when you can't send it on a topic.
;; It is used for renewing keys in a private group chat, because if someone leaves/join.
;; We want to change the symmetric key but we can't do that in the group topic with the old key
;; otherwise leavers can still eavesdrop / joiners can read past history."
(re-frame/reg-fx
:shh/multi-post
(fn [{:keys [web3 message recipients success-event error-event]
:or {error-event :protocol/send-status-message-error}}]
(let [whisper-message (update message :payload (comp transport.utils/from-utf8
transit/serialize))]
(doseq [{:keys [sym-key-id topic]} recipients]
(post-message {:web3 web3
:whisper-message (assoc whisper-message
:symKeyID sym-key-id
:topic topic)
:on-success (if success-event
#(re-frame/dispatch success-event)
#(log/debug :shh/post-success))
:on-error #(re-frame/dispatch [error-event %])})))))
(defn add-sym-key
[{:keys [web3 sym-key on-success on-error]}]
(.. web3
-shh
(addSymKey sym-key (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(defn get-sym-key
[{:keys [web3 sym-key-id on-success on-error]}]
(.. web3
-shh
(getSymKey sym-key-id (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(defn new-sym-key
[{:keys [web3 on-success on-error]}]
(.. web3
-shh
(newSymKey (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(defn log-error [error]
(log/error :shh/get-new-sym-key-error error))
;;TODO (yenda) remove once go implements persistence
(re-frame/reg-fx
:shh/restore-sym-keys
(fn [{:keys [web3 transport on-success]}]
(doseq [[chat-id {:keys [sym-key]}] transport]
(add-sym-key {:web3 web3
:sym-key sym-key
:on-success (fn [sym-key-id]
(on-success chat-id sym-key sym-key-id))
:on-error log-error}))))
(re-frame/reg-fx
:shh/add-new-sym-key
(fn [{:keys [web3 sym-key on-success]}]
(add-sym-key {:web3 web3
:sym-key sym-key
:on-success (fn [sym-key-id]
(on-success sym-key sym-key-id))
:on-error log-error})))
(re-frame/reg-fx
:shh/get-new-sym-key
(fn [{:keys [web3 on-success]}]
(new-sym-key {:web3 web3
:on-success (fn [sym-key-id]
(get-sym-key {:web3 web3
:sym-key-id sym-key-id
:on-success (fn [sym-key]
(on-success sym-key sym-key-id))
:on-error log-error}))
:on-error log-error})))
(re-frame/reg-fx
:shh/generate-sym-key-from-password
(fn [{:keys [web3 password on-success]}]
(generate-sym-key-from-password {:web3 web3
:password password
:on-success (fn [sym-key-id]
(get-sym-key {:web3 web3
:sym-key-id sym-key-id
:on-success (fn [sym-key]
(on-success sym-key sym-key-id))
:on-error log-error}))
:on-error log-error})))

View File

@ -1,4 +1,5 @@
(ns status-im.protocol.web3.utils (ns ^{:doc "Utils for transport layer"}
status-im.transport.utils
(:require [cljs-time.coerce :refer [to-long]] (:require [cljs-time.coerce :refer [to-long]]
[cljs-time.core :refer [now]] [cljs-time.core :refer [now]]
[clojure.string :as string] [clojure.string :as string]
@ -13,6 +14,19 @@
(defn to-utf8 [s] (defn to-utf8 [s]
(.toUtf8 dependencies/Web3.prototype (str s))) (.toUtf8 dependencies/Web3.prototype (str s)))
(defn sha3 [s]
(.sha3 dependencies/Web3.prototype s))
(defn message-id
"Get a message-id"
[message]
(sha3 (pr-str message)))
(defn get-topic
"Get the topic of a group chat or public chat from the chat-id"
[chat-id]
(subs (sha3 chat-id) 0 10))
(defn shh [web3] (defn shh [web3]
(.-shh web3)) (.-shh web3))

View File

@ -102,7 +102,7 @@
(defn- wrap-key-fn [f] (defn- wrap-key-fn [f]
(fn [data index] (fn [data index]
{:post [(string? %)]} {:post [(some? %)]}
(f data index))) (f data index)))
(def default-separator [react/view styles/separator]) (def default-separator [react/view styles/separator])

View File

@ -1,8 +1,6 @@
(ns status-im.ui.screens.accounts.events (ns status-im.ui.screens.accounts.events
(:require [status-im.data-store.accounts :as accounts-store] (:require [re-frame.core :as re-frame]
[re-frame.core :as re-frame]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.protocol.core :as protocol]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.utils.types :refer [json->clj]] [status-im.utils.types :refer [json->clj]]
[status-im.utils.identicon :refer [identicon]] [status-im.utils.identicon :refer [identicon]]
@ -15,20 +13,13 @@
[status-im.utils.gfycat.core :refer [generate-gfy]] [status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.utils.hex :as utils.hex] [status-im.utils.hex :as utils.hex]
[status-im.constants :as constants] [status-im.constants :as constants]
status-im.ui.screens.accounts.create.navigation)) [status-im.transport.message.v1.contact :as message.contact]
[status-im.transport.message.core :as transport]
status-im.ui.screens.accounts.create.navigation
[status-im.chat.models :as chat.models]))
;;;; COFX ;;;; COFX
(re-frame/reg-cofx
:get-new-keypair!
(fn [coeffects _]
(assoc coeffects :keypair (protocol/new-keypair!))))
(re-frame/reg-cofx
::get-all-accounts
(fn [coeffects _]
(assoc coeffects :all-accounts (accounts-store/get-all))))
(re-frame/reg-cofx (re-frame/reg-cofx
::get-signing-phrase ::get-signing-phrase
(fn [coeffects _] (fn [coeffects _]
@ -41,11 +32,6 @@
;;;; FX ;;;; FX
(re-frame/reg-fx
::save-account
(fn [account]
(accounts-store/save account true)))
(re-frame/reg-fx (re-frame/reg-fx
::create-account ::create-account
(fn [password] (fn [password]
@ -53,33 +39,6 @@
password password
#(re-frame/dispatch [::account-created (json->clj %) password])))) #(re-frame/dispatch [::account-created (json->clj %) password]))))
(re-frame/reg-fx
::broadcast-account-update
(fn [{:keys [current-public-key web3 name photo-path status
updates-public-key updates-private-key]}]
(when web3
(protocol/broadcast-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}}}}))))
(re-frame/reg-fx
::send-keys-update
(fn [{:keys [web3 current-public-key contacts
updates-public-key updates-private-key]}]
(doseq [id (handlers/identities contacts)]
(protocol/update-keys!
{:web3 web3
:message {:from current-public-key
:to id
:message-id (random/id)
:payload {:keypair {:public updates-public-key
:private updates-private-key}}}}))))
;;;; Handlers ;;;; Handlers
(handlers/register-handler-fx (handlers/register-handler-fx
@ -96,8 +55,8 @@
:networks networks :networks networks
:wnode wnode :wnode wnode
:address address)] :address address)]
{:db (assoc-in db [:accounts/accounts address] enriched-account) {:db (assoc-in db [:accounts/accounts address] enriched-account)
::save-account enriched-account})) :data-store/save-account enriched-account}))
;; TODO(janherich) we have this handler here only because of the tests, refactor/improve tests ASAP ;; TODO(janherich) we have this handler here only because of the tests, refactor/improve tests ASAP
(handlers/register-handler-fx (handlers/register-handler-fx
@ -107,17 +66,16 @@
(handlers/register-handler-fx (handlers/register-handler-fx
::account-created ::account-created
[re-frame/trim-v (re-frame/inject-cofx :get-new-keypair!) [re-frame/trim-v (re-frame/inject-cofx ::get-signing-phrase) (re-frame/inject-cofx ::get-status)]
(re-frame/inject-cofx ::get-signing-phrase) (re-frame/inject-cofx ::get-status)] (fn [{:keys [signing-phrase status db] :as cofx} [{:keys [pubkey address mnemonic]} password]]
(fn [{:keys [keypair signing-phrase status db]} [{:keys [pubkey address mnemonic]} password]]
(let [normalized-address (utils.hex/normalize-hex address) (let [normalized-address (utils.hex/normalize-hex address)
account {:public-key pubkey account {:public-key pubkey
:address normalized-address :address normalized-address
:name (generate-gfy pubkey) :name (generate-gfy pubkey)
:status status :status status
:signed-up? true :signed-up? true
:updates-public-key (:public keypair) :updates-public-key "public"
:updates-private-key (:private keypair) :updates-private-key "private"
:photo-path (identicon pubkey) :photo-path (identicon pubkey)
:signing-phrase signing-phrase :signing-phrase signing-phrase
:mnemonic mnemonic :mnemonic mnemonic
@ -129,7 +87,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:load-accounts :load-accounts
[(re-frame/inject-cofx ::get-all-accounts)] [(re-frame/inject-cofx :data-store/get-all-accounts)]
(fn [{:keys [db all-accounts]} _] (fn [{:keys [db all-accounts]} _]
(let [accounts (->> all-accounts (let [accounts (->> all-accounts
(map (fn [{:keys [address] :as account}] (map (fn [{:keys [address] :as account}]
@ -147,66 +105,48 @@
(fn [{{:accounts/keys [accounts] :networks/keys [networks] :as db} :db} [_ id]] (fn [{{:accounts/keys [accounts] :networks/keys [networks] :as db} :db} [_ id]]
(let [current-account (get accounts id) (let [current-account (get accounts id)
new-account (assoc current-account :networks networks)] new-account (assoc current-account :networks networks)]
{:db (assoc-in db [:accounts/accounts id] new-account) {:db (assoc-in db [:accounts/accounts id] new-account)
::save-account new-account}))) :data-store/save-account new-account})))
(defn update-wallet-settings [{:accounts/keys [current-account-id accounts] :as db} settings] (defn update-wallet-settings [{:accounts/keys [current-account-id accounts] :as db} settings]
(let [new-account (-> (get accounts current-account-id) (let [new-account (-> (get accounts current-account-id)
(assoc :settings settings))] (assoc :settings settings))]
{:db (assoc-in db [:accounts/accounts current-account-id] new-account) {:db (assoc-in db [:accounts/accounts current-account-id] new-account)
::save-account new-account})) :data-store/save-account new-account}))
(defn account-update (defn account-update
"Takes effects (containing :db) + new account fields, adds all effects necessary for account update." "Takes effects (containing :db) + new account fields, adds all effects necessary for account update.
[{{:accounts/keys [accounts current-account-id] :as db} :db :as fx} new-account-fields] Optionally, one can specify event to be dispatched after fields are persisted."
(let [current-account (get accounts current-account-id) ([new-account-fields cofx]
new-account (merge current-account new-account-fields) (account-update new-account-fields nil cofx))
broadcast-fields [:name :photo-path :status ([new-account-fields after-update-event {{:accounts/keys [accounts current-account-id] :as db} :db :as cofx}]
:updates-public-key :updates-private-key]] (let [current-account (get accounts current-account-id)
(cond-> fx new-account (merge current-account new-account-fields)
true fx {:db (assoc-in db [:accounts/accounts current-account-id] new-account)
(assoc-in [:db :accounts/accounts current-account-id] new-account) :data-store/save-account (assoc new-account :after-update-event after-update-event)}
true {:keys [name photo-path]} new-account]
(assoc ::save-account new-account) (if (or (:name new-account-fields) (:photo-path new-account-fields))
(handlers/merge-fx cofx fx (transport/send (message.contact/ContactUpdate. name photo-path) nil))
(seq (clojure.set/intersection (set (keys new-account-fields)) (set broadcast-fields))) fx))))
(assoc ::broadcast-account-update (merge (select-keys db [:current-public-key :web3])
(select-keys new-account broadcast-fields))))))
(handlers/register-handler-fx
:account-update-keys
[(re-frame/inject-cofx :get-new-keypair!)]
(fn [{:keys [db keypair now]} _]
(let [{:accounts/keys [accounts current-account-id]} db
{:keys [public private]} keypair
current-account (get accounts current-account-id)
new-account (merge current-account {:updates-public-key public
:updates-private-key private
:last-updated now})]
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
::save-account new-account
::send-keys-update (merge
(select-keys db [:web3 :current-public-key :contacts])
(select-keys new-account [:updates-public-key
:updates-private-key]))})))
(handlers/register-handler-fx (handlers/register-handler-fx
:send-account-update-if-needed :send-account-update-if-needed
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db now :now} _] (fn [{{:accounts/keys [accounts current-account-id] :as db} :db now :now :as cofx} _]
(let [{:keys [last-updated]} (get accounts current-account-id) (let [{:keys [last-updated]} (get accounts current-account-id)
needs-update? (> (- now last-updated) time/week)] needs-update? (> (- now last-updated) time/week)]
(log/info "Need to send account-update: " needs-update?) (log/info "Need to send account-update: " needs-update?)
(when needs-update? (when needs-update?
;; TODO(janherich): this is very strange and misleading, need to figure out why it'd necessary to update ;; TODO(janherich): this is very strange and misleading, need to figure out why it'd necessary to update
;; account with network update when last update was more then week ago ;; account with network update when last update was more then week ago
(account-update {:db db} nil))))) (account-update nil cofx)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:account-set-name :account-set-name
(fn [{{:accounts/keys [create] :as db} :db} _] (fn [{{:accounts/keys [create] :as db} :db :as cofx} _]
(-> {:db (assoc-in db [:accounts/create :show-welcome?] true) (handlers/merge-fx cofx
:dispatch [:navigate-to-clean :usage-data [:account-finalized]]} {:db (assoc-in db [:accounts/create :show-welcome?] true)
(account-update {:name (:name create)})))) :dispatch [:navigate-to-clean :usage-data [:account-finalized]]}
(account-update {:name (:name create)}))))
(handlers/register-handler-fx (handlers/register-handler-fx
:account-finalized :account-finalized
@ -222,8 +162,8 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:update-sign-in-time :update-sign-in-time
(fn [{db :db now :now} _] (fn [{db :db now :now :as cofx} _]
(account-update {:db db} {:last-sign-in now}))) (account-update {:last-sign-in now} cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:reset-account-creation :reset-account-creation
@ -232,5 +172,5 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:switch-dev-mode :switch-dev-mode
(fn [{db :db} [_ dev-mode]] (fn [cofx [_ dev-mode]]
(account-update {:db db} {:dev-mode? dev-mode}))) (account-update {:dev-mode? dev-mode} cofx)))

View File

@ -27,7 +27,6 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:account-recovered :account-recovered
[(re-frame/inject-cofx :get-new-keypair!)]
(fn [{:keys [db keypair]} [_ result password]] (fn [{:keys [db keypair]} [_ result password]]
(let [data (types/json->clj result) (let [data (types/json->clj result)
public-key (:pubkey data) public-key (:pubkey data)
@ -38,14 +37,15 @@
:address address :address address
:name (gfycat/generate-gfy public-key) :name (gfycat/generate-gfy public-key)
:photo-path (identicon/identicon public-key) :photo-path (identicon/identicon public-key)
:updates-public-key public ;;TODO remove those
:updates-private-key private :updates-public-key "public"
:updates-private-key "private"
:mnemonic "" :mnemonic ""
:signed-up? true :signed-up? true
:signing-phrase phrase :signing-phrase phrase
:settings constants/default-account-settings}] :settings constants/default-account-settings}]
(when-not (string/blank? public-key) (when-not (string/blank? public-key)
(-> db (-> db
(accounts-events/add-account account) (accounts-events/add-account account)
(assoc :dispatch [:login-account address password]) (assoc :dispatch [:login-account address password])
(assoc :dispatch-later [{:ms 2000 :dispatch [:navigate-to :usage-data]}])))))) (assoc :dispatch-later [{:ms 2000 :dispatch [:navigate-to :usage-data]}]))))))

View File

@ -21,12 +21,13 @@
:icon-opts {:color colors/blue} :icon-opts {:color colors/blue}
:on-press #(re-frame/dispatch [:navigate-to :new-chat])}] :on-press #(re-frame/dispatch [:navigate-to :new-chat])}]
[action-button/action-separator] [action-button/action-separator]
[action-button/action-button ;; TODO temporary removal before everything is fixed in group chats
{:label (i18n/label :t/start-group-chat) #_[action-button/action-button
:accessibility-label :start-group-chat-button {:label (i18n/label :t/start-group-chat)
:icon :icons/contacts :accessibility-label :start-group-chat-button
:icon-opts {:color colors/blue} :icon :icons/contacts
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}] :icon-opts {:color colors/blue}
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}]
[action-button/action-separator] [action-button/action-separator]
[action-button/action-button [action-button/action-button
{:label (i18n/label :t/new-public-group-chat) {:label (i18n/label :t/new-public-group-chat)

View File

@ -1,33 +1,17 @@
(ns status-im.ui.screens.browser.events (ns status-im.ui.screens.browser.events
(:require status-im.ui.screens.browser.navigation (:require status-im.ui.screens.browser.navigation
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.data-store.browser :as browser-store]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.i18n :as i18n])) [status-im.i18n :as i18n]))
(re-frame/reg-cofx
:all-stored-browsers
(fn [cofx _]
(assoc cofx :all-stored-browsers (browser-store/get-all))))
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-browsers :initialize-browsers
[(re-frame/inject-cofx :all-stored-browsers)] [(re-frame/inject-cofx :data-store/all-browsers)]
(fn [{:keys [db all-stored-browsers]} _] (fn [{:keys [db all-stored-browsers]} _]
(let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))] (let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))]
{:db (assoc db :browser/browsers browsers)}))) {:db (assoc db :browser/browsers browsers)})))
(re-frame/reg-fx
:save-browser
(fn [browser]
(browser-store/save browser)))
(re-frame/reg-fx
:remove-browser
(fn [browser-id]
(browser-store/delete browser-id)))
(defn match-url [url] (defn match-url [url]
(str (when (and url (not (re-find #"^[a-zA-Z-_]+:/" url))) "http://") url)) (str (when (and url (not (re-find #"^[a-zA-Z-_]+:/" url))) "http://") url))

View File

@ -0,0 +1,64 @@
(ns status-im.ui.screens.contacts.core
(:require [re-frame.core :as re-frame] [status-im.utils.handlers :as handlers]
[status-im.data-store.messages :as data-store.messages]
[status-im.chat.models :as chat.models]
[status-im.constants :as constants]))
(defn receive-contact-request
[public-key
{:keys [name profile-image address fcm-token]}
{{:contacts/keys [contacts] :as db} :db :as cofx}]
(when-not (get contacts public-key)
(let [contact-props {:whisper-identity public-key
:public-key public-key
:address address
:photo-path profile-image
:name name
:fcm-token fcm-token
:pending? true}
chat-props {:name name
:chat-id public-key
:contact-info (prn-str contact-props)}]
(handlers/merge-fx cofx
{:db (update-in db [:contacts/contacts public-key] merge contact-props)
:data-store/save-contact contact-props}
(chat.models/add-chat public-key chat-props)))))
(defn receive-contact-request-confirmation
[public-key {:keys [name profile-image address fcm-token]}
{{:contacts/keys [contacts] :as db} :db :as cofx}]
(when-let [contact (get contacts public-key)]
(let [contact-props {:whisper-identity public-key
:public-key public-key
:address address
:photo-path profile-image
:name name
:fcm-token fcm-token}
chat-props {:name name
:chat-id public-key}]
(handlers/merge-fx cofx
{:db (update-in db [:contacts/contacts public-key] merge contact-props)
:data-store/save-contact contact-props}
(chat.models/upsert-chat chat-props)))))
(defn- update-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
(when (get-in db [:contacts/contacts whisper-identity])
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
:data-store/save-contact contact}))
(defn receive-contact-update [chat-id public-key {:keys [name profile-image]} {:keys [db now] :as cofx}]
(let [{:keys [chats current-public-key]} db]
(when (not= current-public-key public-key)
(let [prev-last-updated (get-in db [:contacts/contacts public-key :last-updated])]
(when (<= prev-last-updated now)
(let [contact {:whisper-identity public-key
:name name
:photo-path profile-image
:last-updated now}]
(if (chats public-key)
(handlers/merge-fx cofx
(update-contact contact)
(chat.models/update-chat {:chat-id chat-id
:name name}))
(update-contact contact cofx))))))))

View File

@ -28,7 +28,7 @@
(spec/def :contact/dapp? boolean?) (spec/def :contact/dapp? boolean?)
(spec/def :contact/dapp-url (spec/nilable string?)) (spec/def :contact/dapp-url (spec/nilable string?))
(spec/def :contact/dapp-hash (spec/nilable int?)) (spec/def :contact/dapp-hash (spec/nilable int?))
(spec/def :contact/bot-url (spec/nilable string?)) (spec/def :contact/bot-url (spec/nilable string?))
(spec/def :contact/command (spec/nilable (spec/map-of int? map?))) (spec/def :contact/command (spec/nilable (spec/map-of int? map?)))
(spec/def :contact/response (spec/nilable (spec/map-of int? map?))) (spec/def :contact/response (spec/nilable (spec/map-of int? map?)))
(spec/def :contact/jail-loaded? (spec/nilable boolean?)) (spec/def :contact/jail-loaded? (spec/nilable boolean?))
@ -48,13 +48,13 @@
:contact/status :contact/status
:contact/last-updated :contact/last-updated
:contact/last-online :contact/last-online
:contact/pending? :contact/pending?
:contact/hide-contact? :contact/hide-contact?
:contact/unremovable? :contact/unremovable?
:contact/dapp? :contact/dapp?
:contact/dapp-url :contact/dapp-url
:contact/dapp-hash :contact/dapp-hash
:contact/bot-url :contact/bot-url
:contact/jail-loaded? :contact/jail-loaded?
:contact/jail-loaded-events :contact/jail-loaded-events
:contact/command :contact/command
@ -84,4 +84,4 @@
;used in modal list (for example for wallet) ;used in modal list (for example for wallet)
(spec/def :contacts/click-action (spec/nilable #{:send :request})) (spec/def :contacts/click-action (spec/nilable #{:send :request}))
;used in modal list (for example for wallet) ;used in modal list (for example for wallet)
(spec/def :contacts/click-params (spec/nilable map?)) (spec/def :contacts/click-params (spec/nilable map?))

View File

@ -1,120 +1,42 @@
(ns status-im.ui.screens.contacts.events (ns status-im.ui.screens.contacts.events
(:require [re-frame.core :refer [dispatch trim-v reg-fx reg-cofx inject-cofx]] (:require [cljs.reader :as reader]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]] [re-frame.core :as re-frame]
[status-im.data-store.contacts :as contacts] [status-im.utils.handlers :as handlers]
[clojure.string :as s]
[status-im.protocol.core :as protocol]
[status-im.utils.contacts :as utils.contacts] [status-im.utils.contacts :as utils.contacts]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[cljs.reader :refer [read-string]]
[status-im.utils.js-resources :as js-res] [status-im.utils.js-resources :as js-res]
[status-im.i18n :refer [label]] [status-im.i18n :refer [label]]
[taoensso.timbre :as log]
[status-im.utils.js-resources :as js-res]
[status-im.utils.datetime :as datetime]
[status-im.utils.identicon :as identicon]
[status-im.utils.gfycat.core :as gfycat.core]
[status-im.ui.screens.contacts.navigation] [status-im.ui.screens.contacts.navigation]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.chat.console :as console-chat] [status-im.chat.console :as console-chat]
[status-im.chat.events :as chat.events]
[status-im.chat.models :as chat.models]
[status-im.commands.events.loading :as loading-events] [status-im.commands.events.loading :as loading-events]
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db] [status-im.js-dependencies :as js-dependencies]
[status-im.utils.datetime :as datetime] [status-im.transport.message.core :as transport]
[status-im.js-dependencies :as js-dependencies])) [status-im.transport.message.v1.contact :as message.v1.contact]
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]))
;;;; COFX ;;;; COFX
(reg-cofx (re-frame/reg-cofx
:get-all-contacts ::get-default-contacts-and-groups
(fn [coeffects _] (fn [coeffects _]
(assoc coeffects :all-contacts (contacts/get-all)))) (assoc coeffects
:default-contacts js-res/default-contacts
(reg-cofx :default-groups js-res/default-contact-groups)))
::get-default-contacts-and-groups
(fn [coeffects _]
(assoc coeffects
:default-contacts js-res/default-contacts
:default-groups js-res/default-contact-groups)))
;;;; FX
(reg-fx
::watch-contact
(fn [{:keys [web3 whisper-identity 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])})))
(reg-fx
::stop-watching-contact
(fn [{:keys [web3 whisper-identity]}]
(protocol/stop-watching-user! {:web3 web3
:identity whisper-identity})))
(reg-fx
::send-contact-request-fx
(fn [{:keys [web3 current-public-key name whisper-identity
photo-path current-account-id status fcm-token
updates-public-key updates-private-key] :as params}]
(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
:status status
:fcm-token fcm-token}
:keypair {:public updates-public-key
:private updates-private-key}
:timestamp (datetime/timestamp)}}})))
(reg-fx
::reset-pending-messages
(fn [from]
(protocol/reset-pending-messages! from)))
(reg-fx
::save-contact
(fn [contact]
(contacts/save contact)))
(reg-fx
:save-all-contacts
(fn [new-contacts]
(contacts/save-all new-contacts)))
(reg-fx
::delete-contact
(fn [contact]
(contacts/delete contact)))
(defn- contact-name [contact]
(->> contact
((juxt :givenName :middleName :familyName))
(remove s/blank?)
(s/join " ")))
;;;; Handlers ;;;; Handlers
(defn watch-contact (defn- update-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
"Takes effects map, adds effects necessary to start watching contact" (when (get-in db [:contacts/contacts whisper-identity])
[{:keys [db] :as fx} {:keys [public-key private-key] :as contact}] {:db (update-in db [:contacts/contacts whisper-identity] merge contact)
(cond-> fx :data-store/save-contact contact}))
(and public-key private-key)
(assoc ::watch-contact (merge
(select-keys db [:web3])
(select-keys contact [:whisper-identity :public-key :private-key])))))
(register-handler-fx
:watch-contact
(fn [{:keys [db]} [_ contact]]
(watch-contact {:db db} contact)))
(register-handler-fx
:update-contact!
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
(when (get-in db [:contacts/contacts whisper-identity])
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
::save-contact contact})))
(defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}] (defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending? (let [{old-pending :pending?
@ -183,9 +105,9 @@
(:group-id (first contacts)) (:group-id (first contacts))
(mapv :whisper-identity contacts)]))) (mapv :whisper-identity contacts)])))
(register-handler-fx (handlers/register-handler-fx
:load-default-contacts! :load-default-contacts!
[(inject-cofx ::get-default-contacts-and-groups)] [(re-frame/inject-cofx ::get-default-contacts-and-groups)]
(fn [{:keys [db default-contacts default-groups]} _] (fn [{:keys [db default-contacts default-groups]} _]
(let [{:contacts/keys [contacts] :group/keys [contact-groups]} db] (let [{:contacts/keys [contacts] :group/keys [contact-groups]} db]
{:dispatch-n (concat {:dispatch-n (concat
@ -194,21 +116,17 @@
(prepare-add-chat-events contacts default-contacts) (prepare-add-chat-events contacts default-contacts)
(prepare-add-contacts-to-groups-events contacts default-contacts))}))) (prepare-add-contacts-to-groups-events contacts default-contacts))})))
(register-handler-fx (handlers/register-handler-fx
:load-contacts :load-contacts
[(inject-cofx :get-all-contacts)] [(re-frame/inject-cofx :data-store/get-all-contacts)]
(fn [{:keys [db all-contacts]} _] (fn [{:keys [db all-contacts]} _]
(let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts) (let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts)
contacts (into {} contacts-list)] contacts (into {} contacts-list)]
{:db (update db :contacts/contacts #(merge contacts %))}))) {:db (update db :contacts/contacts #(merge contacts %))})))
;; TODO (yenda) this mapv was dispatching useless events, fixed but is it necessary if
;; it was dispatching useless events before with nil
;;:dispatch-n (mapv (fn [[_ contact]] [:watch-contact contact]) contacts)
(handlers/register-handler-fx
(register-handler-fx
:add-contacts :add-contacts
[(inject-cofx :get-local-storage-data)] [(re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [{:keys [db] :as cofx} [_ new-contacts]] (fn [{:keys [db] :as cofx} [_ new-contacts]]
(let [{:contacts/keys [contacts]} db (let [{:contacts/keys [contacts]} db
new-contacts' (->> new-contacts new-contacts' (->> new-contacts
@ -217,158 +135,107 @@
;;(remove #(identities (:whisper-identity %))) ;;(remove #(identities (:whisper-identity %)))
(map #(vector (:whisper-identity %) %)) (map #(vector (:whisper-identity %) %))
(into {})) (into {}))
fx {:db (update db :contacts/contacts merge new-contacts') fx {:db (update db :contacts/contacts merge new-contacts')
:save-all-contacts (vals new-contacts')}] :data-store/save-contacts (vals new-contacts')}]
(transduce (map second) (transduce (map second)
(completing (partial loading-events/load-commands (assoc cofx :db (:db fx)))) (completing (partial loading-events/load-commands (assoc cofx :db (:db fx))))
fx fx
new-contacts')))) new-contacts'))))
(register-handler-db (defn- add-new-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
:remove-contacts-click-handler (let [new-contact (assoc contact :pending? false)]
(fn [db _] {:db (-> db
(dissoc db (update-in [:contacts/contacts whisper-identity] merge new-contact)
:contacts/click-handler (assoc-in [:contacts/new-identity] ""))
:contacts/click-action))) :data-store/save-contact new-contact}))
(defn send-contact-request (defn- own-info [{:accounts/keys [accounts current-account-id] :as db}]
"Takes effects map, adds effects necessary to send a contact request" (let [{:keys [name photo-path address]} (get accounts current-account-id)
[{{:accounts/keys [accounts current-account-id] :as db} :db :as fx} contact]
(let [current-account (get accounts current-account-id)
fcm-token (get-in db [:notifications :fcm-token])] fcm-token (get-in db [:notifications :fcm-token])]
(assoc fx {:name name
::send-contact-request-fx :profile-image photo-path
(merge :address address
(select-keys db [:current-public-key :web3]) :fcm-token fcm-token}))
{:current-account-id current-account-id :fcm-token fcm-token}
(select-keys contact [:whisper-identity])
(select-keys current-account [:name :photo-path :status
:updates-public-key :updates-private-key])))))
(defn add-new-contact [fx {:keys [whisper-identity] :as contact}] (defn send-contact-request [{:keys [whisper-identity pending? dapp?] :as contact} {:keys [db] :as cofx}]
(-> fx (when-not dapp?
(send-contact-request contact) (if pending?
(update-in [:db :contacts/contacts whisper-identity] merge contact) (transport/send (message.v1.contact/map->ContactRequestConfirmed (own-info db)) whisper-identity cofx)
(assoc-in [:db :contacts/new-identity] "") (transport/send (message.v1.contact/map->ContactRequest (own-info db)) whisper-identity cofx))))
(assoc ::save-contact contact)))
(defn add-contact [{:keys [db] :as fx} chat-or-whisper-id] (defn- build-contact [whisper-id {{:keys [chats] :contacts/keys [contacts]} :db}]
(let [{:keys [chats] :contacts/keys [contacts]} db (-> (if-let [contact-info (get-in chats [whisper-id :contact-info])]
contact (if-let [contact-info (get-in chats [chat-or-whisper-id :contact-info])] (reader/read-string contact-info)
(read-string contact-info) (or (get contacts whisper-id)
(or (get contacts chat-or-whisper-id) (utils.contacts/whisper-id->new-contact whisper-id)))
(utils.contacts/whisper-id->new-contact chat-or-whisper-id))) (assoc :address (public-key->address whisper-id))))
contact' (assoc contact
:address (public-key->address chat-or-whisper-id)
:pending? false)]
(-> fx
(watch-contact contact')
(add-new-contact contact'))))
(defn add-contact-and-open-chat [fx whisper-id] (defn add-contact [whisper-id {:keys [db] :as cofx}]
(-> fx (let [contact (build-contact whisper-id cofx)]
(add-contact whisper-id) (handlers/merge-fx cofx
(update :db #(navigation/navigate-to-clean % :home)) (add-new-contact contact)
(assoc :dispatch [:start-chat whisper-id {:navigation-replace? true}]))) (send-contact-request contact))))
(register-handler-fx (defn add-contact-and-open-chat [whisper-id cofx]
(handlers/merge-fx cofx
(navigation/navigate-to-clean :home)
(add-contact whisper-id)
(chat.events/start-chat whisper-id {})))
(handlers/register-handler-fx
:add-contact :add-contact
(fn [{:keys [db]} [_ chat-or-whisper-id]] [(re-frame/inject-cofx :random-id)]
(add-contact {:db db} chat-or-whisper-id))) (fn [cofx [_ whisper-id]]
(add-contact whisper-id cofx)))
(register-handler-fx (handlers/register-handler-fx
:add-contact-and-open-chat
(fn [{:keys [db]} [_ whisper-id]]
(add-contact-and-open-chat {:db db} whisper-id)))
(register-handler-fx
:set-contact-identity-from-qr :set-contact-identity-from-qr
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db} [_ _ contact-identity]] [(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :data-store/get-chat)]
(let [current-account (get accounts current-account-id)] (fn [{{:accounts/keys [accounts current-account-id] :as db} :db :as cofx} [_ _ contact-identity]]
(cond-> {:db (assoc db :contacts/new-identity contact-identity)} (let [current-account (get accounts current-account-id)
(not (new-chat.db/validate-pub-key contact-identity current-account)) fx {:db (assoc db :contacts/new-identity contact-identity)}]
(assoc :dispatch [:add-contact-handler]))))) (if (new-chat.db/validate-pub-key contact-identity current-account)
fx
(handlers/merge-fx cofx
fx
(add-contact-and-open-chat contact-identity))))))
(register-handler-fx (handlers/register-handler-fx
:contact-update-received
(fn [{:keys [db]} [_ {:keys [from payload]}]]
(let [{:keys [chats current-public-key]} db]
(when (not= current-public-key from)
(let [{:keys [content timestamp]} payload
{:keys [status name profile-image]} (:profile content)
prev-last-updated (get-in db [:contacts/contacts from :last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:name name
:photo-path profile-image
:status status
:last-updated timestamp}]
{:dispatch-n (concat [[:update-contact! contact]]
(when (chats from)
[[:update-chat! {:chat-id from
:name name}]]))})))))))
(register-handler-fx
:update-keys-received
(fn [{:keys [db]} [_ {:keys [from payload]}]]
(let [{{:keys [public private]} :keypair
timestamp :timestamp} payload
prev-last-updated (get-in db [:contacts/contacts from :keys-last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:public-key public
:private-key private
:keys-last-updated timestamp}]
{:dispatch [:update-contact! contact]})))))
(register-handler-fx
:contact-online-received
(fn [{:keys [db]} [_ {:keys [from]
{{:keys [timestamp]} :content} :payload}]]
(let [prev-last-online (get-in db [:contacts/contacts from :last-online])]
(when (and timestamp (< prev-last-online timestamp))
{::reset-pending-messages from
:dispatch [:update-contact! {:whisper-identity from
:last-online timestamp}]}))))
(register-handler-fx
:hide-contact :hide-contact
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]] (fn [cofx [_ {:keys [whisper-identity] :as contact}]]
{::stop-watching-contact (merge (update-contact {:whisper-identity whisper-identity
(select-keys db [:web3]) :pending? true}
(select-keys contact [:whisper-identity])) cofx)))
:dispatch-n [[:update-contact! {:whisper-identity whisper-identity
:pending? true}]
[:account-update-keys]]}))
;;used only by status-dev-cli ;;used only by status-dev-cli
(register-handler-fx (handlers/register-handler-fx
:remove-contact :remove-contact
(fn [{:keys [db]} [_ whisper-identity pred]] (fn [{:keys [db]} [_ whisper-identity]]
(let [contact (get-in db [:contacts/contacts whisper-identity])] (when-let [contact (get-in db [:contacts/contacts whisper-identity])]
(when (and contact (pred contact)) {:db (update db :contacts/contacts dissoc whisper-identity)
{:db (update db :contacts/contacts dissoc whisper-identity) :data-store/delete-contact contact})))
::delete-contact contact}))))
(register-handler-fx (handlers/register-handler-db
:open-contact-toggle-list :open-contact-toggle-list
(fn [{:keys [db]} [_ group-type]] (fn [db [_ group-type]]
{:db (-> db (-> (assoc db
(assoc :group/group-type group-type :group/group-type group-type
:group/selected-contacts #{} :group/selected-contacts #{}
:new-chat-name "") :new-chat-name "")
(navigation/navigate-to :contact-toggle-list))})) (navigation/navigate-to :contact-toggle-list))))
(register-handler-fx (handlers/register-handler-fx
:open-chat-with-contact :open-chat-with-contact
(fn [{:keys [db]} [_ {:keys [whisper-identity dapp?] :as contact}]] [(re-frame/inject-cofx :random-id)]
(-> {:db db} (fn [{:keys [db] :as cofx} [_ {:keys [whisper-identity] :as contact}]]
(cond-> (when-not dapp?) (send-contact-request contact)) (handlers/merge-fx cofx
(update :db #(navigation/navigate-to-clean % :home)) (navigation/navigate-to-clean :home)
(assoc :dispatch [:start-chat whisper-identity])))) (add-contact whisper-identity)
(chat.events/start-chat whisper-identity {}))))
(register-handler-fx (handlers/register-handler-fx
:add-contact-handler :add-contact-handler
(fn [{{:contacts/keys [new-identity] :as db} :db}] [(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :data-store/get-chat)]
(fn [{{:contacts/keys [new-identity] :as db} :db :as cofx} _]
(when (seq new-identity) (when (seq new-identity)
(add-contact-and-open-chat {:db db} new-identity)))) (add-contact-and-open-chat new-identity cofx))))

View File

@ -3,6 +3,7 @@
(:require [cljs.spec.alpha :as spec] (:require [cljs.spec.alpha :as spec]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
status-im.transport.db
status-im.ui.screens.accounts.db status-im.ui.screens.accounts.db
status-im.ui.screens.contacts.db status-im.ui.screens.contacts.db
status-im.ui.screens.qr-scanner.db status-im.ui.screens.qr-scanner.db
@ -45,6 +46,7 @@
:inbox/topic constants/inbox-topic :inbox/topic constants/inbox-topic
:inbox/password constants/inbox-password :inbox/password constants/inbox-password
:my-profile/editing? false :my-profile/editing? false
:transport/chats {}
:desktop/desktop {:tab-view-id :home}}) :desktop/desktop {:tab-view-id :home}})
;;;;GLOBAL ;;;;GLOBAL
@ -168,6 +170,8 @@
:browser/options :browser/options
:new/open-dapp :new/open-dapp
:navigation/screen-params :navigation/screen-params
:transport/chats
:transport/discovery-filter
:desktop/desktop] :desktop/desktop]
:opt-un :opt-un
[::current-public-key [::current-public-key

View File

@ -1,6 +1,7 @@
(ns status-im.ui.screens.desktop.main.tabs.profile.views (ns status-im.ui.screens.desktop.main.tabs.profile.views
(:require-macros [status-im.utils.views :as views]) (:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.components.react :as react] (:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.user.views :as profile])) [status-im.ui.screens.profile.user.views :as profile]))
(defn profile-badge [{:keys [name]}] (defn profile-badge [{:keys [name]}]
@ -34,7 +35,7 @@
[react/view [react/view
[my-profile-info current-account]] [my-profile-info current-account]]
[react/view {:style {:height 1 :background-color "#e8ebec" :margin-horizontal 16}}] [react/view {:style {:height 1 :background-color "#e8ebec" :margin-horizontal 16}}]
[react/touchable-highlight {:on-press #(profile/navigate-to-accounts false) [react/touchable-highlight {:on-press #(re-frame/dispatch [:logout])
:style {:margin-top 60}} :style {:margin-top 60}}
[react/view [react/view
[react/text {:style {:color :red}} "Log out"]]]])) [react/text {:style {:color :red}} "Log out"]]]]))

View File

@ -1,10 +1,11 @@
(ns status-im.ui.screens.events (ns status-im.ui.screens.events
(:require status-im.bots.events (:require status-im.bots.events
status-im.chat.handlers status-im.chat.events
status-im.commands.handlers.jail status-im.commands.handlers.jail
status-im.commands.events.loading status-im.commands.events.loading
status-im.commands.handlers.debug status-im.commands.handlers.debug
status-im.network.events status-im.network.events
status-im.transport.handlers
status-im.protocol.handlers status-im.protocol.handlers
status-im.ui.screens.accounts.events status-im.ui.screens.accounts.events
status-im.ui.screens.accounts.login.events status-im.ui.screens.accounts.login.events
@ -31,6 +32,7 @@
[status-im.data-store.core :as data-store] [status-im.data-store.core :as data-store]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.js-dependencies :as dependencies] [status-im.js-dependencies :as dependencies]
[status-im.transport.core :as transport]
[status-im.ui.screens.db :refer [app-db]] [status-im.ui.screens.db :refer [app-db]]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
@ -50,7 +52,7 @@
;;;; Helper fns ;;;; Helper fns
(defn- call-jail-function (defn- call-jail-function
[{:keys [chat-id function callback-events-creator] :as opts}] [{:keys [chat-id function callback-event-creator] :as opts}]
(let [path [:functions function] (let [path [:functions function]
params (select-keys opts [:parameters :context])] params (select-keys opts [:parameters :context])]
(status/call-jail (status/call-jail
@ -58,12 +60,11 @@
:path path :path path
:params params :params params
:callback (fn [jail-response] :callback (fn [jail-response]
(doseq [event (if callback-events-creator (when-let [event (if callback-event-creator
(callback-events-creator jail-response) (callback-event-creator jail-response)
[[:chat-received-message/bot-response [:chat-received-message/bot-response
{:chat-id chat-id} {:chat-id chat-id}
jail-response]]) jail-response])]
:when event]
(re-frame/dispatch event)))}))) (re-frame/dispatch event)))})))
;;;; COFX ;;;; COFX
@ -89,15 +90,14 @@
(re-frame/reg-fx (re-frame/reg-fx
:call-jail :call-jail
(fn [{:keys [callback-events-creator] :as opts}] (fn [{:keys [callback-event-creator] :as opts}]
(status/call-jail (status/call-jail
(-> opts (-> opts
(dissoc :callback-events-creator) (dissoc :callback-event-creator)
(assoc :callback (assoc :callback
(fn [jail-response] (fn [jail-response]
(when callback-events-creator (when-let [event (callback-event-creator jail-response)]
(doseq [event (callback-events-creator jail-response)] (re-frame/dispatch event))))))))
(re-frame/dispatch event)))))))))
(re-frame/reg-fx (re-frame/reg-fx
:call-jail-function :call-jail-function
@ -168,7 +168,7 @@
(re-frame/reg-fx (re-frame/reg-fx
::status-module-initialized-fx ::status-module-initialized-fx
(fn [] (fn [_]
(status/module-initialized!))) (status/module-initialized!)))
(re-frame/reg-fx (re-frame/reg-fx
@ -183,7 +183,7 @@
(re-frame/reg-fx (re-frame/reg-fx
::testfairy-alert ::testfairy-alert
(fn [] (fn [_]
(when config/testfairy-enabled? (when config/testfairy-enabled?
(utils/show-popup (utils/show-popup
(i18n/label :testfairy-title) (i18n/label :testfairy-title)
@ -191,7 +191,7 @@
(re-frame/reg-fx (re-frame/reg-fx
::get-fcm-token-fx ::get-fcm-token-fx
(fn [] (fn [_]
(notifications/get-fcm-token))) (notifications/get-fcm-token)))
(re-frame/reg-fx (re-frame/reg-fx
@ -207,7 +207,8 @@
(re-frame/reg-fx (re-frame/reg-fx
:close-application :close-application
(fn [] (status/close-application))) (fn [_]
(status/close-application)))
;;;; Handlers ;;;; Handlers
@ -232,6 +233,20 @@
[:initialize-crypt] [:initialize-crypt]
[:initialize-geth]]})) [:initialize-geth]]}))
(handlers/register-handler-fx
:logout
(fn [{:keys [db] :as cofx} _]
(let [{:transport/keys [chats] :keys [current-account-id]} db
sharing-usage-data? (get-in db [:accounts/accounts current-account-id :sharing-usage-data?])]
(handlers/merge-fx cofx
{:dispatch-n (concat [[:initialize-db]
[:load-accounts]
[:listen-to-network-status]
[:navigate-to :accounts]]
(when sharing-usage-data?
[[:unregister-mixpanel-tracking]]))}
(transport/stop-whisper)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-db :initialize-db
(fn [{{:keys [status-module-initialized? status-node-started? (fn [{{:keys [status-module-initialized? status-node-started?
@ -280,7 +295,6 @@
:initialize-account :initialize-account
(fn [_ [_ address events-after]] (fn [_ [_ address events-after]]
{:dispatch-n (cond-> [[:initialize-account-db address] {:dispatch-n (cond-> [[:initialize-account-db address]
[:load-processed-messages]
[:initialize-protocol address] [:initialize-protocol address]
[:initialize-sync-listener] [:initialize-sync-listener]
[:initialize-chats] [:initialize-chats]

View File

@ -1,132 +1,11 @@
(ns status-im.ui.screens.group.chat-settings.events (ns status-im.ui.screens.group.chat-settings.events
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.data-store.chats :as chats]
[status-im.data-store.contacts :as contacts]
[status-im.data-store.messages :as messages]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.protocol.core :as protocol] [status-im.chat.models.message :as models.message]
[status-im.utils.handlers :as handlers] [status-im.transport.message.v1.group-chat :as group-chat]
[status-im.utils.random :as random])) [status-im.transport.message.core :as transport]
[status-im.utils.handlers :as handlers]))
;;;; FX
(re-frame/reg-fx
::save-chat-property
(fn [[current-chat-id property-name value]]
(chats/save-property current-chat-id property-name value)))
(re-frame/reg-fx
::add-members-to-chat
(fn [{:keys [current-chat-id selected-participants]}]
(chats/add-contacts current-chat-id selected-participants)))
(re-frame/reg-fx
::remove-members-from-chat
(fn [[current-chat-id participants]]
(chats/remove-contacts current-chat-id participants)))
(defn system-message [message-id content]
{:from "system"
:message-id message-id
:content content
:content-type constants/text-content-type})
(defn removed-participant-message [chat-id identity contact-name]
(let [message-text (str "You've removed " (or contact-name identity))]
(-> (system-message (random/id) message-text)
(assoc :chat-id chat-id)
(messages/save))))
(re-frame/reg-fx
::create-removing-messages
(fn [{:keys [current-chat-id participants contacts/contacts]}]
(doseq [participant participants]
(let [contact-name (get-in contacts [participant :name])]
(removed-participant-message current-chat-id participant contact-name)))))
(re-frame/reg-fx
::notify-about-new-members
(fn [{:keys [current-chat-id selected-participants
current-public-key chats web3]}]
(let [{:keys [name contacts]} (chats current-chat-id)
identities (map :identity contacts)
{:keys [public private]
:as new-keypair} (protocol/new-keypair!)
group-message {:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key}
:message {:from current-public-key
:message-id (random/id)}}]
(re-frame/dispatch [:update-chat! {:chat-id current-chat-id
:public-key public
:private-key private}])
(protocol/start-watching-group! {:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(re-frame/dispatch [:incoming-message %1 %2])})
(protocol/invite-to-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities selected-participants)))
(protocol/update-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities identities)))
(doseq [identity selected-participants]
(protocol/add-to-group! {:web3 web3
:group-id current-chat-id
:identity identity
:keypair new-keypair
:message {:from current-public-key
:message-id (random/id)}})))))
(re-frame/reg-fx
::notify-about-removing
(fn [{:keys [web3 current-chat-id participants chats current-public-key]}]
(let [{:keys [private public] :as new-keypair} (protocol/new-keypair!)
{:keys [name private-key public-key]
:as chat} (get chats current-chat-id)
old-keypair {:private private-key
:public public-key}
contacts (get chat :contacts)
identities (-> (map :identity contacts)
set
(clojure.set/difference participants))]
(re-frame/dispatch [:update-chat! {:chat-id current-chat-id
:private-key private
:public-key public}])
(doseq [participant participants]
(let [id (random/id)]
(doseq [keypair [old-keypair new-keypair]]
(protocol/remove-from-group!
{:web3 web3
:group-id current-chat-id
:identity participant
:keypair keypair
:message {:from current-public-key
:message-id id}}))))
(protocol/start-watching-group!
{:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(re-frame/dispatch [:incoming-message %1 %2])})
(protocol/update-group!
{:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key
:keypair new-keypair}
:identities identities
:message {:from current-public-key
:message-id (random/id)}}))))
;;;; Handlers ;;;; Handlers
@ -134,44 +13,55 @@
:show-group-chat-profile :show-group-chat-profile
(fn [{db :db} [_ chat-id]] (fn [{db :db} [_ chat-id]]
{:db (assoc db :new-chat-name (get-in db [:chats chat-id :name]) {:db (assoc db :new-chat-name (get-in db [:chats chat-id :name])
:group/group-type :chat-group) :group/group-type :chat-group)
:dispatch [:navigate-to :group-chat-profile]})) :dispatch [:navigate-to :group-chat-profile]}))
(handlers/register-handler-fx (handlers/register-handler-fx
:add-new-group-chat-participants :add-new-group-chat-participants
(fn [{{:keys [current-chat-id selected-participants] :as db} :db} _] [(re-frame/inject-cofx :random-id)]
(let [new-identities (map #(hash-map :identity %) selected-participants)] (fn [{{:keys [current-chat-id selected-participants] :as db} :db now :now message-id :random-id :as cofx} _]
{:db (-> db (let [new-identities (map #(hash-map :identity %) selected-participants)
(update-in [:chats current-chat-id :contacts] concat new-identities) participants (concat (get-in db [:chats current-chat-id :contacts])
(assoc :selected-participants #{})) selected-participants)
::add-members-to-chat (select-keys db [:current-chat-id :selected-participants]) contacts (:contacts/contacts db)
::notify-about-new-members (select-keys db [:current-chat-id :selected-participants added-participants-names (map #(get-in contacts [% :name]) selected-participants)]
:current-public-key :chats :web3])}))) (handlers/merge-fx cofx
{:db (-> db
(defn- remove-identities [collection identities] (assoc-in [:chats current-chat-id :contacts] participants)
(remove #(identities (:identity %)) collection)) (assoc :selected-participants #{}))
:data-store/add-chat-contacts (select-keys db [:current-chat-id :selected-participants])}
(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've added " (apply str (interpose ", " added-participants-names)))))
(transport/send (group-chat/GroupAdminUpdate. nil participants) current-chat-id)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:remove-group-chat-participants :remove-group-chat-participants
(fn [{{:keys [current-chat-id] :as db} :db} [_ participants]] [re-frame/trim-v (re-frame/inject-cofx :random-id)]
{:db (update-in db [:chats current-chat-id :contacts] remove-identities participants) (fn [{{:keys [current-chat-id] :as db} :db now :now message-id :random-id :as cofx} [removed-participants]]
::remove-members-from-chat [current-chat-id participants] (let [participants (remove #(removed-participants (:identity %))
::notify-about-removing (merge {:participants participants} (get-in db [:chats current-chat-id :contacts]))
(select-keys db [:web3 :current-chat-id :chats :current-public-key])) contacts (:contacts/contacts db)
::create-removing-messages (merge {:participants participants} removed-participants-names (map #(get-in contacts [% :name]) removed-participants)]
(select-keys db [:current-chat-id :contacts/contacts]))})) (handlers/merge-fx cofx
{:db (assoc-in db [:chats current-chat-id :contacts] participants)
:data-store/remove-chat-contacts [current-chat-id removed-participants]}
(models.message/receive
(models.message/system-message current-chat-id message-id now
(str "You've removed " (apply str (interpose ", " removed-participants-names)))))
(transport/send (group-chat/GroupAdminUpdate. nil participants) current-chat-id)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:set-group-chat-name :set-group-chat-name
(fn [{{:keys [current-chat-id] :as db} :db} [_ new-chat-name]] (fn [{{:keys [current-chat-id] :as db} :db} [_ new-chat-name]]
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name) {:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
::save-chat-property [current-chat-id :name new-chat-name]})) :data-store/save-chat-property [current-chat-id :name new-chat-name]}))
(handlers/register-handler-fx (handlers/register-handler-fx
:clear-history :clear-history
(fn [{{:keys [current-chat-id] :as db} :db} _] (fn [{{:keys [current-chat-id] :as db} :db} _]
{:db (assoc-in db [:chats current-chat-id :messages] {}) {:db (assoc-in db [:chats current-chat-id :messages] {})
:delete-messages current-chat-id})) :data-store/hide-messages current-chat-id}))
(handlers/register-handler-fx (handlers/register-handler-fx
:clear-history? :clear-history?

View File

@ -0,0 +1,15 @@
(ns status-im.ui.screens.group.core)
(defn participants-added [chat-id added-participants-set {:keys [db] :as cofx}]
(when (seq added-participants-set)
{:db (update-in db [:chats chat-id :contacts] concat (mapv #(hash-map :identity %) added-participants-set))
:data-store/add-chat-contacts [chat-id added-participants-set]}))
(defn participants-removed [chat-id removed-participants-set {:keys [now db] :as cofx}]
(when (seq removed-participants-set)
(let [{:keys [is-active timestamp]} (get-in db [:chats chat-id])]
;;TODO: not sure what this condition is for
(when (and is-active (>= now timestamp))
{:db (update-in db [:chats chat-id :contacts]
(partial remove (comp removed-participants-set :identity)))
:data-store/remove-chat-contacts [chat-id removed-participants-set]}))))

View File

@ -1,48 +1,13 @@
(ns status-im.ui.screens.group.events (ns status-im.ui.screens.group.events
(:require [status-im.protocol.core :as protocol] (:require [re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
[re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]] [status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.ui.components.styles :refer [default-chat-color]] [status-im.ui.components.styles :refer [default-chat-color]]
[status-im.data-store.contact-groups :as groups]
[clojure.string :as string] [clojure.string :as string]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.ui.screens.group.navigation] [status-im.ui.screens.group.navigation]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[re-frame.core :as re-frame])) [re-frame.core :as re-frame]))
;;;; COFX
(reg-cofx
::get-all-contact-groups
(fn [coeffects _]
(let [groups (->> (groups/get-all)
(map (fn [{:keys [group-id] :as group}]
[group-id group]))
(into {}))]
(assoc coeffects :all-groups groups))))
;;;; FX
(reg-fx
::save-contact-group
(fn [new-group]
(groups/save new-group)))
(reg-fx
::save-contact-groups
(fn [new-groups]
(groups/save-all new-groups)))
(reg-fx
::save-contact-group-property
(fn [[contact-group-id property-name value]]
(groups/save-property contact-group-id property-name value)))
(reg-fx
::add-contacts-to-contact-group
(fn [[contact-group-id selected-contacts]]
(groups/add-contacts contact-group-id selected-contacts)))
;;;; Handlers ;;;; Handlers
(register-handler-db (register-handler-db
@ -76,14 +41,14 @@
:order (count contact-groups) :order (count contact-groups)
:timestamp now :timestamp now
:contacts selected-contacts'}] :contacts selected-contacts'}]
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group}) {:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
::save-contact-group new-group}))) :data-store/save-contact-group new-group})))
(register-handler-fx (register-handler-fx
::update-contact-group ::update-contact-group
(fn [{:keys [db]} [_ new-group]] (fn [{:keys [db]} [_ new-group]]
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group}) {:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
::save-contact-group new-group})) :data-store/save-contact-group new-group}))
(defn update-pending-status [old-groups {:keys [group-id pending?] :as group}] (defn update-pending-status [old-groups {:keys [group-id pending?] :as group}]
(let [{old-pending :pending? (let [{old-pending :pending?
@ -101,14 +66,14 @@
(remove #(identities (:group-id %))) (remove #(identities (:group-id %)))
(map #(vector (:group-id %2) (assoc %2 :order %1)) (iterate inc old-groups-count)) (map #(vector (:group-id %2) (assoc %2 :order %1)) (iterate inc old-groups-count))
(into {}))] (into {}))]
{:db (update db :group/contact-groups merge new-groups') {:db (update db :group/contact-groups merge new-groups')
::save-contact-groups (into [] (vals new-groups'))}))) :data-store/save-contact-groups (into [] (vals new-groups'))})))
(register-handler-fx (register-handler-fx
:load-contact-groups :load-contact-groups
[(inject-cofx ::get-all-contact-groups)] [(inject-cofx :data-store/get-all-contact-groups)]
(fn [{:keys [db all-groups]} _] (fn [{:keys [db all-contact-groups]} _]
{:db (assoc db :group/contact-groups all-groups)})) {:db (assoc db :group/contact-groups all-contact-groups)}))
(defn move-item [v from to] (defn move-item [v from to]
(if (< from to) (if (< from to)
@ -132,30 +97,37 @@
:save-contact-group-order :save-contact-group-order
(fn [{{:group/keys [contact-groups groups-order] :as db} :db} _] (fn [{{:group/keys [contact-groups groups-order] :as db} :db} _]
(let [new-groups (mapv #(assoc (contact-groups (second %)) :order (first %)) (let [new-groups (mapv #(assoc (contact-groups (second %)) :order (first %))
(map-indexed vector (reverse groups-order)))] (map-indexed vector (reverse groups-order)))]
{:db (update db :group/contact-groups merge (map #(vector (:group-id %) %) new-groups)) {:db (update db
::save-contact-groups new-groups}))) :group/contact-groups
merge (map #(vector (:group-id %) %) new-groups))
:data-store/save-contact-groups new-groups})))
(register-handler-fx (register-handler-fx
:set-contact-group-name :set-contact-group-name
(fn [{{:keys [new-chat-name] :group/keys [contact-group-id] :as db} :db} _] (fn [{{:keys [new-chat-name] :group/keys [contact-group-id] :as db} :db} _]
{:db (assoc-in db [:group/contact-groups contact-group-id :name] new-chat-name) {:db (assoc-in db
::save-contact-group-property [contact-group-id :name new-chat-name]})) [:group/contact-groups contact-group-id :name]
new-chat-name)
:data-store/save-contact-group-property [contact-group-id :name new-chat-name]}))
(register-handler-fx (register-handler-fx
:add-selected-contacts-to-group :add-selected-contacts-to-group
(fn [{{:group/keys [contact-groups contact-group-id selected-contacts] :as db} :db} _] (fn [{{:group/keys [contact-groups contact-group-id selected-contacts] :as db} :db} _]
(let [new-identities (mapv #(hash-map :identity %) selected-contacts)] (let [new-identities (mapv #(hash-map :identity %) selected-contacts)]
{:db (update-in db [:group/contact-groups contact-group-id :contacts] #(into [] (set (concat % new-identities)))) {:db (update-in db
::add-contacts-to-contact-group [contact-group-id selected-contacts]}))) [:group/contact-groups contact-group-id :contacts]
#(into [] (set (concat % new-identities))))
:data-store/add-contacts-to-contact-group [contact-group-id selected-contacts]})))
(register-handler-fx (register-handler-fx
:add-contacts-to-group :add-contacts-to-group
(fn [{:keys [db]} [_ group-id contacts]] (fn [{:keys [db]} [_ group-id contacts]]
(let [new-identities (mapv #(hash-map :identity %) contacts)] (let [new-identities (mapv #(hash-map :identity %) contacts)]
(when (get-in db [:group/contact-groups group-id]) (when (get-in db [:group/contact-groups group-id])
{:db (update-in db [:group/contact-groups group-id :contacts] #(into [] (set (concat % new-identities)))) {:db (update-in db [:group/contact-groups group-id :contacts]
::add-contacts-to-contact-group [group-id contacts]})))) #(into [] (set (concat % new-identities))))
:data-store/add-contacts-to-contact-group [group-id contacts]}))))
(defn remove-contact-from-group [whisper-identity] (defn remove-contact-from-group [whisper-identity]
(fn [contacts] (fn [contacts]
@ -171,5 +143,5 @@
(register-handler-fx (register-handler-fx
:delete-contact-group :delete-contact-group
(fn [{{:group/keys [contact-group-id] :as db} :db} _] (fn [{{:group/keys [contact-group-id] :as db} :db} _]
{:db (assoc-in db [:group/contact-groups contact-group-id :pending?] true) {:db (assoc-in db [:group/contact-groups contact-group-id :pending?] true)
::save-contact-group-property [contact-group-id :pending? true]})) :data-store/save-contact-group-property [contact-group-id :pending? true]}))

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.navigation (ns status-im.ui.screens.navigation
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.utils.handlers :refer [register-handler-db]] [status-im.utils.handlers :as handlers]))
[status-im.constants :refer [console-chat-id]]))
;; private helper fns ;; private helper fns
@ -16,21 +15,24 @@
(pop stack))] (pop stack))]
(conj stack' view-id))) (conj stack' view-id)))
(defn replace-view [db view-id]
(-> db
(update :navigation-stack replace-top-element view-id)
(assoc :view-id view-id)))
;; public fns ;; public fns
(defn navigate-to-clean (defn navigate-to-clean
([db view-id] (navigate-to-clean db view-id nil)) ([view-id cofx] (navigate-to-clean view-id cofx nil))
([db view-id screen-params] ([view-id {:keys [db]} screen-params]
;; TODO (jeluard) Unify all :navigate-to flavours. Maybe accept a map of parameters? ;; TODO (jeluard) Unify all :navigate-to flavours. Maybe accept a map of parameters?
(let [db (cond-> db (let [db (cond-> db
(seq screen-params) (seq screen-params)
(assoc-in [:navigation/screen-params view-id] screen-params))] (assoc-in [:navigation/screen-params view-id] screen-params))]
(push-view db view-id)))) {:db (push-view db view-id)})))
(defn replace-view [view-id {:keys [db]}]
{:db (-> (update db :navigation-stack replace-top-element view-id)
(assoc :view-id view-id))})
(defn navigate-forget [view-id {:keys [db]}]
{:db (assoc db :view-id view-id)})
(defmulti preload-data! (defmulti preload-data!
(fn [db [_ view-id]] (or view-id (:view-id db)))) (fn [db [_ view-id]] (or view-id (:view-id db))))
@ -56,31 +58,25 @@
;; event handlers ;; event handlers
(register-handler-db (handlers/register-handler-db
:navigate-forget
(re-frame/enrich preload-data!)
(fn [db [_ new-view-id]]
(assoc db :view-id new-view-id)))
(register-handler-db
:navigate-to :navigate-to
(re-frame/enrich preload-data!) (re-frame/enrich preload-data!)
(fn [db [_ & params]] (fn [db [_ & params]]
(apply navigate-to db params))) (apply navigate-to db params)))
(register-handler-db (handlers/register-handler-db
:navigate-to-modal :navigate-to-modal
(re-frame/enrich preload-data!) (re-frame/enrich preload-data!)
(fn [db [_ modal-view]] (fn [db [_ modal-view]]
(assoc db :modal modal-view))) (assoc db :modal modal-view)))
(register-handler-db (handlers/register-handler-fx
:navigation-replace :navigation-replace
(re-frame/enrich preload-data!) (re-frame/enrich preload-data!)
(fn [db [_ view-id]] (fn [cofx [_ view-id]]
(replace-view db view-id))) (replace-view view-id cofx)))
(register-handler-db (handlers/register-handler-db
:navigate-back :navigate-back
(re-frame/enrich -preload-data!) (re-frame/enrich -preload-data!)
(fn [{:keys [navigation-stack view-id modal] :as db} _] (fn [{:keys [navigation-stack view-id modal] :as db} _]
@ -98,16 +94,17 @@
(assoc :navigation-stack navigation-stack')) (assoc :navigation-stack navigation-stack'))
(assoc db :view-id first-in-stack)))))) (assoc db :view-id first-in-stack))))))
(register-handler-db (handlers/register-handler-fx
:navigate-to-clean :navigate-to-clean
(fn [db [_ & params]] (fn [{:keys [db]} [_ & params]]
(apply navigate-to db params))) {:db (apply navigate-to db params)}))
(register-handler-db (handlers/register-handler-fx
:navigate-to-tab :navigate-to-tab
(re-frame/enrich preload-data!) (re-frame/enrich preload-data!)
(fn [db [_ view-id]] (fn [{:keys [db] :as cofx} [_ view-id]]
(-> db (handlers/merge-fx cofx
(assoc :prev-tab-view-id (:view-id db)) {:db (-> db
(assoc :prev-view-id (:view-id db)) (assoc :prev-tab-view-id (:view-id db))
(navigate-to-clean view-id)))) (assoc :prev-view-id (:view-id db)))}
(navigate-to-clean view-id))))

View File

@ -1,17 +1,10 @@
(ns status-im.ui.screens.network-settings.events (ns status-im.ui.screens.network-settings.events
(:require [re-frame.core :refer [dispatch dispatch-sync after] :as re-frame] (:require [re-frame.core :refer [dispatch dispatch-sync after] :as re-frame]
[status-im.utils.handlers :refer [register-handler] :as handlers] [status-im.utils.handlers :refer [register-handler] :as handlers]
[status-im.data-store.networks :as networks]
[status-im.ui.screens.accounts.events :as accounts-events] [status-im.ui.screens.accounts.events :as accounts-events]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.ethereum.core :as utils])) [status-im.utils.ethereum.core :as utils]
[status-im.transport.core :as transport]))
;;;; FX
(re-frame/reg-fx
::save-networks
(fn [networks]
(networks/save-all networks)))
;; handlers ;; handlers
@ -29,23 +22,30 @@
:save-networks new-networks'}))) :save-networks new-networks'})))
(handlers/register-handler-fx (handlers/register-handler-fx
::save-network ::close-application
(fn [{:keys [db now]} [_ network]] (fn [_ _]
(accounts-events/account-update {:db db {:close-application nil}))
:close-application nil}
{:network network (handlers/register-handler-fx
:last-updated now}))) ::save-network
(fn [{:keys [db now] :as cofx} [_ network]]
(handlers/merge-fx cofx
(accounts-events/account-update {:network network
:last-updated now}
[::close-application]))))
(handlers/register-handler-fx (handlers/register-handler-fx
:connect-network :connect-network
(fn [{:keys [db now]} [_ network]] (fn [{:keys [db now] :as cofx} [_ network]]
(let [current-network (:network db) (let [current-network (:network db)
networks (:networks/networks db)] networks (:networks/networks db)
chats (:transport/chats db)]
(if (utils/network-with-upstream-rpc? networks current-network) (if (utils/network-with-upstream-rpc? networks current-network)
(merge (accounts-events/account-update {:db db} {:network network (handlers/merge-fx cofx
:last-updated now}) {:dispatch [:navigate-to-clean :accounts]}
{:dispatch [:navigate-to-clean :accounts] (transport/stop-whisper)
:stop-whisper nil}) (accounts-events/account-update {:network network
:last-updated now}))
{:show-confirmation {:title (i18n/label :t/close-app-title) {:show-confirmation {:title (i18n/label :t/close-app-title)
:content (i18n/label :t/close-app-content) :content (i18n/label :t/close-app-content)
:confirm-button-text (i18n/label :t/close-app-button) :confirm-button-text (i18n/label :t/close-app-button)

View File

@ -2,15 +2,16 @@
(:require [re-frame.core :refer [dispatch]] (:require [re-frame.core :refer [dispatch]]
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.ui.screens.accounts.events :as accounts-events] [status-im.ui.screens.accounts.events :as accounts-events]
[status-im.i18n :as i18n])) [status-im.i18n :as i18n]
[status-im.transport.core :as transport]))
(handlers/register-handler-fx (handlers/register-handler-fx
::save-wnode ::save-wnode
(fn [{:keys [db now]} [_ wnode]] (fn [{:keys [db now] :as cofx} [_ wnode]]
(-> (accounts-events/account-update {:db db} (handlers/merge-fx cofx
{:wnode wnode :last-updated now}) {:dispatch [:navigate-to-clean :accounts]}
(merge {:dispatch [:navigate-to-clean :accounts] (accounts-events/account-update {:wnode wnode :last-updated now})
:stop-whisper nil})))) (transport/stop-whisper))))
(handlers/register-handler-fx (handlers/register-handler-fx
:connect-wnode :connect-wnode

View File

@ -17,7 +17,7 @@
[toolbar/content-title ""]]) [toolbar/content-title ""]])
(defn actions [{:keys [pending? whisper-identity dapp?]}] (defn actions [{:keys [pending? whisper-identity dapp?]}]
(concat (if pending? (concat (if (or (nil? pending?) pending?)
[{:label (i18n/label :t/add-to-contacts) [{:label (i18n/label :t/add-to-contacts)
:icon :icons/add-contact :icon :icons/add-contact
:action #(re-frame/dispatch [:add-contact whisper-identity]) :action #(re-frame/dispatch [:add-contact whisper-identity])
@ -28,7 +28,7 @@
:accessibility-label :in-contacts-button}]) :accessibility-label :in-contacts-button}])
[{:label (i18n/label :t/send-message) [{:label (i18n/label :t/send-message)
:icon :icons/chats :icon :icons/chats
:action #(re-frame/dispatch [:start-chat whisper-identity {:navigation-replace? true}]) :action #(re-frame/dispatch [:open-chat-with-contact {:whisper-identity whisper-identity}])
:accessibility-label :start-conversation-button}] :accessibility-label :start-conversation-button}]
(when-not dapp? (when-not dapp?
[{:label (i18n/label :t/send-transaction) [{:label (i18n/label :t/send-transaction)

View File

@ -27,11 +27,12 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:profile/send-transaction :profile/send-transaction
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] [(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
(fn [{{:contacts/keys [contacts] :as db} :db :as cofx} [chat-id]] (fn [{{:contacts/keys [contacts]} :db :as cofx} [chat-id]]
(let [send-command (get-in contacts chat-const/send-command-ref) (let [send-command (get-in contacts chat-const/send-command-ref)]
fx (chat-events/start-chat chat-id true cofx)] (handlers/merge-fx cofx
(merge fx (input-events/select-chat-input-command (:db fx) send-command nil true))))) (chat-events/start-chat chat-id {:navigation-replace? true})
(input-events/select-chat-input-command send-command nil true)))))
(defn get-current-account [{:accounts/keys [current-account-id] :as db}] (defn get-current-account [{:accounts/keys [current-account-id] :as db}]
(get-in db [:accounts/accounts current-account-id])) (get-in db [:accounts/accounts current-account-id]))
@ -59,8 +60,8 @@
name name
(get-in db [:accounts/accounts current-account-id :name])))) (get-in db [:accounts/accounts current-account-id :name]))))
(defn clear-profile [fx] (defn clear-profile [{:keys [db] :as cofx}]
(update fx :db dissoc :my-profile/profile :my-profile/default-name :my-profile/editing?)) {:db (dissoc db :my-profile/profile :my-profile/default-name :my-profile/editing?)})
(handlers/register-handler-fx (handlers/register-handler-fx
:my-profile/start-editing-profile :my-profile/start-editing-profile
@ -69,16 +70,17 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:my-profile/save-profile :my-profile/save-profile
(fn [{:keys [db now]} _] (fn [{:keys [db now] :as cofx} _]
(let [{:keys [photo-path]} (:my-profile/profile db) (let [{:keys [photo-path]} (:my-profile/profile db)
cleaned-name (clean-name db :my-profile/profile) cleaned-name (clean-name db :my-profile/profile)
cleaned-edit (merge {:name cleaned-name cleaned-edit (merge {:name cleaned-name
:last-updated now} :last-updated now}
(if photo-path (if photo-path
{:photo-path photo-path}))] {:photo-path photo-path}))]
(-> (clear-profile {:db db}) (handlers/merge-fx cofx
(accounts-events/account-update cleaned-edit) {:dispatch [:navigate-back]}
(update :dispatch-n concat [[:navigate-back]]))))) (clear-profile)
(accounts-events/account-update cleaned-edit)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:group-chat-profile/start-editing :group-chat-profile/start-editing
@ -107,6 +109,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:my-profile/finish :my-profile/finish
(fn [{:keys [db]} _] (fn [{:keys [db] :as cofx} _]
(-> {:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)} (handlers/merge-fx cofx
(accounts-events/account-update {:seed-backed-up? true})))) {:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)}
(accounts-events/account-update {:seed-backed-up? true}))))

Some files were not shown because too many files have changed in this diff Show More