From 80fb8dde8baea957d37cdf94f0ea88f74b7c810a Mon Sep 17 00:00:00 2001 From: janherich Date: Sun, 28 Jan 2018 12:23:51 +0100 Subject: [PATCH] Read at startup & write through async queues --- src/status_im/chat/events.cljs | 110 +++++--- src/status_im/chat/events/commands.cljs | 36 +-- src/status_im/chat/events/input.cljs | 9 +- .../chat/events/receive_message.cljs | 59 ++-- src/status_im/chat/events/send_message.cljs | 22 +- src/status_im/chat/handlers.cljs | 54 +--- src/status_im/chat/handlers/send_message.cljs | 0 src/status_im/chat/models.cljs | 51 ++-- src/status_im/chat/models/commands.cljs | 3 + src/status_im/chat/models/input.cljs | 3 +- src/status_im/chat/models/message.cljs | 262 +++++++++--------- src/status_im/chat/specs.cljs | 3 + src/status_im/chat/subs.cljs | 19 +- src/status_im/chat/utils.cljs | 17 -- src/status_im/chat/views/input/input.cljs | 6 +- .../chat/views/input/suggestions.cljs | 6 +- src/status_im/chat/views/message/message.cljs | 24 +- src/status_im/commands/handlers/debug.cljs | 2 +- src/status_im/data_store/chats.cljs | 16 +- src/status_im/data_store/messages.cljs | 13 +- src/status_im/data_store/realm/chats.cljs | 40 +-- src/status_im/data_store/realm/messages.cljs | 13 + src/status_im/protocol/handlers.cljs | 46 +-- src/status_im/protocol/listeners.cljs | 6 +- src/status_im/ui/screens/contacts/events.cljs | 4 +- src/status_im/ui/screens/db.cljs | 3 + src/status_im/ui/screens/discover/subs.cljs | 4 +- src/status_im/ui/screens/home/subs.cljs | 2 +- src/status_im/utils/async.cljs | 12 + src/status_im/utils/clocks.cljs | 4 +- src/status_im/utils/events_buffer.cljs | 28 -- test/cljs/status_im/test/contacts/events.cljs | 2 +- 32 files changed, 424 insertions(+), 455 deletions(-) delete mode 100644 src/status_im/chat/handlers/send_message.cljs delete mode 100644 src/status_im/chat/utils.cljs delete mode 100644 src/status_im/utils/events_buffer.cljs diff --git a/src/status_im/chat/events.cljs b/src/status_im/chat/events.cljs index 38e47f3164..c7a1355adb 100644 --- a/src/status_im/chat/events.cljs +++ b/src/status_im/chat/events.cljs @@ -1,9 +1,11 @@ (ns status-im.chat.events - (:require [cljs.core.async :as async] + (:require [clojure.set :as set] + [cljs.core.async :as async] [re-frame.core :as re-frame] [taoensso.timbre :as log] [status-im.utils.handlers :as handlers] [status-im.utils.gfycat.core :as gfycat] + [status-im.utils.async :as async-utils] [status-im.chat.models :as model] [status-im.chat.console :as console-chat] [status-im.chat.constants :as chat-const] @@ -13,6 +15,7 @@ [status-im.data-store.contacts :as contacts-store] [status-im.data-store.requests :as requests-store] [status-im.data-store.messages :as messages-store] + [status-im.data-store.pending-messages :as pending-messages-store] [status-im.ui.screens.navigation :as navigation] [status-im.protocol.core :as protocol] [status-im.constants :as const] @@ -46,6 +49,11 @@ (fn [cofx _] (assoc cofx :get-stored-messages messages-store/get-by-chat-id))) +(re-frame/reg-cofx + :stored-message-ids + (fn [cofx _] + (assoc cofx :stored-message-ids (messages-store/get-stored-message-ids)))) + (re-frame/reg-cofx :all-stored-chats (fn [cofx _] @@ -57,38 +65,50 @@ (assoc cofx :get-stored-chat chats-store/get-by-id))) (re-frame/reg-cofx - :message-exists? - (fn [cofx] - (assoc cofx :message-exists? messages-store/exists?))) - -(re-frame/reg-cofx - :get-last-clock-value - (fn [cofx] - (assoc cofx :get-last-clock-value messages-store/get-last-clock-value))) + :inactive-chat-ids + (fn [cofx _] + (assoc cofx :inactive-chat-ids (chats-store/get-inactive-ids)))) ;;;; Effects -(def ^:private update-message-queue (async/chan 100)) - -(async/go-loop [message (async/ db - (update-in [:chats current-chat-id :messages] merge (index-messages new-messages)) + (update-in [:chats current-chat-id :messages] merge new-messages) + (update-in [:chats current-chat-id :not-loaded-message-ids] #(apply disj % (keys new-messages))) (assoc-in [:chats current-chat-id :all-loaded?] (> const/default-number-of-messages (count new-messages))))})))) (handlers/register-handler-db - :set-message-shown + :message-appeared [re-frame/trim-v] (fn [db [{:keys [chat-id message-id]}]] - (update-in db [:chats chat-id :messages message-id] assoc :new? false))) + (update-in db [:chats chat-id :messages message-id] assoc :appearing? false))) (defn init-console-chat [{:keys [chats] :accounts/keys [current-account-id] :as db}] @@ -167,7 +188,6 @@ (not current-account-id) (update :dispatch-n concat [[:chat-received-message/add-when-commands-loaded console-chat/intro-message1]])))) - (handlers/register-handler-fx :init-console-chat (fn [{:keys [db]} _] @@ -176,14 +196,18 @@ (handlers/register-handler-fx :initialize-chats [(re-frame/inject-cofx :all-stored-chats) + (re-frame/inject-cofx :inactive-chat-ids) (re-frame/inject-cofx :get-stored-messages) (re-frame/inject-cofx :stored-unviewed-messages) + (re-frame/inject-cofx :stored-message-ids) (re-frame/inject-cofx :get-stored-unanswered-requests)] (fn [{:keys [db all-stored-chats + inactive-chat-ids stored-unanswered-requests get-stored-messages - stored-unviewed-messages]} _] + stored-unviewed-messages + stored-message-ids]} _] (let [{:accounts/keys [account-creation?]} db load-default-contacts-event [:load-default-contacts!]] (if account-creation? @@ -194,15 +218,19 @@ {} stored-unanswered-requests) chats (reduce (fn [acc {:keys [chat-id] :as chat}] - (assoc acc chat-id - (assoc chat - :unviewed-messages (get stored-unviewed-messages chat-id) - :requests (get chat->message-id->request chat-id) - :messages (index-messages (get-stored-messages chat-id))))) + (let [chat-messages (index-messages (get-stored-messages chat-id))] + (assoc acc chat-id + (assoc chat + :unviewed-messages (get stored-unviewed-messages chat-id) + :requests (get chat->message-id->request chat-id) + :messages chat-messages + :not-loaded-message-ids (set/difference (get stored-message-ids chat-id) + (-> chat-messages keys set)))))) {} all-stored-chats)] (-> db - (assoc :chats chats) + (assoc :chats chats + :deleted-chats inactive-chat-ids) init-console-chat (update :dispatch-n conj load-default-contacts-event))))))) @@ -272,7 +300,7 @@ (handlers/register-handler-fx :add-chat-loaded-event - [re-frame/trim-v] + [(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] (fn [{:keys [db] :as cofx} [chat-id event]] (if (get (:chats db) chat-id) {:db (assoc-in db [:chats chat-id :chat-loaded-event] event)} @@ -282,7 +310,7 @@ ;; TODO(janherich): remove this unnecessary event in the future (only model function `add-chat` will stay) (handlers/register-handler-fx :add-chat - [re-frame/trim-v] + [(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] (fn [cofx [chat-id chat-props]] (model/add-chat cofx chat-id chat-props))) @@ -305,7 +333,7 @@ (handlers/register-handler-fx :start-chat - [re-frame/trim-v] + [(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] (fn [{:keys [db] :as cofx} [contact-id {:keys [navigation-replace?]}]] (when (not= (:current-public-key db) contact-id) ; don't allow to open chat with yourself (if (get (:chats db) contact-id) @@ -319,13 +347,17 @@ ;; TODO(janherich): remove this unnecessary event in the future (only model function `update-chat` will stay) (handlers/register-handler-fx :update-chat! - [(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] + [re-frame/trim-v] (fn [cofx [chat]] (model/update-chat cofx chat))) -;; TODO(janherich): remove this unnecessary event in the future (only model function `upsert-chat` will stay) (handlers/register-handler-fx - :upsert-chat! - [(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v] - (fn [cofx [chat]] - (model/upsert-chat cofx chat))) \ No newline at end of file + :remove-chat + [re-frame/trim-v] + (fn [{:keys [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}))) diff --git a/src/status_im/chat/events/commands.cljs b/src/status_im/chat/events/commands.cljs index cdff48b05b..c512aee67e 100644 --- a/src/status_im/chat/events/commands.cljs +++ b/src/status_im/chat/events/commands.cljs @@ -31,23 +31,25 @@ :contacts/keys [contacts]} db jail-id (or bot jail-id chat-id) jail-command-name (or content-command-name command-name)] - (if (get-in contacts [jail-id :jail-loaded?]) - (let [path [(if (= :response (keyword type)) :responses :commands) - [jail-command-name - (or content-command-scope-bitmask scope-bitmask)] - data-type] - to (get-in contacts [chat-id :address]) - jail-params {:parameters params - :context (generate-context current-account-id chat-id to group-id)}] - {:db db - :call-jail {:jail-id jail-id - :path path - :params jail-params - :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])}))) + (if-not (get contacts jail-id) ;; bot is not even in contacts, do nothing + {:db db} + (if (get-in contacts [jail-id :jail-loaded?]) + (let [path [(if (= :response (keyword type)) :responses :commands) + [jail-command-name + (or content-command-scope-bitmask scope-bitmask)] + data-type] + to (get-in contacts [chat-id :address]) + jail-params {:parameters params + :context (generate-context current-account-id chat-id to group-id)}] + {:db db + :call-jail {:jail-id jail-id + :path path + :params jail-params + :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 diff --git a/src/status_im/chat/events/input.cljs b/src/status_im/chat/events/input.cljs index 7dfc6a38e1..04e5e7ffee 100644 --- a/src/status_im/chat/events/input.cljs +++ b/src/status_im/chat/events/input.cljs @@ -2,8 +2,7 @@ (:require [clojure.string :as string] [re-frame.core :as re-frame] [taoensso.timbre :as log] - [status-im.chat.constants :as constants] - [status-im.chat.utils :as chat-utils] + [status-im.chat.constants :as constants] [status-im.chat.models :as model] [status-im.chat.models.input :as input-model] [status-im.chat.models.commands :as commands-model] @@ -193,7 +192,7 @@ :validation-messages nil :prev-command name}) (set-chat-input-metadata metadata) - (set-chat-input-text (str (chat-utils/command-name command) + (set-chat-input-text (str (commands-model/command-name command) constants/spacing-char (when-not sequential-params (input-model/join-command-args prefill))))) @@ -476,9 +475,7 @@ (handlers/register-handler-fx :send-current-message - [(re-frame/inject-cofx :random-id) - (re-frame/inject-cofx :get-last-clock-value) - (re-frame/inject-cofx :get-stored-chat)] + message-model/send-interceptors (fn [{{:keys [current-chat-id current-public-key] :as db} :db message-id :random-id current-time :now :as cofx} _] (when-not (get-in db [:chat-ui-props current-chat-id :sending-in-progress?]) diff --git a/src/status_im/chat/events/receive_message.cljs b/src/status_im/chat/events/receive_message.cljs index 6633fa793e..a80848c501 100644 --- a/src/status_im/chat/events/receive_message.cljs +++ b/src/status_im/chat/events/receive_message.cljs @@ -1,7 +1,6 @@ (ns status-im.chat.events.receive-message (:require [re-frame.core :as re-frame] - [taoensso.timbre :as log] - [status-im.data-store.chats :as chat-store] + [taoensso.timbre :as log] [status-im.data-store.messages :as messages-store] [status-im.chat.events.commands :as commands-events] [status-im.chat.models.message :as message-model] @@ -9,16 +8,7 @@ [status-im.utils.handlers :as handlers] [status-im.utils.random :as random])) -;;;; Coeffects - -(re-frame/reg-cofx - :pop-up-chat? - (fn [cofx] - (assoc cofx :pop-up-chat? (fn [chat-id] - (or (not (chat-store/exists? chat-id)) - (chat-store/is-active? chat-id)))))) - -;;;; FX +;;;; Handlers (handlers/register-handler-fx ::received-message @@ -30,25 +20,26 @@ :chat-received-message/add message-model/receive-interceptors (fn [{:keys [db] :as cofx} [{:keys [content] :as message}]] - (if (:command content) - ;; we are dealing with received command message, we can't add it right away, - ;; we first need to fetch short-preview + preview and add it only after we already have those. - ;; note that `request-command-message-data` implicitly wait till jail is ready and - ;; calls are made only after that - (commands-events/request-command-message-data - db message - {:data-type :short-preview - :proceed-event-creator (fn [short-preview] - [:request-command-message-data - message - {:data-type :preview - :proceed-event-creator (fn [preview] - [::received-message - (update message :content merge - {:short-preview short-preview - :preview preview})])}])}) - ;; regular non command message, we can add it right away - (message-model/receive cofx message)))) + (when (message-model/add-to-chat? cofx message) + (if (:command content) + ;; we are dealing with received command message, we can't add it right away, + ;; we first need to fetch short-preview + preview and add it only after we already have those. + ;; note that `request-command-message-data` implicitly wait till jail is ready and + ;; calls are made only after that + (commands-events/request-command-message-data + db message + {:data-type :short-preview + :proceed-event-creator (fn [short-preview] + [:request-command-message-data + message + {:data-type :preview + :proceed-event-creator (fn [preview] + [::received-message + (update message :content merge + {:short-preview short-preview + :preview preview})])}])}) + ;; regular non command message, we can add it right away + (message-model/receive cofx message))))) ;; TODO janherich: get rid of this special case once they hacky app start-up sequence is refactored (handlers/register-handler-fx @@ -72,9 +63,9 @@ (re-frame/dispatch [:update-bot-db {:bot bot-id :db update-db}])) (re-frame/dispatch [:suggestions-handler (assoc params - :bot-id bot-id - :result data - :default-db default-db)]) + :bot-id bot-id + :result data + :default-db default-db)]) (doseq [message log-messages] (let [{:keys [message type]} message] (when (or (not= type "debug") diff --git a/src/status_im/chat/events/send_message.cljs b/src/status_im/chat/events/send_message.cljs index bd68ee44e2..210e7a97a2 100644 --- a/src/status_im/chat/events/send_message.cljs +++ b/src/status_im/chat/events/send_message.cljs @@ -1,16 +1,11 @@ (ns status-im.chat.events.send-message - (:require [re-frame.core :as re-frame] - [status-im.chat.utils :as chat-utils] + (:require [re-frame.core :as re-frame] [status-im.chat.models.message :as message-model] - [status-im.constants :as constants] - [status-im.data-store.chats :as chats-store] + [status-im.constants :as constants] [status-im.native-module.core :as status] - [status-im.protocol.core :as protocol] - [status-im.utils.config :as config] - [status-im.utils.handlers :as handlers] - [status-im.utils.random :as random] - [status-im.utils.types :as types] - [status-im.utils.datetime :as datetime] + [status-im.protocol.core :as protocol] + [status-im.utils.handlers :as handlers] + [status-im.utils.types :as types] [taoensso.timbre :as log])) (re-frame/reg-fx @@ -34,13 +29,6 @@ (fn [value] (protocol/send-message! value))) -(re-frame/reg-fx - :update-message-overhead! - (fn [[chat-id network-status]] - (if (= network-status :offline) - (chats-store/inc-message-overhead chat-id) - (chats-store/reset-message-overhead chat-id)))) - ;;;; Handlers (handlers/register-handler-fx diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 4df4ec0195..e610bbc957 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -1,12 +1,11 @@ (ns status-im.chat.handlers - (:require [re-frame.core :refer [enrich after debug dispatch reg-fx]] + (:require [re-frame.core :refer [after dispatch reg-fx]] [clojure.string :as string] [status-im.ui.components.styles :refer [default-chat-color]] [status-im.chat.constants :as chat-consts] [status-im.protocol.core :as protocol] [status-im.data-store.chats :as chats] - [status-im.data-store.messages :as messages] - [status-im.data-store.pending-messages :as pending-messages] + [status-im.data-store.messages :as messages] [status-im.constants :refer [text-content-type content-type-command content-type-command-request @@ -15,33 +14,6 @@ [status-im.utils.handlers :refer [register-handler register-handler-fx] :as u] status-im.chat.events)) -(defn remove-chat - [db [_ chat-id]] - (update db :chats dissoc chat-id)) - -(reg-fx - ::delete-messages - (fn [id] - (messages/delete-by-chat-id id))) - -(defn delete-messages! - [{:keys [current-chat-id chats]} [_ chat-id]] - (let [id (or chat-id current-chat-id) - {:keys [group-chat]} (chats/get-by-id chat-id)] - (when group-chat - (messages/delete-by-chat-id id)))) - -(defn delete-chat! - [_ [_ chat-id]] - (let [{:keys [debug?]} (chats/get-by-id chat-id)] - (if debug? - (chats/delete chat-id) - (chats/set-inactive chat-id)))) - -(defn remove-pending-messages! - [_ [_ chat-id]] - (pending-messages/delete-all-by-chat-id chat-id)) - (register-handler :leave-group-chat ;; todo order of operations tbd @@ -62,14 +34,6 @@ :message-id (random/id)}}))) (dispatch [:remove-chat current-chat-id])))) -(register-handler - :remove-chat - (u/handlers-> - remove-chat - delete-messages! - remove-pending-messages! - delete-chat!)) - (register-handler :update-group-message (u/side-effect! (fn [{:keys [current-public-key web3 chats]} @@ -93,11 +57,6 @@ :keypair keypair :callback #(dispatch [:incoming-message %1 %2])})))))))) -(reg-fx - ::save-public-chat - (fn [chat] - (chats/save chat))) - (reg-fx ::start-watching-group (fn [{:keys [group-id web3 current-public-key keypair]}] @@ -122,17 +81,12 @@ (merge (when-not exists? {:db (assoc-in db [:chats (:chat-id chat)] chat) - ::save-public-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]]})))) -(reg-fx - ::save-chat - (fn [new-chat] - (chats/save new-chat))) - (reg-fx ::start-listen-group (fn [{:keys [new-chat web3 current-public-key]}] @@ -193,7 +147,7 @@ {:db (-> db (assoc-in [:chats (:chat-id new-chat)] new-chat) (assoc :group/selected-contacts #{})) - ::save-chat new-chat + :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] diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index e51b5c1aac..f2d994f2a5 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -13,40 +13,45 @@ (update-in db [:chat-ui-props current-chat-id ui-element] not)) (defn- create-new-chat - [{:keys [db now] :as cofx} chat-id chat-props] + [{:keys [db now] :as cofx} chat-id] (let [name (get-in db [:contacts/contacts chat-id :name])] - (merge {:chat-id chat-id - :name (or name (gfycat/generate-gfy chat-id)) - :color styles/default-chat-color - :group-chat false - :is-active true - :timestamp now - :contacts [{:identity chat-id}]} - chat-props))) + {:chat-id chat-id + :name (or name (gfycat/generate-gfy chat-id)) + :color styles/default-chat-color + :group-chat false + :is-active true + :timestamp now + :contacts [{:identity chat-id}]})) (defn add-chat + "Adds new chat to db & realm, if the chat with same id already exists, justs restores it" ([cofx chat-id] (add-chat cofx chat-id {})) - ([{:keys [db] :as cofx} chat-id chat-props] - (let [new-chat (create-new-chat cofx chat-id chat-props) - existing-chats (:chats db)] - {:db (cond-> db - (not (contains? existing-chats chat-id)) - (update :chats assoc chat-id new-chat)) + ([{:keys [db get-stored-chat] :as cofx} chat-id chat-props] + (let [{:keys [chats deleted-chats]} db + new-chat (merge (if (get deleted-chats chat-id) + (assoc (get-stored-chat chat-id) :is-active true) + (create-new-chat cofx chat-id)) + chat-props)] + {:db (-> db + (update :chats assoc chat-id new-chat) + (update :deleted-chats (fnil disj #{}) chat-id)) :save-chat new-chat}))) ;; TODO (yenda): there should be an option to update the timestamp ;; this shouldn't need a specific function like `upsert-chat` which ;; is wrongfuly named (defn update-chat - "Updates chat properties, if chat is not present in db, creates a default new one" - [{:keys [db get-stored-chat] :as cofx} {:keys [chat-id] :as chat}] - (let [chat (merge (or (get-stored-chat chat-id) - (create-new-chat cofx chat-id {})) - chat)] - {:db (cond-> db - (:is-active chat) (update-in [:chats chat-id] merge chat)) - :save-chat chat})) + "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}] + (let [{:keys [chats deleted-chats]} db] + (if (get deleted-chats chat-id) ;; when chat is deleted, don't change anything + {:db db} + (let [chat (merge (or (get chats chat-id) + (create-new-chat cofx chat-id)) + chat-props)] + {:db (update-in db [:chats chat-id] merge chat) + :save-chat chat})))) ;; TODO (yenda): an upsert is suppose to add the entry if it doesn't ;; exist and update it if it does diff --git a/src/status_im/chat/models/commands.cljs b/src/status_im/chat/models/commands.cljs index aff9c57e1a..fb521f041f 100644 --- a/src/status_im/chat/models/commands.cljs +++ b/src/status_im/chat/models/commands.cljs @@ -13,6 +13,9 @@ (defn- is-dapp? [all-contacts {:keys [identity]}] (get-in all-contacts [identity :dapp?])) +(defn command-name [{:keys [name]}] + (str chat-consts/command-char name)) + (defn commands-responses "Returns map of commands/responses eligible for current chat." [type access-scope->commands-responses {:keys [address]} {:keys [contacts group-chat public?]} all-contacts] diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 78e3533536..14b2d089d7 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -5,8 +5,7 @@ [status-im.native-module.core :as status] [status-im.chat.constants :as const] [status-im.chat.models.commands :as commands-model] - [status-im.chat.views.input.validation-messages :refer [validation-message]] - [status-im.chat.utils :as chat-utils] + [status-im.chat.views.input.validation-messages :refer [validation-message]] [status-im.i18n :as i18n] [status-im.utils.phone-number :as phone-number] [status-im.js-dependencies :as dependencies] diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 75ffe7c65f..38fc7bb8fa 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -5,8 +5,6 @@ [status-im.chat.events.requests :as requests-events] [status-im.chat.models :as chat-model] [status-im.chat.models.commands :as commands-model] - [status-im.chat.utils :as chat-utils] - [status-im.data-store.messages :as messages-store] [status-im.utils.datetime :as datetime-utils] [status-im.utils.clocks :as clocks-utils] [status-im.utils.random :as random])) @@ -16,12 +14,8 @@ (get accounts current-account-id)) (def receive-interceptors - [(re-frame/inject-cofx :message-exists?) - (re-frame/inject-cofx :pop-up-chat?) - (re-frame/inject-cofx :get-last-clock-value) - (re-frame/inject-cofx :random-id) - (re-frame/inject-cofx :get-stored-chat) - re-frame/trim-v]) + [(re-frame/inject-cofx :get-stored-message) (re-frame/inject-cofx :get-stored-chat) + (re-frame/inject-cofx :random-id) re-frame/trim-v]) (defn- lookup-response-ref [access-scope->commands-responses account chat contacts response-name] @@ -32,58 +26,81 @@ contacts)] (:ref (get available-commands-responses response-name)))) -(defn- add-message-to-db - [db {:keys [message-id] :as message} chat-id current-chat?] - (cond-> (chat-utils/add-message-to-db db chat-id chat-id message (:new? message)) - (not current-chat?) - (update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))) +(defn add-message-to-db + [db chat-id {:keys [message-id clock-value] :as message} current-chat?] + (let [prepared-message (cond-> (assoc message + :chat-id chat-id + :appearing? true) + (not current-chat?) + (assoc :appearing? false))] + (cond-> (-> db + (update-in [:chats chat-id :messages] assoc message-id prepared-message) + (update-in [:chats chat-id :last-clock-value] (fnil max 0) clock-value)) + (not current-chat?) + (update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id)))) (defn receive - [{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx} + [{:keys [db now] :as cofx} {:keys [from group-id chat-id content-type content message-id timestamp clock-value] - :as message - :or {clock-value 0}}] + :as message}] (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) chat-identifier (or group-id chat-id from) - direct-message? (nil? group-id)] - ;; proceed with adding message if message is not already stored in realm, - ;; it's not from current user (outgoing message) and it's for relevant chat - ;; (either current active chat or new chat not existing yet or it's a direct message) - (when (and (not (message-exists? message-id)) - (not= from public-key) - (or (pop-up-chat? chat-identifier) - direct-message?)) - (let [current-chat? (and (= :chat view-id) - (= current-chat-id chat-identifier)) - fx (if (get-in db [:chats chat-identifier]) - (chat-model/upsert-chat cofx {:chat-id chat-identifier - :group-chat (boolean group-id)}) - (chat-model/add-chat cofx chat-identifier)) - command-request? (= content-type constants/content-type-command-request) - command (:command content) - enriched-message (cond-> (assoc message - :chat-id chat-identifier - :timestamp (or timestamp now) - :show? true - :clock-value (clocks-utils/receive - clock-value - (get-last-clock-value chat-identifier))) - 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 enriched-message chat-identifier current-chat?) - (assoc :save-message (dissoc enriched-message :new?))) - command-request? - (requests-events/add-request chat-identifier enriched-message)))))) + current-chat? (and (= :chat view-id) + (= current-chat-id chat-identifier)) + fx (if (get-in db [:chats chat-identifier]) + (chat-model/upsert-chat cofx {:chat-id chat-identifier + :group-chat (boolean group-id)}) + (chat-model/add-chat cofx chat-identifier)) + chat (get-in fx [:db :chats chat-identifier]) + command-request? (= content-type constants/content-type-command-request) + command (:command content) + enriched-message (cond-> (assoc message + :chat-id chat-identifier + :timestamp (or timestamp now) + :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? + [{:keys [db get-stored-message]} {:keys [group-id chat-id from message-id]}] + (let [chat-identifier (or group-id chat-id from) + {:keys [chats deleted-chats current-public-key]} db + {:keys [messages not-loaded-message-ids]} (get chats chat-identifier)] + (when (not= from current-public-key) + (if group-id + (not (or (get deleted-chats chat-identifier) + (get messages message-id) + (get not-loaded-message-ids message-id))) + (not (or (get messages message-id) + (get not-loaded-message-ids message-id) + (and (get deleted-chats chat-identifier) + (get-stored-message message-id)))))))) + +(defn message-seen-by? [message user-pk] + (= :seen (get-in message [:user-statuses user-pk]))) + +;;;; Send message + +(def send-interceptors + [(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :random-id-seq) + (re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]) (defn- handle-message-from-bot [cofx {:keys [message chat-id]}] (when-let [message (cond @@ -159,31 +176,21 @@ (and group-chat (not public?)) (let [{:keys [public-key private-key]} (get chats chat-id)] {:send-group-message (assoc options - :group-id chat-id - :keypair {:public public-key - :private private-key})}) + :group-id chat-id + :keypair {:public public-key + :private private-key})}) (and group-chat public?) {:send-public-group-message (assoc options :group-id chat-id - :username (get-in accounts [current-account-id :name]))} + :username (get-in accounts [current-account-id :name]))} :else (merge {:send-message (assoc-in options [:message :to] chat-id)} (when-not command) {:send-notification fcm-token})))))) -;;;; Send message - -(def send-interceptors - [(re-frame/inject-cofx :random-id) - (re-frame/inject-cofx :random-id-seq) - (re-frame/inject-cofx :get-stored-chat) - (re-frame/inject-cofx :now) - (re-frame/inject-cofx :get-last-clock-value) - re-frame/trim-v]) - -(defn- prepare-message [clock-value params chat] +(defn- prepare-message [params chat] (let [{:keys [chat-id identity message-text]} params - {:keys [group-chat public?]} chat + {:keys [group-chat public? last-clock-value]} chat message {:message-id (random/id) :chat-id chat-id :content message-text @@ -191,36 +198,34 @@ :content-type constants/text-content-type :outgoing true :timestamp (datetime-utils/now-ms) - :clock-value (clocks-utils/send clock-value) + :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)))) - + (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 [network-status] :as db} :db - :keys [now get-stored-chat get-last-clock-value]} + :keys [now]} {:keys [chat-id] :as params}] (let [chat (get-in db [:chats chat-id]) - message (prepare-message (get-last-clock-value chat-id) params chat) + message (prepare-message params chat) params' (assoc params :message message) - - fx {:db (chat-utils/add-message-to-db db chat-id chat-id message) - :update-message-overhead! [chat-id network-status] + fx {:db (add-message-to-db db chat-id message true) + :update-message-overhead [chat-id network-status] :save-message message}] - (-> (merge fx (chat-model/upsert-chat (assoc fx :get-stored-chat get-stored-chat :now now) + (-> (merge fx (chat-model/upsert-chat (assoc fx :now now) {:chat-id chat-id})) (as-> fx' - (merge fx' (send fx' params')))))) + (merge fx' (send fx' params')))))) (defn- prepare-command [identity chat-id clock-value @@ -238,14 +243,14 @@ :scope (:scope command) :params params}) content' (assoc content :handler-data handler-data - :type (name (:type command)) - :content-command (:name command) - :content-command-scope-bitmask (:scope-bitmask command) - :content-command-ref (:ref command) - :preview (:preview command) - :short-preview (:short-preview command) - :bot (or (:bot command) - (:owner-id command)))] + :type (name (:type command)) + :content-command (:name command) + :content-command-scope-bitmask (:scope-bitmask command) + :content-command-ref (:ref command) + :preview (:preview command) + :short-preview (:short-preview command) + :bot (or (:bot command) + (:owner-id command)))] {:message-id id :from identity :to chat-id @@ -263,36 +268,37 @@ :show? true})) (defn send-command - [{{:keys [current-public-key network-status] :as db} :db - :keys [now get-stored-chat random-id-seq get-last-clock-value]} result add-to-chat-id params] + [{{:keys [current-public-key network-status chats] :as db} :db + :keys [now random-id-seq]} result add-to-chat-id params] (let [{{:keys [handler-data command] :as content} :command chat-id :chat-id} params - request (:request handler-data) - hidden-params (->> (:params command) - (filter :hidden) - (map :name)) - command' (prepare-command current-public-key chat-id (get-last-clock-value chat-id) request content) - params' (assoc params :command command') + request (:request handler-data) + last-clock-value (get-in chats [chat-id :last-clock-value]) + hidden-params (->> (:params command) + (filter :hidden) + (map :name)) + command' (prepare-command current-public-key chat-id last-clock-value request content) + params' (assoc params :command command') - fx {:db (-> (merge db (:db result)) - (chat-utils/add-message-to-db add-to-chat-id chat-id command')) - :update-message-overhead! [chat-id network-status] - :save-message (-> command' - (assoc :chat-id chat-id) - (update-in [:content :params] - #(apply dissoc % hidden-params)) - (dissoc :to-message :has-handler :raw-input))}] + fx {:db (-> (merge db (:db result)) + (add-message-to-db chat-id command' true)) + :update-message-overhead [chat-id network-status] + :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 - (chat-model/upsert-chat (assoc fx :get-stored-chat get-stored-chat :now now) + (chat-model/upsert-chat (assoc fx :now now) {:chat-id chat-id}) (dissoc result :db)) true (as-> fx' - (merge fx' (send fx' params'))) + (merge fx' (send fx' params'))) (:to-message command') (assoc :chat-requests/mark-as-answered {:chat-id chat-id @@ -300,9 +306,9 @@ (= 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)))))) + (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 [{:keys [db] :as cofx} {:keys [chat-id command] :as command-params}] @@ -329,8 +335,8 @@ :to to :current-account (get accounts current-account-id) :message-id id} - (:async-handler command) - (assoc :orig-params orig-params))}] + (:async-handler command) + (assoc :orig-params orig-params))}] {:call-jail {:jail-id identity :path [handler-type [name scope-bitmask] :handler] :params jail-params @@ -344,13 +350,13 @@ (-> {:db (chat-model/set-chat-ui-props db {:sending-in-progress? false})} (as-> fx' - (cond - (and (= constants/console-chat-id chat-id) - (console-events/commands-names (:name command))) - (invoke-console-command-handler (merge cofx fx') params) + (cond + (and (= constants/console-chat-id chat-id) + (console-events/commands-names (:name command))) + (invoke-console-command-handler (merge cofx fx') params) - (:has-handler command) - (merge fx' (invoke-command-handlers fx' params)) + (:has-handler command) + (merge fx' (invoke-command-handlers fx' params)) - :else - (merge fx' (send-command cofx fx' chat-id params))))))) + :else + (merge fx' (send-command cofx fx' chat-id params))))))) diff --git a/src/status_im/chat/specs.cljs b/src/status_im/chat/specs.cljs index f436915c18..3a14c3e45c 100644 --- a/src/status_im/chat/specs.cljs +++ b/src/status_im/chat/specs.cljs @@ -2,6 +2,7 @@ (:require [cljs.spec.alpha :as s])) (s/def :chat/chats (s/nilable map?)) ; {id (string) chat (map)} active chats on chat's tab +(s/def :chat/deleted-chats (s/nilable set?)) ; set of deleted chat ids (s/def :chat/current-chat-id (s/nilable string?)) ; current or last opened chat-id (s/def :chat/chat-id (s/nilable string?)) ; what is the difference ? ^ (s/def :chat/new-chat-name (s/nilable string?)) ; we have name in the new-chat why do we need this field @@ -15,5 +16,7 @@ (s/def :chat/public-group-topic (s/nilable string?)) (s/def :chat/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object (s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id +(s/def :chat/not-loaded-message-ids (s/nilable set?)) ; set of message-ids not yet fully loaded from persisted state +(s/def :chat/last-clock-value (s/nilable number?)) ; last logical clock value of messages in chat (s/def :chat/loaded-chats (s/nilable seq?)) (s/def :chat/bot-db (s/nilable map?)) diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index 78bcf3f2f6..450c9d44d2 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -2,8 +2,7 @@ (:require [re-frame.core :refer [reg-sub subscribe]] [status-im.constants :as constants] [status-im.chat.models.input :as input-model] - [status-im.chat.models.commands :as commands-model] - [status-im.chat.utils :as chat-utils] + [status-im.chat.models.commands :as commands-model] [status-im.chat.views.input.utils :as input-utils] [status-im.commands.utils :as commands-utils] [status-im.utils.datetime :as time] @@ -11,7 +10,7 @@ [status-im.i18n :as i18n] [clojure.string :as string])) -(reg-sub :chats :chats) +(reg-sub :get-chats :chats) (reg-sub :get-current-chat-id :current-chat-id) @@ -48,15 +47,21 @@ (fn [kb-height] (if platform/ios? kb-height 0))) +(reg-sub + :get-active-chats + :<- [:get-chats] + (fn [chats] + (into {} (filter (comp :is-active second)) chats))) + (reg-sub :get-chat - :<- [:chats] + :<- [:get-active-chats] (fn [chats [_ chat-id]] (get chats chat-id))) (reg-sub :get-current-chat - :<- [:chats] + :<- [:get-active-chats] :<- [:get-current-chat-id] (fn [[chats current-chat-id]] (get chats current-chat-id))) @@ -69,7 +74,7 @@ (reg-sub :chat - :<- [:chats] + :<- [:get-active-chats] :<- [:get-current-chat-id] (fn [[chats id] [_ k chat-id]] (get-in chats [(or chat-id id) k]))) @@ -161,7 +166,7 @@ (defn- available-commands-responses [[commands-responses {:keys [input-text]}]] (->> commands-responses map->sorted-seq - (filter #(string/includes? (chat-utils/command-name %) (or input-text ""))))) + (filter #(string/includes? (commands-model/command-name %) (or input-text ""))))) (reg-sub :get-available-commands diff --git a/src/status_im/chat/utils.cljs b/src/status_im/chat/utils.cljs deleted file mode 100644 index ba70cc096f..0000000000 --- a/src/status_im/chat/utils.cljs +++ /dev/null @@ -1,17 +0,0 @@ -(ns status-im.chat.utils - (:require [status-im.chat.constants :as chat.constants] - [taoensso.timbre :as log])) - -(defn add-message-to-db - ([db add-to-chat-id chat-id message] (add-message-to-db db add-to-chat-id chat-id message true)) - ([db add-to-chat-id chat-id {:keys [message-id] :as message} new?] - (let [prepared-message (assoc message - :chat-id chat-id - :new? (if (nil? new?) true new?))] - (update-in db [:chats add-to-chat-id :messages] assoc message-id prepared-message)))) - -(defn message-seen-by? [message user-pk] - (= :seen (get-in message [:user-statuses user-pk]))) - -(defn command-name [{:keys [name]}] - (str chat.constants/command-char name)) diff --git a/src/status_im/chat/views/input/input.cljs b/src/status_im/chat/views/input/input.cljs index 705a8788ba..4e39d98149 100644 --- a/src/status_im/chat/views/input/input.cljs +++ b/src/status_im/chat/views/input/input.cljs @@ -6,8 +6,8 @@ [taoensso.timbre :as log] [status-im.chat.constants :as const] [status-im.chat.models.input :as input-model] - [status-im.chat.styles.input.input :as style] - [status-im.chat.utils :as chat-utils] + [status-im.chat.models.commands :as commands-model] + [status-im.chat.styles.input.input :as style] [status-im.chat.views.input.emoji :as emoji] [status-im.chat.views.input.parameter-box :as parameter-box] [status-im.chat.views.input.input-actions :as input-actions] @@ -25,7 +25,7 @@ [react/view [react/text {:style (style/command first?) :font :roboto-mono} - (chat-utils/command-name command)]]]) + (commands-model/command-name command)]]]) (defview commands-view [] [all-commands-responses [:get-available-commands-responses] diff --git a/src/status_im/chat/views/input/suggestions.cljs b/src/status_im/chat/views/input/suggestions.cljs index b2d13726af..7c999efbc6 100644 --- a/src/status_im/chat/views/input/suggestions.cljs +++ b/src/status_im/chat/views/input/suggestions.cljs @@ -4,7 +4,7 @@ [status-im.ui.components.react :as react] [status-im.chat.styles.input.suggestions :as style] [status-im.chat.views.input.animations.expandable :as expandable] - [status-im.chat.utils :as chat.utils] + [status-im.chat.models.commands :as commands-model] [status-im.i18n :as i18n])) (defn suggestion-item [{:keys [on-press name description last?]}] @@ -23,14 +23,14 @@ [suggestion-item {:on-press #(let [metadata (assoc params :to-message-id message-id)] (re-frame/dispatch [:select-chat-input-command command metadata])) - :name (chat.utils/command-name command) + :name (commands-model/command-name command) :description description :last? last?}]) (defn command-item [{:keys [name description bot] :as command} last?] [suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil]) - :name (chat.utils/command-name command) + :name (commands-model/command-name command) :description description :last? last?}]) diff --git a/src/status_im/chat/views/message/message.cljs b/src/status_im/chat/views/message/message.cljs index 4f927632f6..e97f47ec4b 100644 --- a/src/status_im/chat/views/message/message.cljs +++ b/src/status_im/chat/views/message/message.cljs @@ -6,15 +6,14 @@ [status-im.ui.components.react :as react] [status-im.ui.components.animation :as animation] [status-im.ui.components.list-selection :as list-selection] - [status-im.chat.models.commands :as commands] [status-im.commands.utils :as commands.utils] - [status-im.chat.utils :as chat.utils] + [status-im.chat.models.commands :as models.commands] + [status-im.chat.models.message :as models.message] [status-im.chat.styles.message.message :as style] [status-im.chat.styles.message.command-pill :as pill-style] [status-im.chat.views.message.request-message :as request-message] [status-im.constants :as constants] [status-im.ui.components.chat-icon.screen :as chat-icon.screen] - [status-im.utils.events-buffer :as events-buffer] [status-im.utils.identicon :as identicon] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.platform :as platform] @@ -79,7 +78,7 @@ [react/view (pill-style/pill command) [react/text {:style pill-style/pill-text :font :default} - (chat.utils/command-name command)]]]) + (models.commands/command-name command)]]]) (when icon-path [react/view style/command-image-view [react/icon icon-path style/command-image]]) @@ -266,10 +265,10 @@ (callback)))))))) (defn message-container [message & children] - (if (:new? message) + (if (:appearing? message) (let [layout-height (reagent/atom 0) anim-value (animation/create-value 1) - anim-callback #(events-buffer/dispatch [:set-message-shown message]) + anim-callback #(re-frame/dispatch [:message-appeared message]) context {:to-value layout-height :val anim-value :callback anim-callback} @@ -296,13 +295,12 @@ "chat-message" :component-did-mount ;; send `:seen` signal when we have signed-in user, message not from us and we didn't sent it already - (fn [] - (when (and current-public-key message-id chat-id (not outgoing) - (not (chat.utils/message-seen-by? message current-public-key))) - (events-buffer/dispatch [:send-seen! {:chat-id chat-id - :from from - :me current-public-key - :message-id message-id}]))) + #(when (and current-public-key message-id chat-id (not outgoing) + (not (models.message/message-seen-by? message current-public-key))) + (re-frame/dispatch [:send-seen! {:chat-id chat-id + :from from + :me current-public-key + :message-id message-id}])) :reagent-render (fn [{:keys [outgoing group-chat content-type content] :as message}] [message-container message diff --git a/src/status_im/commands/handlers/debug.cljs b/src/status_im/commands/handlers/debug.cljs index d8a5eb1c14..0d84c29a69 100644 --- a/src/status_im/commands/handlers/debug.cljs +++ b/src/status_im/commands/handlers/debug.cljs @@ -48,7 +48,7 @@ (get-in contacts [whisper-identity :debug?])) (let [dapp (merge dapp-data {:dapp? true :debug? true})] - (re-frame/dispatch [:upsert-chat! {:chat-id whisper-identity + (re-frame/dispatch [:update-chat! {:chat-id whisper-identity :name name :debug? true}]) (if (get contacts whisper-identity) diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index 3266528f51..d81d3863f7 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -2,13 +2,13 @@ (:require [status-im.data-store.realm.chats :as data-store]) (:refer-clojure :exclude [exists?])) -(defn- normalize-contacts - [item] - (update item :contacts vals)) - (defn get-all [] - (map normalize-contacts (data-store/get-all-active))) + (data-store/get-all-active)) + +(defn get-inactive-ids + [] + (data-store/get-inactive-ids)) (defn get-by-id [id] @@ -19,7 +19,7 @@ (data-store/exists? chat-id)) (defn save - [{:keys [last-message-id chat-id] :as chat}] + [{:keys [chat-id] :as chat}] (data-store/save chat (data-store/exists? chat-id))) (defn delete @@ -85,8 +85,8 @@ (defn new-update? [timestamp chat-id] (let - [{:keys [added-to-at removed-at removed-from-at added-at]} - (get-by-id chat-id)] + [{:keys [added-to-at removed-at removed-from-at added-at]} + (get-by-id chat-id)] (and (> timestamp added-to-at) (> timestamp removed-at) (> timestamp removed-from-at) diff --git a/src/status_im/data_store/messages.cljs b/src/status_im/data_store/messages.cljs index d7aabb9706..65951661f6 100644 --- a/src/status_im/data_store/messages.cljs +++ b/src/status_im/data_store/messages.cljs @@ -16,9 +16,6 @@ {:outgoing false :to nil}) -(defn exists? [message-id] - (data-store/exists? message-id)) - (defn get-by-id [message-id] (data-store/get-by-id message-id)) @@ -33,18 +30,16 @@ (update message :content reader/read-string) message)))))) +(defn get-stored-message-ids + [] + (data-store/get-stored-message-ids)) + (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 get-last-clock-value - [chat-id] - (if-let [message (data-store/get-last-message chat-id)] - (:clock-value message) - 0)) - (defn get-unviewed [current-public-key] (into {} diff --git a/src/status_im/data_store/realm/chats.cljs b/src/status_im/data_store/realm/chats.cljs index 369b967743..b36512c70f 100644 --- a/src/status_im/data_store/realm/chats.cljs +++ b/src/status_im/data_store/realm/chats.cljs @@ -1,31 +1,39 @@ (ns status-im.data-store.realm.chats (:require [goog.object :as object] [status-im.data-store.realm.core :as realm] + [status-im.data-store.realm.messages :as messages] [status-im.utils.random :refer [timestamp]] [taoensso.timbre :as log]) (:refer-clojure :exclude [exists?])) -(defn get-all - [] - (-> @realm/account-realm - (realm/get-all :chat) - (realm/sorted :timestamp :desc))) - -(defn get-all-as-list - [] - (realm/js-object->clj (get-all))) +(defn- normalize-chat [{:keys [chat-id] :as chat}] + (let [last-message (messages/get-last-message chat-id)] + (-> chat + (realm/fix-map->vec :contacts) + (assoc :last-clock-value (or (:clock-value last-message) 0))))) (defn get-all-active [] - (-> (realm/get-by-field @realm/account-realm :chat :is-active true) - (realm/sorted :timestamp :desc) - realm/js-object->clj)) + (map normalize-chat + (-> (realm/get-by-field @realm/account-realm :chat :is-active true) + (realm/sorted :timestamp :desc) + realm/js-object->clj))) + +(defn get-inactive-ids + [] + (-> (realm/get-by-field @realm/account-realm :chat :is-active false) + (.map (fn [chat _ _] + (aget chat "chat-id"))) + realm/js-object->clj + set)) (defn- groups [active?] - (realm/filtered (get-all) - (str "group-chat = true && is-active = " - (if active? "true" "false")))) + (-> @realm/account-realm + (realm/get-all :chat) + (realm/sorted :timestamp :desc) + (realm/filtered (str "group-chat = true && is-active = " + (if active? "true" "false"))))) (defn get-active-group-chats [] @@ -46,7 +54,7 @@ [chat-id] (-> @realm/account-realm (realm/get-one-by-field-clj :chat :chat-id chat-id) - (realm/fix-map->vec :contacts))) + normalize-chat)) (defn save [chat update?] diff --git a/src/status_im/data_store/realm/messages.cljs b/src/status_im/data_store/realm/messages.cljs index 12f8fe5236..a68857ced6 100644 --- a/src/status_im/data_store/realm/messages.cljs +++ b/src/status_im/data_store/realm/messages.cljs @@ -32,6 +32,19 @@ realm/js-object->clj)] (mapv transform-message messages)))) +(defn get-stored-message-ids + [] + (let [chat-id->message-id (volatile! {})] + (-> @realm/account-realm + (.objects "message") + (.map (fn [msg _ _] + (vswap! chat-id->message-id + #(update % + (aget msg "chat-id") + (fnil conj #{}) + (aget msg "message-id")))))) + @chat-id->message-id)) + (defn get-by-fields [fields from number-of-messages] (-> (realm/get-by-fields @realm/account-realm :message :and fields) diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index 776618804e..51878704ae 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -1,5 +1,6 @@ (ns status-im.protocol.handlers (:require [re-frame.core :as re-frame] + [cljs.core.async :as async] [status-im.utils.handlers :as handlers] [status-im.data-store.contacts :as contacts] [status-im.data-store.messages :as messages] @@ -10,13 +11,13 @@ [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.utils :as chat.utils] + [status-im.chat.models.message :as models.message] [status-im.protocol.web3.inbox :as inbox] [status-im.protocol.web3.keys :as web3.keys] - [status-im.utils.datetime :as datetime] - [status-im.utils.events-buffer :as events-buffer] + [status-im.utils.datetime :as datetime] [taoensso.timbre :as log] [status-im.native-module.core :as status] [clojure.string :as string] @@ -40,11 +41,6 @@ (fn [coeffects _] (assoc coeffects :pending-messages (pending-messages/get-all)))) -(re-frame/reg-cofx - ::get-all-contacts - (fn [coeffects _] - (assoc coeffects :contacts (contacts/get-all)))) - (re-frame/reg-cofx ::message-get-by-id (fn [coeffects _] @@ -74,6 +70,8 @@ ;;;; FX +(def ^:private protocol-realm-queue (async-utils/task-queue 200)) + (re-frame/reg-fx :stop-whisper (fn [] (protocol/stop-whisper!))) @@ -85,7 +83,7 @@ {:web3 web3 :identity public-key :groups groups - :callback #(events-buffer/dispatch [:incoming-message %1 %2]) + :callback #(re-frame/dispatch [:incoming-message %1 %2]) :ack-not-received-s-interval 125 :default-ttl 120 :send-online-s-interval 180 @@ -118,7 +116,7 @@ (re-frame/reg-fx ::save-processed-messages (fn [processed-message] - (processed-messages/save processed-message))) + (async/put! protocol-realm-queue #(processed-messages/save processed-message)))) (defn system-message [message-id timestamp content] {:from "system" @@ -198,12 +196,12 @@ (re-frame/reg-fx ::pending-messages-delete (fn [message-id] - (pending-messages/delete message-id))) + (async/put! protocol-realm-queue #(pending-messages/delete message-id)))) (re-frame/reg-fx ::pending-messages-save (fn [pending-message] - (pending-messages/save pending-message))) + (async/put! protocol-realm-queue #(pending-messages/save pending-message)))) (re-frame/reg-fx ::status-init-jail @@ -370,15 +368,15 @@ (re-frame/inject-cofx ::get-web3) (re-frame/inject-cofx ::get-chat-groups) (re-frame/inject-cofx ::get-pending-messages) - (re-frame/inject-cofx ::get-all-contacts)] - (fn [{:keys [db web3 groups contacts pending-messages]} [current-account-id ethereum-rpc-url]] + (re-frame/inject-cofx :get-all-contacts)] + (fn [{:keys [db web3 groups all-contacts pending-messages]} [current-account-id ethereum-rpc-url]] (let [{:keys [public-key status updates-public-key updates-private-key]} (get-in db [:accounts/accounts current-account-id])] (when public-key {::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 contacts} + :status status :contacts all-contacts} :db (assoc db :web3 web3 :rpc-url (or ethereum-rpc-url constants/ethereum-rpc-url))})))) @@ -426,10 +424,10 @@ ;;; MESSAGES -(defn- transform-protocol-message [{:keys [from to payload]}] +(defn- transform-protocol-message [{:keys [from to payload]}] (merge payload {:from from :to to - :chat-id from})) + :chat-id (or (:group-id payload) from)})) (defn- message-from-self [{:keys [current-public-key]} {:keys [id to group-id]}] {:from to @@ -491,11 +489,14 @@ chat-identifier (or (:group-id payload) from) message-db-path [:chats chat-identifier :messages message-identifier] from-id (or sent-from from) - message (get-stored-message message-identifier)] + 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 (get-in db [:chats chat-identifier]) + (when (and message + (get-in db [:chats chat-identifier]) (not= status (get-in message [:user-statuses from-id])) - (not (chat.utils/message-seen-by? message 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}} @@ -526,8 +527,7 @@ ;; Get timestamp from message root level. ;; Root level "timestamp" is a unix ts in seconds. timestamp' (or (:payload timestamp) - (* 1000 timestamp))] - + (* 1000 timestamp))] (if-not existing-contact (let [contact (assoc contact :pending? true)] {:dispatch-n [[:add-contacts [contact]] @@ -535,7 +535,7 @@ (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]])))))))) + (<= prev-last-updated timestamp') (update :dispatch-n concat [[:update-contact! contact]])))))))) ;;GROUP diff --git a/src/status_im/protocol/listeners.cljs b/src/status_im/protocol/listeners.cljs index 750ada8c6d..4d29c61c09 100644 --- a/src/status_im/protocol/listeners.cljs +++ b/src/status_im/protocol/listeners.cljs @@ -1,11 +1,11 @@ (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] - [status-im.utils.events-buffer :as events-buffer])) + [status-im.utils.hex :as i])) (defn empty-public-key? [public-key] (or (= "0x0" public-key) @@ -96,5 +96,5 @@ "Valid options are: web3, identity, callback, keypair" [options] (fn [js-error js-message] - (events-buffer/dispatch [:handle-whisper-message js-error js-message options]))) + (re-frame/dispatch [:handle-whisper-message js-error js-message options]))) diff --git a/src/status_im/ui/screens/contacts/events.cljs b/src/status_im/ui/screens/contacts/events.cljs index 7d44a744e7..90e14cce58 100644 --- a/src/status_im/ui/screens/contacts/events.cljs +++ b/src/status_im/ui/screens/contacts/events.cljs @@ -27,7 +27,7 @@ ;;;; COFX (reg-cofx - ::get-all-contacts + :get-all-contacts (fn [coeffects _] (assoc coeffects :all-contacts (contacts/get-all)))) @@ -278,7 +278,7 @@ (register-handler-fx :load-contacts - [(inject-cofx ::get-all-contacts)] + [(inject-cofx :get-all-contacts)] (fn [{:keys [db all-contacts]} _] (let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts) contacts (into {} contacts-list)] diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 210914463a..87ebf78002 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -161,6 +161,7 @@ :qr/qr-modal :qr/current-qr-context :chat/chats + :chat/deleted-chats :chat/current-chat-id :chat/chat-id :chat/new-chat @@ -177,6 +178,8 @@ :chat/public-group-topic :chat/confirmation-code-sms-listener :chat/messages + :chat/not-loaded-message-ids + :chat/last-clock-value :chat/loaded-chats :chat/bot-db :commands/access-scope->commands-responses diff --git a/src/status_im/ui/screens/discover/subs.cljs b/src/status_im/ui/screens/discover/subs.cljs index faa3beb069..7ad231982b 100644 --- a/src/status_im/ui/screens/discover/subs.cljs +++ b/src/status_im/ui/screens/discover/subs.cljs @@ -25,7 +25,7 @@ (reg-sub :discover/discoveries-with-priority :<- [:discover/discoveries] - :<- [:chats] + :<- [:get-active-chats] :<- [:get-contacts] :<- [:get :current-public-key] (fn [[discoveries chats contacts current-public-key]] @@ -88,7 +88,7 @@ (reg-sub :discover/search-results :<- [:discover/discoveries-by-tags] :<- [:discover/search-tags] - :<- [:chats] + :<- [:get-active-chats] :<- [:get-contacts] :<- [:get :current-public-key] (fn [[discoveries search-tags chats contacts current-public-key] [_ limit]] diff --git a/src/status_im/ui/screens/home/subs.cljs b/src/status_im/ui/screens/home/subs.cljs index 535755a9e8..ba4f38c98f 100644 --- a/src/status_im/ui/screens/home/subs.cljs +++ b/src/status_im/ui/screens/home/subs.cljs @@ -2,7 +2,7 @@ (:require [re-frame.core :as re-frame])) (re-frame/reg-sub :home-items - :<- [:chats] + :<- [:get-active-chats] :<- [:browsers] (fn [[chats browsers]] (sort-by #(-> % second :timestamp) > (merge chats browsers)))) diff --git a/src/status_im/utils/async.cljs b/src/status_im/utils/async.cljs index fc3359b3ac..849d467d23 100644 --- a/src/status_im/utils/async.cljs +++ b/src/status_im/utils/async.cljs @@ -22,3 +22,15 @@ (recur (conj acc v) (and (seq acc) flush?)) (async/close! output-ch)) (recur acc (seq acc))))))) + +(defn task-queue + "Creates `core.async` channel which will process 0 arg functions put there in serial fashon. + Takes the same argument/s as `core.async/chan`, those arguments will be delegated to the + channel constructor. + Returns task-queue where tasks represented by 0 arg task functions can be put for processing." + [& args] + (let [task-queue (apply async/chan args)] + (async/go-loop [task-fn (async/