From 78d694f52f565442f6c4ce9d262594326c4082f5 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 26 Nov 2019 14:15:19 +0100 Subject: [PATCH] Move message processing to status-go and introduce protobuf This commit moves all the processing of messages to status-go. Messages are going arrive to status-react already saved an processed. Receiving/sending/retrieving from db is now using the same identical structure. The only processing left in status-react is to mark the messages as seen and update the unviewed count locally (only status-react knows whether the count should be updated). Partially remove commands as well as won't be used anymore. Signed-off-by: Andrea Maria Piana --- externs.js | 2 + .../module/NewMessageSignalHandler.java | 109 ++++---- src/status_im/chat/models.cljs | 32 +-- src/status_im/chat/models/input.cljs | 16 +- src/status_im/chat/models/message.cljs | 245 ++++-------------- src/status_im/chat/models/message_list.cljs | 17 +- src/status_im/constants.cljs | 13 +- src/status_im/contact/block.cljs | 8 +- src/status_im/contact/db.cljs | 2 +- src/status_im/data_store/chats.cljs | 15 +- src/status_im/data_store/messages.cljs | 115 +++----- src/status_im/ens/core.cljs | 4 +- src/status_im/ethereum/json_rpc.cljs | 4 +- src/status_im/events.cljs | 42 ++- src/status_im/group_chats/core.cljs | 55 +--- src/status_im/mailserver/core.cljs | 14 +- src/status_im/mailserver/topics.cljs | 7 +- src/status_im/pairing/core.cljs | 19 +- src/status_im/signals/core.cljs | 2 +- src/status_im/transport/db.cljs | 2 +- src/status_im/transport/filters/core.cljs | 2 +- src/status_im/transport/message/core.cljs | 170 +++++------- src/status_im/transport/message/protocol.cljs | 50 ++-- src/status_im/transport/utils.cljs | 11 - src/status_im/tribute_to_talk/whitelist.cljs | 3 +- .../ui/screens/chat/message/message.cljs | 62 ++--- .../screens/desktop/main/tabs/home/views.cljs | 11 +- src/status_im/ui/screens/ens/views.cljs | 3 +- .../ui/screens/home/views/inner_item.cljs | 23 +- src/status_im/utils/clocks.cljs | 7 +- src/status_im/utils/fx.cljs | 4 +- src/status_im/utils/utils.cljs | 27 +- status-go-version.json | 6 +- .../atomic/account_management/test_profile.py | 13 +- .../tests/atomic/chats/test_group_chat.py | 8 +- test/cljs/status_im/test/chat/models.cljs | 11 +- .../status_im/test/chat/models/message.cljs | 86 ++---- .../cljs/status_im/test/data_store/chats.cljs | 12 +- .../status_im/test/data_store/messages.cljs | 59 ++--- .../cljs/status_im/test/group_chats/core.cljs | 22 +- test/cljs/status_im/test/runner.cljs | 2 - test/cljs/status_im/test/transport/utils.cljs | 11 - .../test/tribute_to_talk/whitelist.cljs | 7 +- translations/en.json | 12 +- 44 files changed, 462 insertions(+), 883 deletions(-) delete mode 100644 test/cljs/status_im/test/transport/utils.cljs diff --git a/externs.js b/externs.js index 9e7ad86b90..e292a401f1 100644 --- a/externs.js +++ b/externs.js @@ -57,6 +57,8 @@ var TopLevel = { "code" : function () {}, "concat" : function () {}, "confirmMessagesProcessed" : function () {}, + "chats": function() {}, + "rawMessages": function() {}, "messages": function() {}, "discovery": function() {}, "dismiss": function() {}, diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NewMessageSignalHandler.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NewMessageSignalHandler.java index 155de35bc0..c4b16a4311 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NewMessageSignalHandler.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/NewMessageSignalHandler.java @@ -140,7 +140,7 @@ class NewMessageSignalHandler { void handleNewMessageSignal(JSONObject newMessageSignal) { try { - JSONArray chatsNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("messages"); + JSONArray chatsNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("chats"); for (int i = 0; i < chatsNewMessagesData.length(); i++) { try { upsertChat(chatsNewMessagesData.getJSONObject(i)); @@ -148,6 +148,15 @@ class NewMessageSignalHandler { Log.e(TAG, "JSON conversion failed: " + e.getMessage()); } } + JSONArray messagesNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("messages"); + for (int i = 0; i < messagesNewMessagesData.length(); i++) { + try { + upsertMessage(messagesNewMessagesData.getJSONObject(i)); + } catch (JSONException e) { + Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + } + } + if(shouldRefreshNotifications) { refreshNotifications(); shouldRefreshNotifications = false; @@ -173,72 +182,69 @@ class NewMessageSignalHandler { return person; } - private void upsertChat(JSONObject chatNewMessagesData) { + private void upsertChat(JSONObject chatData) { try { - JSONObject chatData = chatNewMessagesData.getJSONObject("chat"); // NOTE: this is an exemple of chatData // {"chatId":"contact-discovery-3622","filterId":"c0239d63f830e8b25f4bf7183c8d207f355a925b89514a17068cae4898e7f193", // "symKeyId":"","oneToOne":true,"identity":"046599511623d7385b926ce709ac57d518dac10d451a81f75cd32c7fb4b1c...", // "topic":"0xc446561b","discovery":false,"negotiated":false,"listen":true} - boolean oneToOne = chatData.getBoolean("oneToOne"); + int oneToOne = chatData.getInt("chatType"); // NOTE: for now we only notify one to one chats // TODO: also notifiy on mentions, keywords and group chats // TODO: one would have to pass the ens name and keywords to notify on when instanciating the class as well // as have a method to add new ones after the handler is instanciated - if (oneToOne) { - JSONArray messagesData = chatNewMessagesData.getJSONArray("messages"); + if (oneToOne == 1) { + //JSONArray messagesData = chatNewMessagesData.getJSONArray("messages"); // there is no proper id for oneToOne chat in chatData so we peek into first message sig // TODO: won't work for sync becaus it could be our own message - String id = messagesData.getJSONObject(0).getJSONObject("message").getString("sig"); + String id = chatData.getString("id"); StatusChat chat = chats.get(id); // if the chat was not already there, we create one if (chat == null) { - chat = new StatusChat(id, oneToOne); + chat = new StatusChat(id, true); } - ArrayList messages = chat.getMessages(); - // parse the new messages - for (int j = 0; j < messagesData.length(); j++) { - StatusMessage message = createMessage(messagesData.getJSONObject(j)); - if (message != null) { - messages.add(message); - } - } - - if (!messages.isEmpty()) { - chat.setMessages(messages); chats.put(id, chat); - shouldRefreshNotifications = true; - } } } catch (JSONException e) { Log.e(TAG, "JSON conversion failed: " + e.getMessage()); } } - private StatusMessage createMessage(JSONObject messageData) { - try { - JSONObject metadata = messageData.getJSONObject("metadata"); - JSONObject authorMetadata = metadata.getJSONObject("author"); - JSONArray payload = new JSONArray(messageData.getString("payload")); - // NOTE: this is an exemple of payload we are currently working with - // it is in the transit format, which is basically JSON - // refer to `transport.message.transit.cljs` on react side for details - // ["~#c4",["7","text/plain","~:public-group-user-message",157201130275201,1572011302752,["^ ","~:chat-id","test","~:text","7"]]] - if (payload.getString(0).equals("~#c4")) { - Person author = getPerson(authorMetadata.getString("publicKey"), authorMetadata.getString("identicon"), authorMetadata.getString("alias")); - JSONArray payloadContent = payload.getJSONArray(1); - String text = payloadContent.getString(0); - Double timestamp = payloadContent.getDouble(4); - return new StatusMessage(author, timestamp.longValue(), text); - } - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + + + private void upsertMessage(JSONObject messageData) { + try { + String chatId = messageData.getString("localChatId"); + StatusChat chat = chats.get(chatId); + if (chat == null) { + return; } - return null; + + StatusMessage message = createMessage(messageData); + if (message != null) { + chat.appendMessage(message); + chats.put(chatId, chat); + shouldRefreshNotifications = true; + } + + } + catch (JSONException e) { + Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + } + } + + private StatusMessage createMessage(JSONObject messageData) { + try { + Person author = getPerson(messageData.getString("from"), messageData.getString("identicon"), messageData.getString("alias")); + return new StatusMessage(author, messageData.getLong("whisperTimestamp"), messageData.getString("text")); + } catch (JSONException e) { + Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + } + return null; } } @@ -252,6 +258,7 @@ class StatusChat { this.id = id; this.oneToOne = oneToOne; this.messages = new ArrayList(); + this.name = name; } public String getId() { @@ -259,15 +266,23 @@ class StatusChat { } public String getName() { - //TODO this should be improved as it would rename the chat - // after our own user if we were posting from another device - // in 1-1 chats it should be the name of the user whose - // key is different than ours - return getLastMessage().getAuthor().getName().toString(); + + //TODO this should be improved as it would rename the chat + // after our own user if we were posting from another device + // in 1-1 chats it should be the name of the user whose + // key is different than ours + StatusMessage message = getLastMessage(); + if (message == null) { + return "no-name"; + } + return message.getAuthor().getName().toString(); } private StatusMessage getLastMessage() { + if (messages.size() > 0) { return messages.get(messages.size()-1); + } + return null; } public long getTimestamp() { @@ -278,8 +293,8 @@ class StatusChat { return messages; } - public void setMessages(ArrayList messages) { - this.messages = messages; + public void appendMessage(StatusMessage message) { + this.messages.add(message); } public String getSummary() { diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index 4f05b32205..0d8bddc9e1 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -114,13 +114,6 @@ [{:keys [db] :as cofx} chat-id] (chats-store/save-chat cofx (get-in db [:chats chat-id]))) -(fx/defn save-chat-delayed - "Debounce saving the chat" - [_ chat-id] - {:dispatch-debounce [{:key :save-chat - :event [::save-chat chat-id] - :delay 500}]}) - (fx/defn add-public-chat "Adds new public group chat to db" [cofx topic] @@ -139,12 +132,9 @@ "Clears history of the particular chat" [{:keys [db] :as cofx} chat-id] (let [{:keys [messages + last-message deleted-at-clock-value]} (get-in db [:chats chat-id]) - last-message-clock-value (or (->> messages - vals - (sort-by (comp unchecked-negate :clock-value)) - first - :clock-value) + last-message-clock-value (or (:clock-value last-message) deleted-at-clock-value (utils.clocks/send 0))] (fx/merge @@ -152,9 +142,7 @@ {:db (update-in db [:chats chat-id] merge {:messages {} :message-list nil - :last-message-content nil - :last-message-content-type nil - :last-message-timestamp nil + :last-message nil :unviewed-messages-count 0 :deleted-at-clock-value last-message-clock-value})} (messages-store/delete-messages-by-chat-id chat-id) @@ -204,13 +192,11 @@ [{:keys [db] :as cofx} {:keys [chat-id loaded-unviewed-messages-ids]}] (let [{:keys [loaded-unviewed-messages-ids unviewed-messages-count]} (get-in db [:chats chat-id])] - (upsert-chat - cofx - {:chat-id chat-id - :unviewed-messages-count (subtract-seen-messages - unviewed-messages-count - loaded-unviewed-messages-ids) - :loaded-unviewed-messages-ids #{}}))) + {:db (update-in db [:chats chat-id] assoc + :unviewed-messages-count (subtract-seen-messages + unviewed-messages-count + loaded-unviewed-messages-ids) + :loaded-unviewed-messages-ids #{})})) (fx/defn mark-messages-seen "Marks all unviewed loaded messages as seen in particular chat" @@ -224,7 +210,7 @@ true)) db loaded-unviewed-ids)} - (messages-store/mark-messages-seen loaded-unviewed-ids) + (messages-store/mark-messages-seen chat-id loaded-unviewed-ids) (update-chats-unviewed-messages-count {:chat-id chat-id}) (when platform/desktop? (update-dock-badge-label)))))) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index c634014384..e71a4bb803 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -100,12 +100,9 @@ :content-type (if emoji? constants/content-type-emoji constants/content-type-text) - :content (cond-> {:chat-id current-chat-id - :text input-text} - message-id - (assoc :response-to message-id) - preferred-name - (assoc :name preferred-name))}) + :text input-text + :response-to message-id + :ens-name preferred-name}) (set-chat-input-text nil) (process-cooldown))))) @@ -114,10 +111,9 @@ (when-not (string/blank? hash) (chat.message/send-message cofx {:chat-id current-chat-id :content-type constants/content-type-sticker - :content (cond-> {:chat-id current-chat-id - :hash hash - :pack pack - :text "Update to latest version to see a nice sticker here!"})}))) + :sticker {:hash hash + :pack pack} + :text "Update to latest version to see a nice sticker here!"}))) (fx/defn send-current-message "Sends message from current chat input" diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index c9cea13f90..0baaebe6f6 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -9,7 +9,7 @@ [status-im.chat.models.message-content :as message-content] [status-im.constants :as constants] [status-im.contact.db :as contact.db] - [status-im.data-store.messages :as messages-store] + [status-im.data-store.messages :as data-store.messages] [status-im.ethereum.core :as ethereum] [status-im.mailserver.core :as mailserver] [status-im.native-module.core :as status] @@ -26,40 +26,20 @@ [status-im.utils.types :as types] [taoensso.timbre :as log])) -(defn- wrap-group-message - "Wrap a group message in a membership update" - [cofx chat-id message] - (when-let [chat (get-in cofx [:db :chats chat-id])] - (message.group-chat/map->GroupMembershipUpdate. - {:chat-id chat-id - :membership-updates (:membership-updates chat) - :message message}))) - (defn- prepare-message [{:keys [content content-type] :as message} chat-id current-chat?] (cond-> message current-chat? - (assoc :seen true) (and (= constants/content-type-text content-type) - (message-content/should-collapse? - (:text content) - (:line-count content))) + (assoc :seen true) + (and (= constants/content-type-text content-type) + (message-content/should-collapse? + (:text content) + (:line-count content))) (assoc :should-collapse? true))) (defn system-message? [{:keys [message-type]}] - (= :system-message message-type)) - -(defn add-outgoing-status - [{:keys [from outgoing-status] :as message} current-public-key] - (if (and (= from current-public-key) - (not (system-message? message))) - (assoc message - :outgoing true - ;; We don't override outgoing-status if there, which means - ;; that our device has sent the message, while if empty is coming - ;; from a different device - :outgoing-status (or outgoing-status :sent)) - message)) + (= constants/message-type-private-group-system-message message-type)) (defn build-desktop-notification [{:keys [db] :as cofx} {:keys [chat-id timestamp content from] :as message}] @@ -81,16 +61,10 @@ (fx/defn add-message [{:keys [db] :as cofx} - {{:keys [chat-id message-id clock-value timestamp from] :as message} :message - :keys [current-chat? batch?]}] + {{:keys [chat-id message-id timestamp from] :as message} :message + :keys [current-chat?]}] (let [current-public-key (multiaccounts.model/current-public-key cofx) - prepared-message (-> message - (prepare-message chat-id current-chat?) - (add-outgoing-status current-public-key)) - chat-initialized? - (or - current-chat? - (get-in db [:chats chat-id :messages-initialized?]))] + prepared-message (prepare-message message chat-id current-chat?)] (when (and platform/desktop? (not= from current-public-key) (get-in db [:multiaccount :desktop-notifications?]) @@ -100,7 +74,6 @@ (fx/merge cofx {:db (cond-> (-> db - (update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value)) ;; We should not be always adding to the list, as it does not make sense ;; if the chat has not been initialized, but run into ;; some troubles disabling it, so next time @@ -110,25 +83,21 @@ (not= from current-public-key)) (update-in [:chats chat-id :loaded-unviewed-messages-ids] (fnil conj #{}) message-id))} - #(messages-store/save-message % prepared-message) (when (and platform/desktop? - (not batch?) (not (system-message? prepared-message))) (chat-model/update-dock-badge-label))))) (fx/defn add-received-message [{:keys [db] :as cofx} - {:keys [from message-id chat-id content metadata] :as message}] + {:keys [from message-id chat-id content] :as message}] (let [{:keys [current-chat-id view-id]} db - current-public-key (multiaccounts.model/current-public-key cofx) current-chat? (and (or (= :chat view-id) (= :chat-modal view-id)) (= current-chat-id chat-id))] - (add-message cofx {:batch? true - :message message - :metadata metadata - :current-chat? current-chat?}))) + (fx/merge cofx + (add-message {:message message + :current-chat? current-chat?})))) (defn- add-to-chat? [{:keys [db]} {:keys [chat-id clock-value message-id from]}] @@ -140,43 +109,36 @@ (defn extract-chat-id [cofx {:keys [chat-id from message-type]}] "Validate and return a valid chat-id" (cond - (and (= :group-user-message message-type) + (and (= constants/message-type-private-group message-type) (and (get-in cofx [:db :chats chat-id :contacts from]) (get-in cofx [:db :chats chat-id :members-joined (multiaccounts.model/current-public-key cofx)]))) chat-id - (and (= :public-group-user-message message-type) + (and (= constants/message-type-public-group message-type) (get-in cofx [:db :chats chat-id :public?])) chat-id - (and (= :user-message message-type) + (and (= constants/message-type-one-to-one message-type) (= (multiaccounts.model/current-public-key cofx) from)) chat-id - (= :user-message message-type) from)) + (= constants/message-type-one-to-one message-type) from)) -(defn calculate-unviewed-message-count - [{:keys [db] :as cofx} {:keys [chat-id from]}] +(fx/defn update-unviewed-count + [{:keys [now db] :as cofx} {:keys [chat-id + from + message-id] :as message}] (let [{:keys [current-chat-id view-id]} db chat-view? (or (= :chat view-id) (= :chat-modal view-id)) current-count (get-in db [:chats chat-id :unviewed-messages-count])] - (if (or (and chat-view? (= current-chat-id chat-id)) - (= from (multiaccounts.model/current-public-key cofx))) - current-count - (inc current-count)))) + (cond + (= from (multiaccounts.model/current-public-key cofx)) + ;; nothing to do + nil -(fx/defn update-unviewed-count [{:keys [now db] :as cofx} {:keys [chat-id] :as message}] - {:db (update-in db [:chats chat-id] - assoc - :is-active true - :timestamp now - :unviewed-messages-count (calculate-unviewed-message-count cofx message))}) + (and chat-view? (= current-chat-id chat-id)) + (fx/merge cofx + (data-store.messages/mark-messages-seen current-chat-id [message-id])) -(fx/defn update-last-message [{:keys [db]} {:keys [clock-value chat-id content timestamp content-type]}] - (let [last-chat-clock-value (get-in db [:chats chat-id :last-message-clock-value])] - ;; We should also compare message-id in case of clashes, but not sure it's worth - (when (> clock-value last-chat-clock-value) + :else {:db (update-in db [:chats chat-id] assoc - :last-message-clock-value clock-value - :last-message-content content - :last-message-timestamp timestamp - :last-message-content-type content-type)}))) + :unviewed-messages-count (inc current-count))}))) (fx/defn receive-one [cofx message] @@ -184,128 +146,32 @@ (let [message-with-chat-id (assoc message :chat-id chat-id)] (when (add-to-chat? cofx message-with-chat-id) (fx/merge cofx - (chat-model/ensure-chat {:chat-id chat-id}) (add-received-message message-with-chat-id) (update-unviewed-count message-with-chat-id) (chat-model/join-time-messages-checked chat-id) - (update-last-message message-with-chat-id) (when platform/desktop? - (chat-model/update-dock-badge-label)) - ;; And save chat - (chat-model/save-chat-delayed chat-id)))))) - -(defn system-message [{:keys [now] :as cofx} {:keys [clock-value chat-id content from]}] - (let [{:keys [last-clock-value]} (get-in cofx [:db :chats chat-id]) - message {:chat-id chat-id - :from from - :timestamp now - :whisper-timestamp now - :clock-value (or clock-value - (utils.clocks/send last-clock-value)) - :content content - :message-type :system-message - :content-type constants/content-type-status}] - (assoc message - :message-id (transport.utils/system-message-id message)))) - -(defn group-message? [{:keys [message-type]}] - (#{:group-user-message :public-group-user-message} message-type)) + (chat-model/update-dock-badge-label))))))) ;;;; Send message -(fx/defn send - [{{:keys [peers-count]} :db :as cofx} chat-id message send-record] - (protocol/send send-record chat-id (assoc cofx :message message))) - -(defn add-message-type [message {:keys [chat-id group-chat public?]}] - (cond-> message - (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))) - (def ^:private transport-keys [:content :content-type :message-type :clock-value :timestamp :name]) -(defn remove-icon - "Coin's icon's resource is represented as a function, - can't be properly de/serialised and has to be removed." - [message] - (cond-> message - (get-in message [:content :params :coin :icon :source]) - (update-in [:content :params :coin] dissoc :icon))) - -(fx/defn add-message-with-id [cofx message chat-id] - (when (and message - (not (get-in cofx [:db :chats chat-id :messages (:message-id message)]))) - (add-message cofx {:batch? false - :message message - :current-chat? (= (get-in cofx [:db :current-chat-id]) chat-id)}))) - -(fx/defn prepare-message-content [cofx chat-id message] - {::json-rpc/call - [{:method "shhext_prepareContent" - :params [(:content message)] - :on-success #(re-frame/dispatch [::prepared-message chat-id message %]) - :on-failure #(log/error "failed to prepare content" %)}]}) - -(fx/defn prepared-message - {:events [::prepared-message]} - [{:keys [now] :as cofx} chat-id message content] - (let [message-with-content - (update message :content - assoc - :parsed-text (:parsedText content) - :line-count (:lineCount content) - :should-collapse? (message-content/should-collapse? - (:text content) - (:lineCount content)) - :rtl? (:rtl content)) - send-record (protocol/map->Message - (select-keys message-with-content transport-keys)) - wrapped-record (if (= (:message-type send-record) :group-user-message) - (wrap-group-message cofx chat-id send-record) - send-record)] - (fx/merge cofx - (chat-model/upsert-chat - {:chat-id chat-id - :timestamp now - :last-message-timestamp (:timestamp message-with-content) - :last-message-content (:content message-with-content) - :last-message-content-type (:content-type message-with-content) - :last-clock-value (:clock-value message-with-content)}) - (send chat-id message-with-content wrapped-record)))) - -(fx/defn upsert-and-send - [{:keys [now] :as cofx} {:keys [chat-id from] :as message}] - (let [message (remove-icon message) - message (assoc message :outgoing-status :sending)] - - (prepare-message-content cofx chat-id message))) - (fx/defn update-message-status [{:keys [db] :as cofx} chat-id message-id status] (fx/merge cofx {:db (assoc-in db [:chats chat-id :messages message-id :outgoing-status] status)} - (messages-store/update-outgoing-status message-id status))) + (data-store.messages/update-outgoing-status message-id status))) (fx/defn resend-message - [cofx chat-id message-id] - (let [message (get-in cofx [:db :chats chat-id :messages message-id]) - send-record (-> message - (select-keys transport-keys) - (update :message-type keyword) - protocol/map->Message) - - wrapped-record (if (= (:message-type send-record) :group-user-message) - (wrap-group-message cofx chat-id send-record) - send-record)] - (fx/merge cofx - (send chat-id message wrapped-record) - (update-message-status chat-id message-id :sending)))) + [{:keys [db] :as cofx} chat-id message-id] + (fx/merge cofx + {::json-rpc/call [{:method "shhext_reSendChatMessage" + :params [message-id] + :on-success #(log/debug "re-sent message successfully") + :on-error #(log/error "failed to re-send message" %)}]} + (update-message-status chat-id message-id :sending))) (fx/defn rebuild-message-list [{:keys [db]} chat-id] @@ -317,35 +183,26 @@ [{:keys [db] :as cofx} chat-id message-id] (fx/merge cofx {:db (update-in db [:chats chat-id :messages] dissoc message-id)} - (messages-store/delete-message message-id) + (data-store.messages/delete-message message-id) (rebuild-message-list chat-id))) +(fx/defn handle-saved-system-messages + {:events [:messages/system-messages-saved]} + [cofx messages] + (apply fx/merge cofx (map #(add-message {:message % + :current-chat? true}) + messages))) + (fx/defn add-system-messages [cofx messages] - (let [messages-fx (map #(add-message - {:batch false - :message (system-message cofx %) - :current-chat? true}) - messages)] - (apply fx/merge cofx messages-fx))) + (data-store.messages/save-system-messages cofx messages)) (fx/defn send-message [{:keys [db now] :as cofx} {:keys [chat-id] :as message}] (let [{:keys [chats]} db - {:keys [last-clock-value] :as chat} (get chats chat-id) message-data (-> message - (assoc :from (multiaccounts.model/current-public-key cofx) - :timestamp now - :whisper-timestamp now - :clock-value (utils.clocks/send - last-clock-value)) - (tribute-to-talk/add-transaction-hash db) - (add-message-type chat))] - (upsert-and-send cofx message-data))) + (tribute-to-talk/add-transaction-hash db))] + (protocol/send-chat-message cofx message-data))) (fx/defn toggle-expand-message [{:keys [db]} chat-id message-id] {:db (update-in db [:chats chat-id :messages message-id :expanded?] not)}) - -(fx/defn confirm-message-processed - [_ raw-message] - {:transport/confirm-messages-processed [raw-message]}) diff --git a/src/status_im/chat/models/message_list.cljs b/src/status_im/chat/models/message_list.cljs index 499c4cb175..338a94903a 100644 --- a/src/status_im/chat/models/message_list.cljs +++ b/src/status_im/chat/models/message_list.cljs @@ -2,6 +2,7 @@ (:require [status-im.js-dependencies :as dependencies] [taoensso.timbre :as log] + [status-im.constants :as constants] [status-im.utils.fx :as fx] [status-im.chat.db :as chat.db] [status-im.utils.datetime :as time])) @@ -20,12 +21,13 @@ whisper-timestamp]}] (-> {:whisper-timestamp whisper-timestamp :from from - :one-to-one? (= :user-message message-type) - :system-message? (= :system-message message-type) + :one-to-one? (= constants/message-type-one-to-one message-type) + :system-message? (= constants/message-type-private-group-system-message + message-type) :clock-value clock-value :type :message :message-id message-id - :outgoing outgoing} + :outgoing (boolean outgoing)} add-datemark add-timestamp)) @@ -65,7 +67,8 @@ We divide messages in groups. Messages are sorted descending so :first? is the most recent message, similarly :first-in-group? is the most recent message in a group." - [{:keys [one-to-one? outgoing] :as current-message} + [{:keys [system-message? + one-to-one? outgoing] :as current-message} {:keys [outgoing-seen?] :as previous-message} next-message] (let [last-in-group? (or (nil? next-message) @@ -80,6 +83,7 @@ (not (same-group? current-message previous-message))) :last-in-group? last-in-group? :display-username? (and last-in-group? + (not system-message?) (not outgoing) (not one-to-one?)) :display-photo? (display-photo? current-message)))) @@ -100,10 +104,13 @@ (defn update-previous-message "If this is a new group, we mark the previous as the last one in the group" - [current-message {:keys [one-to-one? outgoing] :as previous-message}] + [current-message {:keys [one-to-one? + system-message? + outgoing] :as previous-message}] (let [last-in-group? (not (same-group? current-message previous-message))] (assoc previous-message :display-username? (and last-in-group? + (not system-message?) (not outgoing) (not one-to-one?)) :last-in-group? last-in-group?))) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index c85db9084f..a8613666a4 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -7,10 +7,15 @@ (def ms-in-bg-for-require-bioauth 5000) -(def content-type-text "text/plain") -(def content-type-sticker "sticker") -(def content-type-status "status") -(def content-type-emoji "emoji") +(def content-type-text 0) +(def content-type-sticker 1) +(def content-type-status 2) +(def content-type-emoji 3) + +(def message-type-one-to-one 0) +(def message-type-public-group 1) +(def message-type-private-group 2) +(def message-type-private-group-system-message 3) (def desktop-content-types #{content-type-text content-type-emoji content-type-status}) diff --git a/src/status_im/contact/block.cljs b/src/status_im/contact/block.cljs index 072191d80d..71fb24f3b0 100644 --- a/src/status_im/contact/block.cljs +++ b/src/status_im/contact/block.cljs @@ -23,9 +23,7 @@ public-key {:keys [chat-id unviewed-messages-count - last-message-content - last-message-timestamp - last-message-content-type]}] + last-message]}] (let [removed-messages-ids (keep (fn [[message-id {:keys [from]}]] (when (= from public-key) @@ -41,9 +39,7 @@ (update-in [:chats chat-id] assoc :unviewed-messages-count unviewed-messages-count - :last-message-content last-message-content - :last-message-timestamp last-message-timestamp - :last-message-content-type last-message-content-type))] + :last-message last-message))] {:db (update-in db [:chats chat-id :message-list] message-list/add-many (vals (get-in db [:chats chat-id :messages])))})) (fx/defn contact-blocked diff --git a/src/status_im/contact/db.cljs b/src/status_im/contact/db.cljs index f024285f77..b160301f28 100644 --- a/src/status_im/contact/db.cljs +++ b/src/status_im/contact/db.cljs @@ -115,7 +115,7 @@ (->> members (map #(or (get all-contacts %) (public-key->new-contact %))) - (sort-by (comp clojure.string/lower-case :name)) + (sort-by (comp clojure.string/lower-case #(or (:name %) (:alias %)))) (map #(if (get admins (:public-key %)) (assoc % :admin? true) %))))) diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index d830c2d621..3445f1e151 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -111,13 +111,11 @@ type->rpc marshal-members (update :membership-updates marshal-membership-updates) - (utils/update-if-present :last-message-content messages/prepare-content) + (update :last-message messages/->rpc) (clojure.set/rename-keys {:chat-id :id :membership-updates :membershipUpdates :unviewed-messages-count :unviewedMessagesCount - :last-message-content :lastMessageContent - :last-message-content-type :lastMessageContentType - :last-message-timestamp :lastMessageTimestamp + :last-message :lastMessage :deleted-at-clock-value :deletedAtClockValue :is-active :active :last-clock-value :lastClockValue}) @@ -133,16 +131,13 @@ unmarshal-members (clojure.set/rename-keys {:id :chat-id :membershipUpdates :membership-updates - :unviewedMessagesCount :unviewed-messages-count - :lastMessageContent :last-message-content - :lastMessageContentType :last-message-content-type - :lastMessageTimestamp :last-message-timestamp :deletedAtClockValue :deleted-at-clock-value + :unviewedMessagesCount :unviewed-messages-count + :lastMessage :last-message :active :is-active :lastClockValue :last-clock-value}) (update :membership-updates (partial unmarshal-membership-updates (:id chat))) - (update :last-message-content utils/safe-read-message-content) - (update :last-clock-value utils.clocks/safe-timestamp) + (update :last-message #(when % (messages/<-rpc %))) (dissoc :chatType :members))) (fx/defn save-chat [cofx {:keys [chat-id] :as chat}] diff --git a/src/status_im/data_store/messages.cljs b/src/status_im/data_store/messages.cljs index a2d2aaac49..83b3277d17 100644 --- a/src/status_im/data_store/messages.cljs +++ b/src/status_im/data_store/messages.cljs @@ -10,55 +10,37 @@ [status-im.constants :as constants] [status-im.utils.core :as utils])) -(defn prepare-content [content] - (if (string? content) +(defn ->rpc [{:keys [content] :as message}] + (cond-> message content - (utils.types/clj->json content))) - -(defn ->rpc [message] - (-> message - (dissoc :dedup-id) - (update :message-type name) - (update :outgoing-status #(if % (name %) "")) - (utils/update-if-present :content prepare-content) - (clojure.set/rename-keys {:message-id :id - :whisper-timestamp :whisperTimestamp - :message-type :messageType - :chat-id :chatId - :content-type :contentType - :clock-value :clockValue - :outgoing-status :outgoingStatus}) - (assoc :replyTo (get-in message [:content :response-to])))) - -(defn update-quoted-message [message] - (let [parsed-content (utils/safe-read-message-content (get-in message [:quotedMessage :content]))] - (cond-> message - parsed-content - (assoc :quoted-message {:from (get-in message [:quotedMessage :from]) - :text (:text parsed-content)}) - :always - (dissoc message :quotedMessage)))) + (assoc :text (:text content) + :sticker (:sticker content)) + :always + (clojure.set/rename-keys {:chat-id :chatId + :clock-value :clock}))) (defn <-rpc [message] - (when-let [parsed-content (utils/safe-read-message-content (:content message))] - (let [outgoing-status (when-not (empty? (:outgoingStatus message)) - (keyword (:outgoingStatus message)))] + (-> message + (clojure.set/rename-keys {:id :message-id + :whisperTimestamp :whisper-timestamp + :messageType :message-type + :localChatId :chat-id + :contentType :content-type + :clock :clock-value + :quotedMessage :quoted-message + :outgoingStatus :outgoing-status}) - (-> message - (update :messageType keyword) - (update :outgoingStatus keyword) - (assoc :content parsed-content - :outgoingStatus outgoing-status - :outgoing outgoing-status) - (update-quoted-message) - (clojure.set/rename-keys {:id :message-id - :whisperTimestamp :whisper-timestamp - :messageType :message-type - :chatId :chat-id - :contentType :content-type - :replyTo :reply-to - :clockValue :clock-value - :outgoingStatus :outgoing-status}))))) + (update :outgoing-status keyword) + (assoc :content {:chat-id (:chatId message) + :text (:text message) + :sticker (:sticker message) + :ens-name (:ensName message) + :line-count (:lineCount message) + :parsed-text (:parsedText message) + :rtl (:rtl message) + :response-to (:responseTo message)} + :outgoing (boolean (:outgoingStatus message))) + (dissoc :ensName :chatId :text :rtl :responseTo :sticker :lineCount :parsedText))) (defn update-outgoing-status-rpc [message-id status] {::json-rpc/call [{:method "shhext_updateMessageOutgoingStatus" @@ -66,12 +48,11 @@ :on-success #(log/debug "updated message outgoing stauts" message-id status) :on-failure #(log/error "failed to update message outgoing status" message-id status %)}]}) -(defn save-messages-rpc [messages] - (let [confirmations (keep :metadata messages)] - (json-rpc/call {:method "shhext_saveMessages" - :params [(map ->rpc messages)] - :on-success #(re-frame/dispatch [:message/messages-persisted confirmations]) - :on-failure #(log/error "failed to save messages" %)}))) +(defn save-system-messages-rpc [messages] + (json-rpc/call {:method "shhext_addSystemMessages" + :params [(map ->rpc messages)] + :on-success #(re-frame/dispatch [:messages/system-messages-saved (map <-rpc %)]) + :on-failure #(log/error "failed to save messages" %)})) (defn messages-by-chat-id-rpc [chat-id cursor limit on-success] {::json-rpc/call [{:method "shhext_chatMessages" @@ -80,9 +61,9 @@ (on-success (update result :messages #(map <-rpc %)))) :on-failure #(log/error "failed to get messages" %)}]}) -(defn mark-seen-rpc [ids] +(defn mark-seen-rpc [chat-id ids] {::json-rpc/call [{:method "shhext_markMessagesSeen" - :params [ids] + :params [chat-id ids] :on-success #(log/debug "successfully marked as seen") :on-failure #(log/error "failed to get messages" %)}]}) @@ -105,28 +86,12 @@ :on-failure #(log/error "failed to delete messages by chat-id" % chat-id)}]}) (re-frame/reg-fx - ::save-message + ::save-system-message (fn [messages] - (save-messages-rpc messages))) + (save-system-messages-rpc messages))) -(fx/defn save-messages [{:keys [db]}] - (when-let [messages (vals (:messages/stored db))] - ;; Pull message from database to pick up most recent changes, default to - ;; stored one in case it has been offloaded - (let [hydrated-messages (map #(get-in db [:chats (-> % :content :chat-id) :messages (:message-id %)] %) messages)] - {:db (dissoc db :messages/stored) - ::save-message hydrated-messages}))) - -(fx/defn handle-save-messages - {:events [::save-messages]} - [cofx] - (save-messages cofx)) - -(fx/defn save-message [{:keys [db]} {:keys [message-id] :as message}] - {:db (assoc-in db [:messages/stored message-id] message) - :dispatch-debounce [{:key :save-messages - :event [::save-messages] - :delay 500}]}) +(fx/defn save-system-messages [cofx messages] + {::save-system-message messages}) (fx/defn delete-message [cofx id] (delete-message-rpc id)) @@ -134,8 +99,8 @@ (fx/defn delete-messages-from [cofx author] (delete-messages-from-rpc author)) -(fx/defn mark-messages-seen [_ ids] - (mark-seen-rpc ids)) +(fx/defn mark-messages-seen [_ chat-id ids] + (mark-seen-rpc chat-id ids)) (fx/defn update-outgoing-status [cofx message-id status] (update-outgoing-status-rpc message-id status)) diff --git a/src/status_im/ens/core.cljs b/src/status_im/ens/core.cljs index 4534c806fc..a1276a7d08 100644 --- a/src/status_im/ens/core.cljs +++ b/src/status_im/ens/core.cljs @@ -291,8 +291,8 @@ (stateofus/valid-username? ens-name)))) (fx/defn verify-names-from-message [cofx {:keys [content]} signature] - (when (should-be-verified? cofx (:name content) signature) - {::verify-names [{:name (:name content) + (when (should-be-verified? cofx (:ens-name content) signature) + {::verify-names [{:name (:ens-name content) :publicKey (subs signature 2)}]})) (fx/defn verify-names-from-contact-request [cofx {:keys [name]} signature] diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index 09f1c5ef4e..ab5cb83701 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -35,13 +35,15 @@ "shhext_sendPublicMessage" {} "shhext_enableInstallation" {} "shhext_disableInstallation" {} + "shhext_sendChatMessage" {} + "shhext_reSendChatMessage" {} "shhext_getOurInstallations" {} "shhext_setInstallationMetadata" {} "shhext_loadFilters" {} "shhext_loadFilter" {} "shhext_removeFilters" {} "shhext_chats" {} - "shhext_saveMessages" {} + "shhext_addSystemMessages" {} "shhext_deleteMessagesFrom" {} "shhext_deleteMessagesByChatID" {} "shhext_deleteMessage" {} diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index aaf8e24f21..188b3e0164 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -3,7 +3,7 @@ [re-frame.core :as re-frame] [status-im.multiaccounts.core :as multiaccounts] [status-im.data-store.messages :as data-store.messages] - + [status-im.data-store.chats :as data-store.chats] [status-im.multiaccounts.create.core :as multiaccounts.create] [status-im.multiaccounts.login.core :as multiaccounts.login] [status-im.multiaccounts.logout.core :as multiaccounts.logout] @@ -172,10 +172,7 @@ (handlers/register-handler-fx :multiaccounts.logout.ui/logout-confirmed (fn [cofx _] - (fx/merge - cofx - (data-store.messages/save-messages) - (multiaccounts.logout/logout)))) + (multiaccounts.logout/logout cofx))) ;; multiaccounts update module @@ -551,8 +548,12 @@ (handlers/register-handler-fx :chat.ui/resend-message - (fn [cofx [_ chat-id message-id]] - (chat.message/resend-message cofx chat-id message-id))) + (fn [{:keys [db] :as cofx} [_ chat-id message-id]] + (let [message (get-in db [:chats chat-id :messages message-id])] + (fx/merge + cofx + (transport.message/set-message-envelope-hash chat-id message-id (:message-type message) 1) + (chat.message/resend-message chat-id message-id))))) (handlers/register-handler-fx :chat.ui/delete-message @@ -616,16 +617,6 @@ (fn [cofx [_ chat-id message-id status]] (chat.message/update-message-status cofx chat-id message-id status))) -(handlers/register-handler-fx - :message/messages-persisted - (fn [cofx [_ raw-messages]] - (apply fx/merge - cofx - (map - (fn [raw-message] - (chat.message/confirm-message-processed raw-message)) - raw-messages)))) - ;; signal module (handlers/register-handler-fx @@ -1204,13 +1195,20 @@ (fn [{:keys [db] :as cofx} [_ err]] (log/error :send-status-message-error err))) +(fx/defn handle-update [cofx {:keys [chats messages] :as response}] + (let [chats (map data-store.chats/<-rpc chats) + messages (map data-store.messages/<-rpc messages) + message-fxs (map chat.message/receive-one messages) + chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)] + (apply fx/merge cofx (concat chat-fxs message-fxs)))) + (handlers/register-handler-fx :transport/message-sent - (fn [cofx [_ chat-id message message-type message-id messages-count]] - (fx/merge cofx - (when message (chat.message/add-message-with-id (assoc message :message-id message-id) chat-id)) - - (transport.message/set-message-envelope-hash chat-id message-id message-type messages-count)))) + (fn [cofx [_ response messages-count]] + (let [{:keys [localChatId id messageType]} (-> response :messages first)] + (fx/merge cofx + (handle-update response) + (transport.message/set-message-envelope-hash localChatId id messageType messages-count))))) (handlers/register-handler-fx :transport/contact-message-sent diff --git a/src/status_im/group_chats/core.cljs b/src/status_im/group_chats/core.cljs index aba7602c69..1a5a2b7d0b 100644 --- a/src/status_im/group_chats/core.cljs +++ b/src/status_im/group_chats/core.cljs @@ -5,6 +5,7 @@ [clojure.string :as string] [status-im.ethereum.json-rpc :as json-rpc] [re-frame.core :as re-frame] + [status-im.constants :as constants] [status-im.chat.models.message-content :as message-content] [status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.model :as multiaccounts.model] @@ -141,7 +142,7 @@ :success-event [:transport/message-sent chat-id (:message cofx) - :group-user-message] + constants/message-type-private-group] :payload payload}})))) (fx/defn handle-membership-update-received @@ -374,7 +375,7 @@ (let [get-contact (partial models.contact/build-contact cofx) format-message (fn [contact text clock-value] {:chat-id chat-id - :content {:text text} + :text text :clock-value clock-value :from (:public-key contact)}) creator-contact (when creator (get-contact creator)) @@ -465,43 +466,6 @@ (transport.filters/upsert-group-chat-topics) (transport.filters/load-members members))))) -(fx/defn prepared-message - {:events [::prepared-message]} - [{:keys [now] :as cofx} - chat-id message - content - sender-signature - whisper-timestamp - metadata] - (let [message-with-content - (update message :content - assoc - :parsed-text (:parsedText content) - :line-count (:lineCount content) - :should-collapse? (message-content/should-collapse? - (:text content) - (:lineCount content)) - :rtl? (:rtl content))] - (protocol/receive message-with-content - chat-id - sender-signature - whisper-timestamp - (assoc cofx :metadata metadata)))) - -(fx/defn prepare-message-content - [cofx chat-id message sender-signature whisper-timestamp metadata] - {::json-rpc/call - [{:method "shhext_prepareContent" - :params [(:content message)] - :on-success #(re-frame/dispatch [::prepared-message - chat-id - message - % - sender-signature - whisper-timestamp - metadata]) - :on-failure #(log/error "failed to prepare content" %)}]}) - (fx/defn handle-membership-update "Upsert chat and receive message if valid" ;; Care needs to be taken here as chat-id is not coming from a whisper filter @@ -532,18 +496,7 @@ :contacts (:contacts new-group)}) (add-system-messages chat-id previous-chat new-group) - (set-up-filter chat-id previous-chat) - #(when (and message - ;; don't allow anything but group messages - (instance? protocol/Message message) - (= :group-user-message (:message-type message))) - (prepare-message-content - % - chat-id - message - sender-signature - whisper-timestamp - metadata))))))) + (set-up-filter chat-id previous-chat)))))) (defn handle-sign-success "Upsert chat and send signed payload to group members" diff --git a/src/status_im/mailserver/core.cljs b/src/status_im/mailserver/core.cljs index f31a5c7319..487b6fb55b 100644 --- a/src/status_im/mailserver/core.cljs +++ b/src/status_im/mailserver/core.cljs @@ -11,12 +11,12 @@ [status-im.multiaccounts.model :as multiaccounts.model] [status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.native-module.core :as status] - [status-im.transport.message.protocol :as protocol] [status-im.transport.utils :as transport.utils] [status-im.ui.screens.mobile-network-settings.utils :as mobile-network-utils] [status-im.ui.screens.navigation :as navigation] + [status-im.utils.config :as config] [status-im.utils.fx :as fx] [status-im.utils.handlers :as handlers] [status-im.utils.platform :as platform] @@ -43,6 +43,14 @@ (defn connected? [db id] (= (:mailserver/current-id db) id)) +(def whisper-opts + {;; time drift that is tolerated by whisper, in seconds + :whisper-drift-tolerance 10 + ;; ttl of 10 sec + :ttl 10 + :powTarget config/pow-target + :powTime config/pow-time}) + (defn fetch [db id] (get-in db [:mailserver/mailservers (node/current-fleet-key db) id])) @@ -297,8 +305,8 @@ (defn adjust-request-for-transit-time [from] - (let [ttl (:ttl protocol/whisper-opts) - whisper-tolerance (:whisper-drift-tolerance protocol/whisper-opts) + (let [ttl (:ttl whisper-opts) + whisper-tolerance (:whisper-drift-tolerance whisper-opts) adjustment (+ whisper-tolerance ttl) adjusted-from (- (max from adjustment) adjustment)] (log/debug "Adjusting mailserver request" "from:" from diff --git a/src/status_im/mailserver/topics.cljs b/src/status_im/mailserver/topics.cljs index c152dc7726..d02e116954 100644 --- a/src/status_im/mailserver/topics.cljs +++ b/src/status_im/mailserver/topics.cljs @@ -115,8 +115,11 @@ "return all the topics for this chat, including discovery topics if specified" [topics chat-id include-discovery?] (reduce-kv - (fn [acc topic {:keys [discovery? chat-ids]}] - (if (or (and discovery? + (fn [acc topic {:keys [negotiated? + discovery? + chat-ids]}] + (if (or (and (or discovery? + negotiated?) include-discovery?) (chat-ids chat-id)) (conj acc topic) diff --git a/src/status_im/pairing/core.cljs b/src/status_im/pairing/core.cljs index d8ea113197..53fab56ea1 100644 --- a/src/status_im/pairing/core.cljs +++ b/src/status_im/pairing/core.cljs @@ -4,6 +4,7 @@ [status-im.chat.models :as models.chat] [status-im.contact.core :as contact] [status-im.contact.db :as contact.db] + [taoensso.timbre :as log] [status-im.ethereum.json-rpc :as json-rpc] [status-im.i18n :as i18n] [status-im.multiaccounts.model :as multiaccounts.model] @@ -70,10 +71,6 @@ device-type utils.platform/os] (protocol/send (transport.pairing/PairInstallation. installation-id device-type installation-name nil) nil cofx))) -(fx/defn confirm-message-processed - [cofx confirmation] - {:transport/confirm-messages-processed [confirmation]}) - (defn send-pair-installation [cofx payload] (let [current-public-key (multiaccounts.model/current-public-key cofx)] @@ -321,8 +318,8 @@ (defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account chat]} sender] (let [confirmation (:metadata cofx)] - (if (= sender (multiaccounts.model/current-public-key cofx)) - (let [on-success #(re-frame/dispatch [:message/messages-persisted [confirmation]]) + (when (= sender (multiaccounts.model/current-public-key cofx)) + (let [on-success #(log/debug "handled sync installation successfully") new-contacts (when (seq contacts) (vals (merge-contacts (:contacts/contacts db) ((comp ensure-photo-path @@ -339,17 +336,15 @@ :on-success on-success}]} #(when (:public? chat) (models.chat/start-public-chat % (:chat-id chat) {:dont-navigate? true}))] - contacts-fx))) - (confirm-message-processed cofx confirmation)))) + contacts-fx)))))) (defn handle-pair-installation [{:keys [db] :as cofx} {:keys [name installation-id device-type]} timestamp sender] - (if (and (= sender (multiaccounts.model/current-public-key cofx)) - (not= (get-in db [:multiaccount :installation-id]) installation-id)) + (when (and (= sender (multiaccounts.model/current-public-key cofx)) + (not= (get-in db [:multiaccount :installation-id]) installation-id)) {:pairing/set-installation-metadata [[installation-id {:name name - :deviceType device-type}]]} - (confirm-message-processed cofx (:metadata cofx)))) + :deviceType device-type}]]})) (fx/defn update-installation [{:keys [db]} installation-id metadata] {:db (update-in db [:pairing/installations installation-id] diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 35e73989e9..ed92246849 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -62,6 +62,6 @@ "subscriptions.data" (ethereum.subscriptions/handle-signal cofx (js->clj event-js :keywordize-keys true)) "subscriptions.error" (ethereum.subscriptions/handle-error cofx (js->clj event-js :keywordize-keys true)) "whisper.filter.added" (transport.filters/handle-negotiated-filter cofx (js->clj event-js :keywordize-keys true)) - "messages.new" (transport.message/receive-messages cofx event-js) + "messages.new" (transport.message/process-response cofx event-js) "wallet" (ethereum.subscriptions/new-wallet-event cofx (js->clj event-js :keywordize-keys true)) (log/debug "Event " type " not handled")))) diff --git a/src/status_im/transport/db.cljs b/src/status_im/transport/db.cljs index b1e2c54350..c437fa04a3 100644 --- a/src/status_im/transport/db.cljs +++ b/src/status_im/transport/db.cljs @@ -60,7 +60,7 @@ (spec/def ::content-type #{constants/content-type-text constants/content-type-emoji constants/content-type-sticker}) -(spec/def ::message-type #{:group-user-message :public-group-user-message :user-message}) +(spec/def ::message-type #{constants/message-type-private-group constants/message-type-public-group constants/message-type-one-to-one}) (spec/def ::clock-value (spec/and pos-int? utils.clocks/safe-timestamp?)) (spec/def ::timestamp (spec/nilable pos-int?)) diff --git a/src/status_im/transport/filters/core.cljs b/src/status_im/transport/filters/core.cljs index 2dda9128ff..49ce298842 100644 --- a/src/status_im/transport/filters/core.cljs +++ b/src/status_im/transport/filters/core.cljs @@ -238,7 +238,7 @@ (fx/defn handle-negotiated-filter "Check if it's a new filter, if so create an shh filter and process it" [{:keys [db] :as cofx} {:keys [filters]}] - (let [processed-filters (map responses->filters filters) + (let [processed-filters (map #(responses->filters (assoc % :negotiated true)) filters) new-filters (filter (partial not-loaded? db) processed-filters)] diff --git a/src/status_im/transport/message/core.cljs b/src/status_im/transport/message/core.cljs index aff9a490a3..be8be6e17a 100644 --- a/src/status_im/transport/message/core.cljs +++ b/src/status_im/transport/message/core.cljs @@ -3,6 +3,10 @@ (:require [goog.object :as o] [re-frame.core :as re-frame] [status-im.chat.models.message :as models.message] + [status-im.chat.models :as models.chat] + [status-im.data-store.messages :as data-store.messages] + [status-im.data-store.chats :as data-store.chats] + [status-im.constants :as constants] [status-im.utils.handlers :as handlers] [status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.core :as ethereum] @@ -12,77 +16,34 @@ [status-im.transport.message.transit :as transit] [status-im.transport.utils :as transport.utils] [status-im.tribute-to-talk.whitelist :as whitelist] + [status-im.ens.core :as ens] [cljs-bean.core :as clj-bean] [status-im.utils.config :as config] [status-im.utils.fx :as fx] [taoensso.timbre :as log] [status-im.ethereum.json-rpc :as json-rpc])) -(def message-type-message 1) - -(defn build-content [content-js] - {:text (.-text content-js) - :line-count (.-lineCount content-js) - :parsed-text (clj-bean/->clj (.-parsedText content-js)) - :name (.-name content-js) - :rtl? (.-rtl content-js) - :response-to (aget content-js "response-to") - :chat-id (.-chat_id content-js)}) - -(defn build-message [parsed-message-js] - (let [content (.-content parsed-message-js) - built-message - (protocol/Message. - (build-content content) - (.-content_type parsed-message-js) - (keyword (.-message_type parsed-message-js)) - (.-clock parsed-message-js) - (.-timestamp parsed-message-js))] - built-message)) - -(defn handle-message - "Check if parsedMessage is present and of a supported type, if so - build a record using the right type. Otherwise defaults to transit - deserializing" - [message-js] - (if (and (.-parsedMessage message-js) - (= message-type-message (.-messageType message-js))) - (build-message (.-parsedMessage message-js)) - (transit/deserialize (.-payload message-js)))) - -(fx/defn receive-message +(fx/defn handle-raw-message "Receive message handles a new status-message. dedup-id is passed by status-go and is used to deduplicate messages at that layer. Once a message has been successfuly processed, that id needs to be sent back in order to stop receiving that message" - [{:keys [db] :as cofx} now-in-s filter-chat-id message-js] - (let [blocked-contacts (get db :contacts/blocked #{}) - timestamp (.-timestamp (.-message message-js)) - metadata-js (.-metadata message-js) - metadata {:author {:publicKey (.-publicKey (.-author metadata-js)) - :alias (.-alias (.-author metadata-js)) - :identicon (.-identicon (.-author metadata-js))} - :dedupId (.-dedupId metadata-js) - :encryptionId (.-encryptionId metadata-js) - :messageId (.-messageId metadata-js)} - status-message (handle-message message-js) - sig (-> metadata :author :publicKey)] - (when (and sig - status-message - (not (blocked-contacts sig))) - (try - (when-let [valid-message (protocol/validate status-message)] - (protocol/receive - (assoc valid-message - :metadata metadata) - (or - filter-chat-id - (get-in valid-message [:content :chat-id]) - sig) - sig - timestamp - (assoc cofx :metadata metadata))) - (catch :default e nil))))) ; ignore unknown message types + [{:keys [db] :as cofx} raw-message-js] + (let [timestamp (.-timestamp raw-message-js) + sig (.-from raw-message-js) + payload (.-payload raw-message-js)] + (let [status-message (transit/deserialize payload)] + (when (and sig + status-message) + (try + (when-let [valid-message (protocol/validate status-message)] + (protocol/receive + valid-message + sig + sig + timestamp + cofx)) + (catch :default e nil)))))) ; ignore unknown message types (defn- js-obj->seq [obj] ;; Sometimes the filter will return a single object instead of a collection @@ -91,42 +52,48 @@ (aget obj i)) [obj])) +(fx/defn handle-chat [cofx chat] + ;; :unviewed-messages-count is managed by status-react, so we don't copy + ;; over it + (models.chat/ensure-chat cofx (dissoc chat :unviewed-messages-count))) + +(fx/defn handle-message-2 [cofx message] + (fx/merge cofx + (models.message/receive-one message) + (ens/verify-names-from-message message (:from message)))) + +(fx/defn process-response [cofx response-js] + (let [chats (.-chats response-js) + raw-messages (.-rawMessages response-js) + messages (.-messages response-js)] + (cond + (seq chats) + (let [chat (.pop chats)] + (fx/merge cofx + {:dispatch-later [{:ms 20 :dispatch [::process response-js]}]} + (handle-chat (-> chat (clj-bean/->clj) (data-store.chats/<-rpc))))) + (seq raw-messages) + (let [first-filter (aget raw-messages 0) + messages (.-messages first-filter) + first-message (.pop messages)] + ;; Pop the empty array + (when (= (.-length messages) 0) + (.pop raw-messages)) + (when first-message + (fx/merge cofx + {:dispatch-later [{:ms 20 :dispatch [::process response-js]}]} + (handle-raw-message first-message)))) + + (seq messages) + (let [message (.pop messages)] + (fx/merge cofx + {:dispatch-later [{:ms 20 :dispatch [::process response-js]}]} + (handle-message-2 (-> message (clj-bean/->clj) (data-store.messages/<-rpc)))))))) + (handlers/register-handler-fx ::process - (fn [cofx [_ messages now-in-s]] - (let [[chat-id message] (first messages) - remaining-messages (rest messages)] - (if (seq remaining-messages) - (assoc - (receive-message cofx now-in-s chat-id message) - ;; We dispatch later to let the UI thread handle events, without this - ;; it will keep processing events ignoring user input. - :dispatch-later [{:ms 20 :dispatch [::process remaining-messages now-in-s]}]) - (receive-message cofx now-in-s chat-id message))))) - -(fx/defn receive-messages - "Initialize the ::process event, which will process messages one by one - dispatching later to itself" - [{:keys [now] :as cofx} event-js] - (let [now-in-s (quot now 1000) - events (reduce - (fn [acc message-specs] - (let [chat (.-chat message-specs) - messages (.-messages message-specs) - error (.-error message-specs) - chat-id (if (or (.-discovery chat) - (.-negotiated chat)) - nil - (.-chatId chat))] - (if (seq messages) - (reduce (fn [acc m] - (conj acc [chat-id m])) - acc - messages) - acc))) - [] - (.-messages event-js))] - {:dispatch [::process events now-in-s]})) + (fn [cofx [_ response-js]] + (process-response cofx response-js))) (fx/defn remove-hash [{:keys [db] :as cofx} envelope-hash] @@ -192,16 +159,3 @@ {:name name :profile-image photo-path :address address})) - -(fx/defn resend-contact-request [cofx own-info chat-id {:keys [sym-key topic]}] - (protocol/send (contact/map->ContactRequest own-info) - chat-id cofx)) - -(re-frame/reg-fx - :transport/confirm-messages-processed - (fn [confirmations] - (when (seq confirmations) - (json-rpc/call {:method "shhext_confirmMessagesProcessedByID" - :params [confirmations] - :on-success #(log/debug "successfully confirmed messages") - :on-failure #(log/error "failed to confirm messages" %)})))) diff --git a/src/status_im/transport/message/protocol.cljs b/src/status_im/transport/message/protocol.cljs index f240aaeca6..099e8e4d6f 100644 --- a/src/status_im/transport/message/protocol.cljs +++ b/src/status_im/transport/message/protocol.cljs @@ -2,32 +2,24 @@ status-im.transport.message.protocol (:require [cljs.spec.alpha :as spec] [status-im.multiaccounts.model :as multiaccounts.model] + [re-frame.core :as re-frame] + [status-im.data-store.messages :as data-store.messages] + [status-im.ethereum.json-rpc :as json-rpc] [status-im.constants :as constants] [status-im.ethereum.core :as ethereum] [status-im.transport.db :as transport.db] [status-im.utils.pairing :as pairing.utils] [status-im.transport.utils :as transport.utils] [status-im.tribute-to-talk.whitelist :as whitelist] - [status-im.utils.config :as config] [status-im.utils.fx :as fx] [taoensso.timbre :as log])) -(defn discovery-topic-hash [] (transport.utils/get-topic constants/contact-discovery)) - (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 timestamp cofx] "Method producing all effects necessary for receiving the message record") (validate [this] "Method returning the message if it is valid or nil if it is not")) -(def whisper-opts - {;; time drift that is tolerated by whisper, in seconds - :whisper-drift-tolerance 10 - ;; ttl of 10 sec - :ttl 10 - :powTarget config/pow-target - :powTime config/pow-time}) - (defn send-public-message "Sends the payload to topic" [cofx chat-id success-event payload] @@ -52,24 +44,28 @@ success-event payload)) +(fx/defn send-chat-message [_ {:keys [chat-id + text + response-to + ens-name + message-type + sticker + content-type] + :as message}] + {::json-rpc/call [{:method "shhext_sendChatMessage" + :params [{:chatId chat-id + :text text + :responseTo response-to + :ensName ens-name + :sticker sticker + :contentType content-type}] + :on-success + #(re-frame/dispatch [:transport/message-sent % 1]) + :on-failure #(log/error "failed to send a message" %)}]}) + (defrecord Message [content content-type message-type clock-value timestamp] StatusMessage - (send [this chat-id {:keys [message] :as cofx}] - (let [current-public-key (multiaccounts.model/current-public-key cofx) - params {:chat-id chat-id - :payload this - :success-event [:transport/message-sent - chat-id - message - message-type]}] - (case message-type - :public-group-user-message - (send-public-message cofx chat-id (:success-event params) this) - :user-message - (fx/merge cofx - (when (pairing.utils/has-paired-installations? cofx) - (send-direct-message current-public-key nil this)) - (send-with-pubkey params))))) + (send [this chat-id {:keys [message] :as cofx}]) (validate [this] (if (spec/valid? :message/message this) this diff --git a/src/status_im/transport/utils.cljs b/src/status_im/transport/utils.cljs index b7267d7893..a52f85a159 100644 --- a/src/status_im/transport/utils.cljs +++ b/src/status_im/transport/utils.cljs @@ -4,17 +4,6 @@ [status-im.ethereum.core :as ethereum] [status-im.js-dependencies :as dependencies])) -(defn system-message-id - [{:keys [from chat-id clock-value]}] - (ethereum/sha3 (str from chat-id clock-value))) - -(defn message-id - "Get a message-id by appending the hex-encoded pk of the sender to the raw-payload. - We strip 0x from the payload so web3 understand that the whole thing is to be - decoded as raw bytes" - [from raw-payload] - (ethereum/sha3 (str from (subs raw-payload 2)))) - (defn get-topic "Get the topic of a group chat or public chat from the chat-id" [chat-id] diff --git a/src/status_im/tribute_to_talk/whitelist.cljs b/src/status_im/tribute_to_talk/whitelist.cljs index 2d62f21141..c6f8985767 100644 --- a/src/status_im/tribute_to_talk/whitelist.cljs +++ b/src/status_im/tribute_to_talk/whitelist.cljs @@ -1,6 +1,7 @@ (ns status-im.tribute-to-talk.whitelist (:require [status-im.contact.db :as contact.db] [status-im.data-store.contacts :as contacts-store] + [status-im.constants :as constants] [status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.utils.fx :as fx])) @@ -61,7 +62,7 @@ along the message" [{:keys [db] :as cofx} received-message-fx message-type tribute-transaction from] ;; if it is not a user-message or the user is whitelisted it passes - (if (or (not= :user-message message-type) + (if (or (not= constants/message-type-one-to-one message-type) (contains? (:contacts/whitelist db) from)) received-message-fx ;; if ttt is disabled it passes diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 551e11f494..e57065d255 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -52,11 +52,6 @@ :number-of-lines 5} (or text (:text quote))]]))) -(defview message-content-status [{:keys [content]}] - [react/view style/status-container - [react/text {:style style/status-text} - (:text content)]]) - (defn expand-button [expanded? chat-id message-id] [react/text {:style style/message-expand-button :on-press #(re-frame/dispatch [:chat.ui/message-expand-toggled chat-id message-id])} @@ -103,6 +98,14 @@ (conj acc literal))) +(defview message-content-status [{:keys [content]}] + [react/view style/status-container + [react/text {:style style/status-text} + (reduce + (fn [acc e] (render-inline (:text content) false acc e)) + [react/text-class {:style style/status-text}] + (-> content :parsed-text peek :children))]]) + (defn render-block [{:keys [chat-id message-id content timestamp-str group-chat outgoing current-public-key expanded?] :as message} @@ -184,7 +187,7 @@ [wrapper {:keys [content] :as message}] [wrapper message [react/image {:style {:margin 10 :width 140 :height 140} - :source {:uri (contenthash/url (:hash content))}}]]) + :source {:uri (contenthash/url (-> content :sticker :hash))}}]]) (defmethod message-content :default [wrapper {:keys [content-type] :as message}] @@ -220,7 +223,7 @@ [{:keys [chat-id message-id outgoing-status first-outgoing? content message-type] :as message}] - (when (not= :system-message message-type) + (when (not= constants/message-type-private-group-system-message message-type) (case outgoing-status :sending [message-activity-indicator] :not-sent [message-not-sent-text chat-id message-id] @@ -267,25 +270,26 @@ (defn chat-message [{:keys [outgoing group-chat modal? current-public-key content-type content] :as message}] - [react/view - [react/touchable-highlight - {:on-press (fn [arg] - (if (and platform/desktop? (= "right" (.-button (.-nativeEvent arg)))) - (open-chat-context-menu message) - (do - (when (and (= content-type constants/content-type-sticker) (:pack content)) - (re-frame/dispatch [:stickers/open-sticker-pack (:pack content)])) - (re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true - :input-bottom-sheet nil}]) - (when-not platform/desktop? - (react/dismiss-keyboard!))))) - :on-long-press #(when (or (= content-type constants/content-type-text) - (= content-type constants/content-type-emoji)) - (open-chat-context-menu 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 - :group-chat group-chat - :modal? modal? - :incoming-group incoming-group})])]]]) + (let [sticker (:sticker content)] + [react/view + [react/touchable-highlight + {:on-press (fn [arg] + (if (and platform/desktop? (= "right" (.-button (.-nativeEvent arg)))) + (open-chat-context-menu message) + (do + (when (and (= content-type constants/content-type-sticker) (:pack sticker)) + (re-frame/dispatch [:stickers/open-sticker-pack (:pack sticker)])) + (re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true + :input-bottom-sheet nil}]) + (when-not platform/desktop? + (react/dismiss-keyboard!))))) + :on-long-press #(when (or (= content-type constants/content-type-text) + (= content-type constants/content-type-emoji)) + (open-chat-context-menu 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 + :group-chat group-chat + :modal? modal? + :incoming-group incoming-group})])]]])) diff --git a/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs index d76215f1d9..845f46af03 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs @@ -20,16 +20,13 @@ [{:keys [chat-id name group-chat color public? public-key timestamp chat-name - last-message-content - last-message-timestamp - last-message-content-type] + last-message] :as chat-item}] (views/letsubs [photo-path [:contacts/chat-photo chat-id] unviewed-messages-count [:chats/unviewed-messages-count chat-id] current-chat-id [:chats/current-chat-id]] - (let [last-message {:content last-message-content - :timestamp (if (pos? last-message-timestamp) last-message-timestamp timestamp) - :content-type last-message-content-type} + (let [last-message (or last-message + {:timestamp timestamp}) name (or chat-name (gfycat/generate-gfy public-key)) [unviewed-messages-label large?] [(utils/unread-messages-count unviewed-messages-count) true] @@ -60,7 +57,7 @@ [react/text {:ellipsize-mode :tail :number-of-lines 1 :style styles/chat-last-message} - (or (:text last-message-content) + (or (:text last-message) (i18n/label :no-messages-yet))]))] [react/view {:style styles/timestamp} [chat-item/message-timestamp (:timestamp last-message)] diff --git a/src/status_im/ui/screens/ens/views.cljs b/src/status_im/ui/screens/ens/views.cljs index 230c0b7e40..010b760105 100644 --- a/src/status_im/ui/screens/ens/views.cljs +++ b/src/status_im/ui/screens/ens/views.cljs @@ -3,6 +3,7 @@ [reagent.core :as reagent] [status-im.ens.core :as ens] [status-im.ethereum.core :as ethereum] + [status-im.constants :as constants] [status-im.ethereum.ens :as ethereum.ens] [status-im.ethereum.stateofus :as stateofus] [status-im.i18n :as i18n] @@ -649,7 +650,7 @@ :action-fn #(re-frame/dispatch [::ens/switch-show-username]) :value show?}]] (let [message {:content {:parsed-text [{:type "paragraph", :children [{:literal (i18n/label :t/ens-test-message)}]}]} - :content-type "text/plain" + :content-type constants/content-type-text :timestamp-str "9:41 AM"}] [react/view [react/view {:padding-left 60} diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index 0b4861dab9..6d96132a2d 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -25,7 +25,7 @@ (= constants/content-type-sticker content-type) [react/image {:style {:margin 1 :width 20 :height 20} - :source {:uri (contenthash/url (:hash content))}}] + :source {:uri (contenthash/url (-> content :sticker :hash))}}] (string/blank? (:text content)) [react/text {:style styles/last-message-text} @@ -36,14 +36,7 @@ :number-of-lines 1 :ellipsize-mode :tail :accessibility-label :chat-message-text} - (string/trim-newline (:text content))] - - :else - [react/text {:style styles/last-message-text - :number-of-lines 1 - :ellipsize-mode :tail - :accessibility-label :chat-message-text} - content])]) + (string/trim-newline (:text content))])]) (defn message-timestamp [timestamp] (when timestamp @@ -61,10 +54,8 @@ [chat-id chat-name color online group-chat public? contact - last-message-timestamp timestamp - last-message-content - last-message-content-type]} home-item + last-message]} home-item private-group? (and group-chat (not public?)) public-group? (and group-chat public?) truncated-chat-name (utils/truncate-str chat-name 30) @@ -81,14 +72,14 @@ :else nil) :title truncated-chat-name :title-accessibility-label :chat-name-text - :title-row-accessory [message-timestamp (if (pos? last-message-timestamp) - last-message-timestamp + :title-row-accessory [message-timestamp (if (pos? (:whisper-timestamp last-message)) + (:whisper-timestamp last-message) timestamp)] :subtitle (let [{:keys [tribute-status tribute-label]} (:tribute-to-talk contact)] (if (not (#{:require :pending} tribute-status)) - [message-content-text {:content last-message-content - :content-type last-message-content-type}] + [message-content-text {:content (:content last-message) + :content-type (:content-type last-message)}] tribute-label)) :subtitle-row-accessory [unviewed-indicator chat-id] :on-press #(do diff --git a/src/status_im/utils/clocks.cljs b/src/status_im/utils/clocks.cljs index 691546b00d..a403c282cf 100644 --- a/src/status_im/utils/clocks.cljs +++ b/src/status_im/utils/clocks.cljs @@ -41,7 +41,7 @@ ;; ;; But what we can do, is to use our time to make a "bid", hoping that it will ;; beat the current chat-timestamp. So our Lamport timestamp format is: -;; {unix-timestamp-ms}{2-digits-post-id} +;; {unix-timestamp-ms} ;; ;; We always need to make sure we take the max value between the last-clock-value ;; for the chat and the bid-timestamp. @@ -70,13 +70,12 @@ ;; http://amturing.acm.org/p558-lamport.pdf (def one-month-in-ms (* 60 60 24 31 1000)) -(def post-id-digits 100) (defn- ->timestamp-bid [] - (* (utils.datetime/timestamp) post-id-digits)) + (utils.datetime/timestamp)) (defn max-timestamp [] - (* (+ one-month-in-ms (utils.datetime/timestamp)) post-id-digits)) + (+ one-month-in-ms (utils.datetime/timestamp))) ; The timestamp has an upper limit of Number.MAX_SAFE_INTEGER ; A malicious client could send a crafted message with timestamp = Number.MAX_SAFE_INTEGER ; which effectively would DoS the chat, as any new message would get diff --git a/src/status_im/utils/fx.cljs b/src/status_im/utils/fx.cljs index 516674ca9b..d551acbcf2 100644 --- a/src/status_im/utils/fx.cljs +++ b/src/status_im/utils/fx.cljs @@ -12,10 +12,8 @@ cofx)) (def ^:private mergeable-keys - #{:dispatch-debounce - :filters/load-filters + #{:filters/load-filters :pairing/set-installation-metadata - :status-im.data-store.messages/save-message :status-im.ens.core/verify-names :shh/send-direct-message :shh/remove-filter diff --git a/src/status_im/utils/utils.cljs b/src/status_im/utils/utils.cljs index ffb843095b..8d9ac4bc2e 100644 --- a/src/status_im/utils/utils.cljs +++ b/src/status_im/utils/utils.cljs @@ -88,31 +88,6 @@ (when address (get-shortened-address (eip55/address->checksum (ethereum/normalized-hex address))))) -;; debounce, taken from https://github.com/johnswanson/re-frame-debounce-fx -; {:dispatch-debounce {:key :search -; :event [:search value] -; :delay 250}})) - -(def registered-keys (atom nil)) - -(defn dispatch-if-not-superceded [{:keys [key delay event time-received]}] - (when (= time-received (get @registered-keys key)) - ;; no new events on this key! - (re-frame/dispatch event))) - -(defn dispatch-debounced [{:keys [delay] :as debounce}] - (js/setTimeout - (fn [] (dispatch-if-not-superceded debounce)) - delay)) - -(re-frame/reg-fx - :dispatch-debounce - (fn dispatch-debounce [debounces] - (doseq [debounce debounces] - (let [ts (.getTime (js/Date.))] - (swap! registered-keys assoc (:key debounce) ts) - (dispatch-debounced (assoc debounce :time-received ts)))))) - ;; background-timer (defn set-timeout [cb ms] @@ -159,4 +134,4 @@ (let [decimal-part (get (string/split (str amount) ".") 1)] (if (> (count decimal-part) places) (gstring/format (str "%." places "f") amount) - (or (str amount) 0)))) \ No newline at end of file + (or (str amount) 0)))) diff --git a/status-go-version.json b/status-go-version.json index 788afee7ae..1677f669ee 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.35.1", - "commit-sha1": "4769fb91c3247c72c21013bf36194843d8d72d18", - "src-sha256": "1b6050kvnvf69gqw5llb7gb5v25pq6vbhkf9nznk9ra2spasqqzy" + "version": "v0.36.1", + "commit-sha1": "fd49b0140ebafdcec35b4da84685bcd8559a7dd9", + "src-sha256": "1pqnvmldg93vbmmsvpr24pj87d2vx3cfm7rr9rgwdk469pd1hhhy" } diff --git a/test/appium/tests/atomic/account_management/test_profile.py b/test/appium/tests/atomic/account_management/test_profile.py index 54ebbf8b88..8c3713391c 100644 --- a/test/appium/tests/atomic/account_management/test_profile.py +++ b/test/appium/tests/atomic/account_management/test_profile.py @@ -861,13 +861,14 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase): self.errors.append('Public chat "%s" doesn\'t appear on other device when devices are paired' % public_chat_before_sync_name) - device_2_home.element_by_text(group_chat_name).click() - device_2_group_chat = device_2_home.get_chat_view() + # Disabling for now until GroupMembershipUpdate is also moved to status-go + #device_2_home.element_by_text(group_chat_name).click() + #device_2_group_chat = device_2_home.get_chat_view() - if not device_2_group_chat.chat_element_by_text(message_after_sync).is_element_displayed(): - self.errors.append('"%s" message in group chat is not synced' % message_after_sync) + #if not device_2_group_chat.chat_element_by_text(message_after_sync).is_element_displayed(): + # self.errors.append('"%s" message in group chat is not synced' % message_after_sync) - device_2_group_chat.get_back_to_home_view() + #device_2_group_chat.get_back_to_home_view() device_2_home.profile_button.click() if not device_2_profile.profile_picture.is_element_image_equals_template('sauce_logo_red_profile.png'): self.errors.append('Profile picture was not updated after changing when devices are paired') @@ -951,4 +952,4 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase): if not profile_2.element_by_text('@' + user_1['ens']).is_element_displayed(): self.errors.append('ENS username is not shown in contacts') - self.errors.verify_no_errors() \ No newline at end of file + self.errors.verify_no_errors() diff --git a/test/appium/tests/atomic/chats/test_group_chat.py b/test/appium/tests/atomic/chats/test_group_chat.py index 10e08eb58a..c3558a0d8b 100644 --- a/test/appium/tests/atomic/chats/test_group_chat.py +++ b/test/appium/tests/atomic/chats/test_group_chat.py @@ -5,19 +5,19 @@ from views.sign_in_view import SignInView def return_left_chat_system_message(username): - return "*%s* left the group" % username + return "%s left the group" % username def return_created_chat_system_message(username, chat_name): - return "*%s* created the group *%s*" % (username, chat_name) + return "%s created the group %s" % (username, chat_name) def return_joined_chat_system_message(username): - return "*%s* has joined the group" % username + return "%s has joined the group" % username def return_made_admin_system_message(username): - return "*%s* has been made admin" % username + return "%s has been made admin" % username def create_users(driver_1, driver_2): diff --git a/test/cljs/status_im/test/chat/models.cljs b/test/cljs/status_im/test/chat/models.cljs index 29f6beb998..d7c15806f4 100644 --- a/test/cljs/status_im/test/chat/models.cljs +++ b/test/cljs/status_im/test/chat/models.cljs @@ -65,9 +65,7 @@ (deftest clear-history-test (let [chat-id "1" cofx {:db {:chats {chat-id {:message-list [{:something "a"}] - :messages {"1" {:clock-value 1} - "2" {:clock-value 10} - "3" {:clock-value 2}} + :last-message {:clock-value 10} :unviewed-messages-count 1}}}}] (testing "it deletes all the messages" (let [actual (chat/clear-history cofx chat-id)] @@ -85,7 +83,7 @@ (let [actual (chat/clear-history (update-in cofx [:db :chats chat-id] assoc - :messages {} + :last-message nil :deleted-at-clock-value 100) chat-id)] (is (= 100 (get-in actual [:db :chats chat-id :deleted-at-clock-value]))))) @@ -94,7 +92,7 @@ (let [actual (chat/clear-history (update-in cofx [:db :chats chat-id] assoc - :messages {}) + :last-message nil) chat-id)] (is (= 42 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))) (testing "it adds the relevant rpc calls" @@ -104,7 +102,8 @@ (deftest remove-chat-test (let [chat-id "1" - cofx {:db {:chats {chat-id {:messages {"1" {:clock-value 1} + cofx {:db {:chats {chat-id {:last-message {:clock-value 10} + :messages {"1" {:clock-value 1} "2" {:clock-value 10} "3" {:clock-value 2}}}}}}] (testing "it deletes all the messages" diff --git a/test/cljs/status_im/test/chat/models/message.cljs b/test/cljs/status_im/test/chat/models/message.cljs index 947c8e6812..808ca790e4 100644 --- a/test/cljs/status_im/test/chat/models/message.cljs +++ b/test/cljs/status_im/test/chat/models/message.cljs @@ -2,6 +2,7 @@ (:require [cljs.test :refer-macros [deftest is testing]] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.identicon :as identicon] + [status-im.constants :as constants] [status-im.utils.datetime :as time] [status-im.transport.message.protocol :as protocol] [status-im.chat.models.message-list :as models.message-list] @@ -49,51 +50,17 @@ (testing "a message coming from you!" (let [actual (message/receive-one {:db db} {:from "me" - :message-type :user-message + :message-type constants/message-type-one-to-one :timestamp 0 :whisper-timestamp 0 :message-id "id" :chat-id "chat-id" + :outgoing true :content "b" :clock-value 1}) message (get-in actual [:db :chats "chat-id" :messages "id"])] (testing "it adds the message" - (is message)) - (testing "it marks the message as outgoing" - (is (= true (:outgoing message)))))))) - -(deftest receive-one-clock-value - (let [db {:multiaccount {:public-key "me"} - :view-id :chat - :current-chat-id "chat-id" - :chats {"chat-id" {:last-clock-value 10 - :messages {}}}}] - (testing "a message with a higher clock value" - (let [actual (message/receive-one {:db db} - {:from "chat-id" - :message-type :user-message - :timestamp 0 - :whisper-timestamp 0 - :message-id "id" - :chat-id "chat-id" - :content "b" - :clock-value 12}) - chat-clock-value (get-in actual [:db :chats "chat-id" :last-clock-value])] - (testing "it sets last-clock-value" - (is (= 12 chat-clock-value))))) - (testing "a message with a lower clock value" - (let [actual (message/receive-one {:db db} - {:from "chat-id" - :message-type :user-message - :timestamp 0 - :whisper-timestamp 0 - :message-id "id" - :chat-id "chat-id" - :content "b" - :clock-value 2}) - chat-clock-value (get-in actual [:db :chats "chat-id" :last-clock-value])] - (testing "it sets last-clock-value" - (is (= 10 chat-clock-value))))))) + (is message)))))) (deftest receive-group-chats (let [cofx {:db {:chats {"chat-id" {:contacts #{"present"} @@ -104,21 +71,21 @@ cofx-without-member (update-in cofx [:db :chats "chat-id" :members-joined] disj "a") valid-message {:chat-id "chat-id" :from "present" - :message-type :group-user-message + :message-type constants/message-type-private-group :message-id "1" :clock-value 1 :whisper-timestamp 0 :timestamp 0} bad-chat-id-message {:chat-id "bad-chat-id" :from "present" - :message-type :group-user-message + :message-type constants/message-type-private-group :message-id "1" :clock-value 1 :whisper-timestamp 0 :timestamp 0} bad-from-message {:chat-id "chat-id" :from "not-present" - :message-type :group-user-message + :message-type constants/message-type-private-group :message-id "1" :clock-value 1 :whisper-timestamp 0 @@ -139,14 +106,14 @@ :view-id :chat}} valid-message {:chat-id "chat-id" :from "anyone" - :message-type :public-group-user-message + :message-type constants/message-type-public-group :message-id "1" :clock-value 1 :whisper-timestamp 0 :timestamp 0} bad-chat-id-message {:chat-id "bad-chat-id" :from "present" - :message-type :public-group-user-message + :message-type constants/message-type-public-group :message-id "1" :clock-value 1 :whisper-timestamp 0 @@ -166,14 +133,14 @@ :view-id :chat}} valid-message {:chat-id "matching" :from "matching" - :message-type :user-message + :message-type constants/message-type-one-to-one :message-id "1" :clock-value 1 :whisper-timestamp 0 :timestamp 0} own-message {:chat-id "matching" :from "me" - :message-type :user-message + :message-type constants/message-type-one-to-one :message-id "1" :clock-value 1 :whisper-timestamp 0 @@ -181,7 +148,7 @@ bad-chat-id-message {:chat-id "bad-chat-id" :from "not-matching" - :message-type :user-message + :message-type constants/message-type-one-to-one :message-id "1" :clock-value 1 :whisper-timestamp 0 @@ -229,38 +196,15 @@ :clock-value 0 :first-in-group? true :from nil - :first-outgoing? nil - :outgoing-seen? nil + :first-outgoing? false + :outgoing-seen? false :timestamp-str "timestamp" :first? true :display-username? true - :outgoing nil}] + :outgoing false}] (models.message-list/->seq (get-in fx1 [:db :chats "chat-id" :message-list])))) (is (= {} (get-in fx2 [:db :chats "chat-id" :messages]))) (is (= nil (get-in fx2 [:db :chats "chat-id" :message-list]))))))) - -(deftest add-outgoing-status - (testing "coming from us" - (testing "system-message" - (let [message (message/add-outgoing-status {:message-type :system-message - :from "us"} "us")] - (is (not (:outgoing message))) - (is (not (:outgoing-status message))))) - (testing "has already a an outgoing status" - (testing "it does not override it" - (let [message (message/add-outgoing-status {:outgoing-status :sending - :from "us"} "us")] - (is (:outgoing message)) - (is (= :sending (:outgoing-status message)))))) - (testing "does not have an outgoing status" - (testing "it sets it to sent" - (let [message (message/add-outgoing-status {:from "us"} "us")] - (is (:outgoing message)) - (is (= :sent (:outgoing-status message))))))) - (testing "not coming from us" - (let [message (message/add-outgoing-status {:from "not-us"} "us")] - (is (not (:outgoing message))) - (is (not (:outgoing-status message)))))) diff --git a/test/cljs/status_im/test/data_store/chats.cljs b/test/cljs/status_im/test/data_store/chats.cljs index 34d71cb507..23868c4039 100644 --- a/test/cljs/status_im/test/data_store/chats.cljs +++ b/test/cljs/status_im/test/data_store/chats.cljs @@ -27,8 +27,6 @@ :is-active true :messages {} :pagination-info {} - :last-message-content "content" - :last-message-content-type "type" :chat-id "chat-id" :loaded-unviewed-messages-ids [] :timestamp 2 @@ -37,8 +35,7 @@ :color "color" :name "name" :chatType 3 - :lastMessageContent "content" - :lastMessageContentType "type" + :lastMessage nil :members #{{:id "a" :admin true :joined true} @@ -90,9 +87,6 @@ :admin false :joined false}] :lastClockValue 10 - :lastMessageContent "\"content\"" ;; goes through edn/read-string - :lastMessageContentType "type" - :membershipUpdates [{:type "chat-created" :name "test" :clockValue 1 @@ -111,11 +105,9 @@ expected-chat {:public? false :group-chat true :color "color" - :last-message-content "content" - :last-message-content-type "type" - :contacts #{"a" "b" "c" "d"} :last-clock-value 10 + :last-message nil :admins #{"a" "b"} :members-joined #{"a" "c"} :name "name" diff --git a/test/cljs/status_im/test/data_store/messages.cljs b/test/cljs/status_im/test/data_store/messages.cljs index 5187c2ae9f..26a7cb62a7 100644 --- a/test/cljs/status_im/test/data_store/messages.cljs +++ b/test/cljs/status_im/test/data_store/messages.cljs @@ -6,60 +6,45 @@ (def chat-id "chat-id") (def from "0x0424a68f89ba5fcd5e0640c1e1f591d561fa4125ca4e2a43592bc4123eca10ce064e522c254bb83079ba404327f6eafc01ec90a1444331fe769d3f3a7f90b0dde1") -(deftest message->rpc - (testing "message to rpc" - (let [message {:message-id message-id - :content {:chat-id chat-id - :response-to "id-2" - :text "hta"} - :whisper-timestamp 1 - :dedup-id "ATIwMTkwODE0YTdkNWZhZGY1N2E0ZDU3MzUxZmJkNDZkZGM1ZTU4ZjRlYzUyYWYyMDA5NTc2NWYyYmIxOTQ2OTM3NGUwNjdiMvEpTIGEjHOTAyqsrN39wST4npnSAv1AR8jJWeubanjkoGIyJooD5RVRnx6ZMt+/JzBOD2hoZzlHQWA0bC6XbdU=" - :outgoing-status :sending - :message-type :public-group-user-message - :clock-value 2 - :from from - :chat-id chat-id - :content-type "text/plain" - :timestamp 3} - expected {:id message-id - :whisperTimestamp 1 - :from from - :chatId chat-id - :replyTo "id-2" - :content "{\"chat-id\":\"chat-id\",\"response-to\":\"id-2\",\"text\":\"hta\"}" - :contentType "text/plain" - :messageType "public-group-user-message" - :clockValue 2 - :timestamp 3 - :outgoingStatus "sending"}] - (is (= expected (m/->rpc message)))))) - (deftest message<-rpc (testing "message to rpc" (let [expected {:message-id message-id :content {:chat-id chat-id - :text "hta"} + :sticker {:hash "hash" :pack 1} + :text "hta" + :line-count 1 + :ens-name "ens-name" + :parsed-text "parsed-text" + :rtl false + :response-to "a"} :whisper-timestamp 1 :outgoing-status :sending - :outgoing :sending - :message-type :public-group-user-message + :outgoing true + :message-type 0 :clock-value 2 :from from :chat-id chat-id :quoted-message {:from "from" :text "reply"} - :content-type "text/plain" + :content-type 1 :timestamp 3} message {:id message-id :whisperTimestamp 1 + :parsedText "parsed-text" + :ensName "ens-name" + :localChatId chat-id :from from + :text "hta" + :rtl false :chatId chat-id - :content "{\"chat-id\":\"chat-id\",\"text\":\"hta\"}" - :contentType "text/plain" - :messageType "public-group-user-message" - :clockValue 2 + :lineCount 1 + :sticker {:hash "hash" :pack 1} + :contentType 1 + :messageType 0 + :clock 2 + :responseTo "a" :quotedMessage {:from "from" - :content "{\"chat-id\":\"chat-id\",\"text\":\"reply\"}"} + :text "reply"} :timestamp 3 :outgoingStatus "sending"}] (is (= expected (m/<-rpc message)))))) diff --git a/test/cljs/status_im/test/group_chats/core.cljs b/test/cljs/status_im/test/group_chats/core.cljs index e736ed0b1e..2f1cb6d1da 100644 --- a/test/cljs/status_im/test/group_chats/core.cljs +++ b/test/cljs/status_im/test/group_chats/core.cljs @@ -57,14 +57,7 @@ (:membership-updates actual)))) (testing "it sets the right admins" (is (= #{admin} - (:admins actual)))) - (testing "it adds a system message" - (is (= 3 (count (:messages actual))))) - (testing "it adds the right text" - (is (= ["group-chat-created" - "group-chat-member-added" - "group-chat-member-added"] - (map (comp :text :content) (sort-by :clock-value (vals (:messages actual))))))))) + (:admins actual)))))) (testing "a chat with the wrong id" (let [bad-chat-id (str random-id member-2) actual (-> @@ -162,18 +155,7 @@ (testing "members are updated" (is (= #{member-1 member-2 member-4} (:contacts actual-chat)))) (testing "the name is updated" - (is (= "new-name" (:name actual-chat)))) - (testing "it adds a system message" - (is (= 7 (count (:messages actual-chat))))) - (testing "it sets the right text" - (is (= ["group-chat-created" - "group-chat-member-added" - "group-chat-member-added" - "group-chat-admin-added" - "group-chat-member-added" - "group-chat-member-removed" - "group-chat-name-changed"] - (map (comp :text :content) (sort-by :clock-value (vals (:messages actual-chat))))))))))))) + (is (= "new-name" (:name actual-chat)))))))))) (deftest build-group-test (testing "only adds" diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 2bde67d744..ba293f37d9 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -36,7 +36,6 @@ [status-im.test.sign-in.flow] [status-im.test.stickers.core] [status-im.test.transport.core] - [status-im.test.transport.utils] [status-im.test.tribute-to-talk.core] [status-im.test.tribute-to-talk.db] [status-im.test.tribute-to-talk.whitelist] @@ -112,7 +111,6 @@ 'status-im.test.signing.core 'status-im.test.signing.gas 'status-im.test.transport.core - 'status-im.test.transport.utils 'status-im.test.tribute-to-talk.core 'status-im.test.tribute-to-talk.db 'status-im.test.tribute-to-talk.whitelist diff --git a/test/cljs/status_im/test/transport/utils.cljs b/test/cljs/status_im/test/transport/utils.cljs deleted file mode 100644 index f8c3803712..0000000000 --- a/test/cljs/status_im/test/transport/utils.cljs +++ /dev/null @@ -1,11 +0,0 @@ -(ns status-im.test.transport.utils - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.utils.fx :as fx] - [status-im.transport.utils :as transport])) - -(deftest test-message-id - (testing "test" - (let [pk "0x03d0370306168850aa1f06a2f22c9a756c7dd00e35dd797fcdf351e53ff6ae7b9f" - payload "0x74657374" - expected-message-id "0x642b7f39873aab69d5aee686f4ed0ca02f82e025242ea57569a70640a94aea34"] - (is (= expected-message-id (transport/message-id pk payload)))))) diff --git a/test/cljs/status_im/test/tribute_to_talk/whitelist.cljs b/test/cljs/status_im/test/tribute_to_talk/whitelist.cljs index f32d99532a..e6ea13f728 100644 --- a/test/cljs/status_im/test/tribute_to_talk/whitelist.cljs +++ b/test/cljs/status_im/test/tribute_to_talk/whitelist.cljs @@ -1,6 +1,7 @@ (ns status-im.test.tribute-to-talk.whitelist (:require [cljs.test :refer-macros [deftest testing is]] [status-im.utils.gfycat.core :as gfycat] + [status-im.constants :as constants] [status-im.utils.identicon :as identicon] [status-im.tribute-to-talk.whitelist :as whitelist])) @@ -113,21 +114,21 @@ (whitelist/filter-message (whitelist/enable-whitelist ttt-enabled-multiaccount) :unfiltered-fx - :user-message + constants/message-type-one-to-one nil "whitelisted because added")) (testing "tribute to talk is disabled" (whitelist/filter-message ttt-disabled-multiaccount :unfiltered-fx - :user-message + constants/message-type-one-to-one nil "public-key")) (testing "user is not whitelisted but transaction is valid" (let [result (whitelist/filter-message ttt-enabled-multiaccount #(assoc % :message-received true) - :user-message + constants/message-type-one-to-one "transaction-hash-1" sender-pk)] (is (contains? (get-in result [:db :contacts/whitelist]) diff --git a/translations/en.json b/translations/en.json index afa4d3bd55..dd39a73197 100644 --- a/translations/en.json +++ b/translations/en.json @@ -492,14 +492,14 @@ "got-it": "Got it", "group-chat": "Group chat", "group-chat-admin": "Admin", - "group-chat-admin-added": "*{{member}}* has been made admin", - "group-chat-created": "*{{member}}* created the group *{{name}}*", + "group-chat-admin-added": "**{{member}}** has been made admin", + "group-chat-created": "**{{member}}** created the group **{{name}}**", "group-chat-decline-invitation": "Decline invitation", - "group-chat-member-added": "*{{member}}* has been invited", - "group-chat-member-joined": "*{{member}}* has joined the group", - "group-chat-member-removed": "*{{member}}* left the group", + "group-chat-member-added": "**{{member}}** has been invited", + "group-chat-member-joined": "**{{member}}** has joined the group", + "group-chat-member-removed": "**{{member}}** left the group", "group-chat-members-count": "{{selected}}/{{max}} members", - "group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*", + "group-chat-name-changed": "**{{member}}** changed the group's name to **{{name}}**", "group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting", "group-info": "Group info", "gwei": "Gwei",