diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 0baaebe6f6..e3b8ec507e 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -13,7 +13,6 @@ [status-im.ethereum.core :as ethereum] [status-im.mailserver.core :as mailserver] [status-im.native-module.core :as status] - [status-im.transport.message.group-chat :as message.group-chat] [status-im.transport.message.protocol :as protocol] [status-im.transport.message.transit :as transit] [status-im.transport.utils :as transport.utils] @@ -116,29 +115,32 @@ (get-in cofx [:db :chats chat-id :public?])) chat-id (and (= constants/message-type-one-to-one message-type) (= (multiaccounts.model/current-public-key cofx) from)) chat-id + (= constants/message-type-private-group-system-message message-type) chat-id (= constants/message-type-one-to-one message-type) from)) (fx/defn update-unviewed-count [{:keys [now db] :as cofx} {:keys [chat-id from + message-type 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])] - (cond - (= from (multiaccounts.model/current-public-key cofx)) - ;; nothing to do - nil + (when-not (= message-type constants/message-type-private-group-system-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])] + (cond + (= from (multiaccounts.model/current-public-key cofx)) + ;; nothing to do + nil - (and chat-view? (= current-chat-id chat-id)) - (fx/merge cofx - (data-store.messages/mark-messages-seen current-chat-id [message-id])) + (and chat-view? (= current-chat-id chat-id)) + (fx/merge cofx + (data-store.messages/mark-messages-seen current-chat-id [message-id])) - :else - {:db (update-in db [:chats chat-id] - assoc - :unviewed-messages-count (inc current-count))}))) + :else + {:db (update-in db [:chats chat-id] + assoc + :unviewed-messages-count (inc current-count))})))) (fx/defn receive-one [cofx message] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index a8613666a4..6489f5df62 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -7,15 +7,15 @@ (def ms-in-bg-for-require-bioauth 5000) -(def content-type-text 0) -(def content-type-sticker 1) -(def content-type-status 2) -(def content-type-emoji 3) +(def content-type-text 1) +(def content-type-sticker 2) +(def content-type-status 3) +(def content-type-emoji 4) -(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 message-type-one-to-one 1) +(def message-type-public-group 2) +(def message-type-private-group 3) +(def message-type-private-group-system-message 4) (def desktop-content-types #{content-type-text content-type-emoji content-type-status}) diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index 3445f1e151..821c30081a 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -34,32 +34,6 @@ ; Build an event id from a message (def event-id (comp ethereum/sha3 event->string)) -(defn marshal-membership-updates [updates] - (mapcat (fn [{:keys [signature events from]}] - (map #(-> % - (assoc - :clockValue (:clock-value %) - :id (event-id %) - :signature signature - :from from) - (dissoc :clock-value)) events)) updates)) - -(defn unmarshal-membership-updates [chat-id updates] - (->> updates - (group-by :signature) - (map (fn [[signature events]] - {:events - (into [] - (sort-by :clock-value (map #(-> % - (assoc :clock-value (:clockValue %)) - (update :members (fn [members] (into #{} members))) - (dissoc :signature :from :id :clockValue) - remove-empty-vals) events))) - :from (-> events first :from) - :signature signature - :chat-id chat-id})) - (into #{}))) - (defn type->rpc [{:keys [public? group-chat] :as chat}] (assoc chat :chatType (cond public? public-chat-type @@ -110,10 +84,9 @@ (-> chat type->rpc marshal-members - (update :membership-updates marshal-membership-updates) (update :last-message messages/->rpc) (clojure.set/rename-keys {:chat-id :id - :membership-updates :membershipUpdates + :membership-update-events :membershipUpdateEvents :unviewed-messages-count :unviewedMessagesCount :last-message :lastMessage :deleted-at-clock-value :deletedAtClockValue @@ -130,13 +103,12 @@ rpc->type unmarshal-members (clojure.set/rename-keys {:id :chat-id - :membershipUpdates :membership-updates + :membershipUpdateEvents :membership-update-events :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 #(when % (messages/<-rpc %))) (dissoc :chatType :members))) diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index ab5cb83701..279b2d73a8 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -36,6 +36,13 @@ "shhext_enableInstallation" {} "shhext_disableInstallation" {} "shhext_sendChatMessage" {} + "shhext_confirmJoiningGroup" {} + "shhext_addAdminsToGroupChat" {} + "shhext_addMembersToGroupChat" {} + "shhext_removeMemberFromGroupChat" {} + "shhext_leaveGroupChat" {} + "shhext_changeGroupChatName" {} + "shhext_createGroupChatWithMembers" {} "shhext_reSendChatMessage" {} "shhext_getOurInstallations" {} "shhext_setInstallationMetadata" {} diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index e1b018dac7..7e26da6104 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1170,17 +1170,6 @@ (fn [cofx [_ chat-id]] (group-chats/join-chat cofx chat-id))) -(handlers/register-handler-fx - :group-chats.callback/sign-success - [(re-frame/inject-cofx :random-guid-generator)] - (fn [cofx [_ group-update]] - (group-chats/handle-sign-success cofx group-update))) - -(handlers/register-handler-fx - :group-chats.callback/extract-signature-success - (fn [cofx [_ group-update message-info sender-signature]] - (group-chats/handle-membership-update cofx group-update message-info sender-signature))) - ;; profile module (handlers/register-handler-fx diff --git a/src/status_im/group_chats/core.cljs b/src/status_im/group_chats/core.cljs index 1a5a2b7d0b..a842023319 100644 --- a/src/status_im/group_chats/core.cljs +++ b/src/status_im/group_chats/core.cljs @@ -7,6 +7,9 @@ [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.chat.models.message-content :as message-content] + [status-im.ui.screens.navigation :as navigation] + [status-im.data-store.chats :as data-store.chats] + [status-im.data-store.messages :as data-store.messages] [status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.model :as multiaccounts.model] [status-im.utils.pairing :as pairing.utils] @@ -17,7 +20,6 @@ [status-im.group-chats.db :as group-chats.db] [status-im.i18n :as i18n] [status-im.native-module.core :as native-module] - [status-im.transport.message.group-chat :as message.group-chat] [status-im.transport.message.protocol :as protocol] [status-im.utils.clocks :as utils.clocks] [status-im.utils.fx :as fx] @@ -25,266 +27,81 @@ [status-im.mailserver.topics :as mailserver.topics] [taoensso.timbre :as log])) -;; Description of the flow: -;; the flow is complicated a bit by 2 asynchronous call to status-go, which might make the logic a bit more opaque. -;; To send a group-membership update, we first build a message. -;; We then sign it with our private key and dispatch it. -;; Conversely when receiving a message, we first extract the public keys from the signature and attach those in the :from field. -;; We then process the events. -;; It is importatn that the from field is not trusted for updates coming from the outside. -;; When messages are coming from the database it can be trustured as already verified by us. - -(defn sort-events [events] - (sort-by :clock-value events)) - -(defn- event->vector - "Transform an event in an a vector with keys in alphabetical order" - [event] - (mapv - #(vector % (get event %)) - (sort (keys event)))) - -(defn get-last-clock-value - "Given a chat id get the last clock value of an event" - [cofx chat-id] - (->> (get-in cofx [:db :chats chat-id :last-clock-value]))) - -(defn- parse-response [response-js] - (-> response-js - js/JSON.parse - (js->clj :keywordize-keys true))) - -(defn extract-creator - "Takes a chat as an input, returns the creator" - [{:keys [membership-updates]}] - (->> membership-updates - (filter (fn [{:keys [events]}] - (some #(= "chat-created" (:type %)) events))) - first - :from)) - -(defn creator? [public-key chat] - (= public-key (extract-creator chat))) - -(defn signature-material - "Transform an update into a signable string" - [chat-id events] - (js/JSON.stringify - (clj->js [(mapv event->vector (sort-events events)) chat-id]))) - -(defn signature-pairs - "Transform a bunch of updates into signable pairs to be verified" - [{:keys [chat-id membership-updates] :as payload}] - (let [pairs (mapv (fn [{:keys [events signature]}] - [(signature-material chat-id events) - signature]) - membership-updates)] - (js/JSON.stringify (clj->js pairs)))) - -(defn valid-chat-id? - ;; We need to make sure the chat-id ends with the admin pk (and it's not the same). - ;; this is due to prevent an attack whereby a non-admin user would - ;; send out a message with identical chat-id and themselves as admin to other members, - ;; who would then have to trust the first of the two messages received, possibly - ;; resulting in a situation where some of the members in the chat trust a different admin. - [chat-id admin] - (and (string/ends-with? chat-id admin) - (not= chat-id admin))) - -(defn valid-event? - "Check if event can be applied to current group" - [{:keys [admins contacts]} {:keys [chat-id from member members] :as new-event}] - (when from - (condp = (:type new-event) - "chat-created" (and (empty? admins) - (empty? contacts)) - "name-changed" (and (admins from) - (not (string/blank? (:name new-event)))) - "members-added" (admins from) - "member-joined" (and (contacts member) - (= from member)) - "admins-added" (and (admins from) - (clojure.set/subset? members contacts)) - "member-removed" (or - ;; An admin removing a member - (and (admins from) - (not (admins member))) - ;; Members can remove themselves - (= from member)) - "admin-removed" (and (admins from) - (= from member)) - false))) - -(defn send-membership-update - "Send a membership update to all participants but the sender" - ([cofx payload chat-id] - (send-membership-update cofx payload chat-id nil)) - ([{:keys [message-id] :as cofx} payload chat-id removed-members] - (let [chat (get-in cofx [:db :chats chat-id]) - creator (extract-creator chat) - members (clojure.set/union (get-in cofx [:db :chats chat-id :contacts]) - removed-members) - current-public-key (multiaccounts.model/current-public-key cofx) - members-allowed (filter - (fn [pk] - (if (= pk current-public-key) - (pairing.utils/has-paired-installations? cofx) - true)) - members) - destinations (map (fn [member] - {:public-key member}) - members-allowed)] - (fx/merge - cofx - {:shh/send-group-message - {:src current-public-key - :dsts destinations - :success-event [:transport/message-sent - chat-id - (:message cofx) - constants/message-type-private-group] - :payload payload}})))) - -(fx/defn handle-membership-update-received - "Extract signatures in status-go and act if successful" - [cofx membership-update signature message-info] - {:group-chats/extract-membership-signature [[membership-update message-info signature]]}) - -(defn chat->group-update - "Transform a chat in a GroupMembershipUpdate" - [chat-id {:keys [membership-updates]}] - (message.group-chat/map->GroupMembershipUpdate. {:chat-id chat-id - :membership-updates membership-updates})) - -(defn handle-sign-response - "Callback to dispatch on sign response" - [payload response-js] - (let [{:keys [error signature]} (parse-response response-js)] - (if error - (re-frame/dispatch [:group-chats.callback/sign-failed error]) - (re-frame/dispatch [:group-chats.callback/sign-success (assoc payload :signature signature)])))) - -(defn add-identities - "Add verified identities extracted from the signature to the updates" - [payload identities] - (update payload :membership-updates (fn [updates] - (map - #(assoc %1 :from (str "0x" %2)) - updates - identities)))) - -(defn handle-extract-signature-response - "Callback to dispatch on extract signature response" - [payload message-info sender-signature response-js] - (let [{:keys [error identities]} (parse-response response-js)] - (if error - (re-frame/dispatch [:group-chats.callback/extract-signature-failed error]) - (re-frame/dispatch [:group-chats.callback/extract-signature-success - (add-identities payload identities) message-info sender-signature])))) - -(defn sign-membership [{:keys [chat-id events] :as payload}] - (native-module/sign-group-membership (signature-material chat-id events) - (partial handle-sign-response payload))) - -(defn extract-membership-signature [payload message-info sender] - (native-module/extract-group-membership-signatures - (signature-pairs payload) - (partial handle-extract-signature-response payload message-info sender))) - -(defn- members-added-event [last-clock-value members] - {:type "members-added" - :clock-value (utils.clocks/send last-clock-value) - :members members}) - -(defn- member-joined-event [last-clock-value member] - {:type "member-joined" - :clock-value (utils.clocks/send last-clock-value) - :member member}) - -(fx/defn create - "Format group update message and sign membership" - [{:keys [db random-guid-generator] :as cofx} group-name] - (let [my-public-key (multiaccounts.model/current-public-key cofx) - chat-id (str (random-guid-generator) my-public-key) - selected-contacts (:group/selected-contacts db) - clock-value (utils.clocks/send 0) - create-event {:type "chat-created" - :name group-name - :clock-value clock-value} - events [create-event - (members-added-event clock-value selected-contacts)]] - - (fx/merge - cofx - {:group-chats/sign-membership {:chat-id chat-id - :from my-public-key - :events events} - :db (assoc db :group/selected-contacts #{})}))) - (fx/defn remove-member "Format group update message and sign membership" [{:keys [db] :as cofx} chat-id member] - (let [my-public-key (multiaccounts.model/current-public-key cofx) - last-clock-value (get-last-clock-value cofx chat-id) - chat (get-in cofx [:db :chats chat-id]) - remove-event {:type "member-removed" - :member member - :clock-value (utils.clocks/send last-clock-value)}] - (when (valid-event? chat (assoc remove-event - :from - my-public-key)) - {:group-chats/sign-membership {:chat-id chat-id - :from my-public-key - :events [remove-event]}}))) + {::json-rpc/call [{:method "shhext_removeMemberFromGroupChat" + :params [nil chat-id member] + :on-success #(re-frame/dispatch [::chat-updated %])}]}) -(fx/defn join-chat - "Format group update message and sign membership" - [cofx chat-id] - (let [my-public-key (multiaccounts.model/current-public-key cofx) - last-clock-value (get-last-clock-value cofx chat-id) - chat (get-in cofx [:db :chats chat-id]) - event (member-joined-event last-clock-value my-public-key)] - (when (valid-event? chat (assoc event - :from - my-public-key)) +(fx/defn set-up-filter + "Listen/Tear down the shared topic/contact-codes. Stop listening for members who + have left the chat" + [cofx chat-id previous-chat] + (let [my-public-key (multiaccounts.model/current-public-key cofx) + new-chat (get-in cofx [:db :chats chat-id]) + members (:members-joined new-chat)] + ;; If we left the chat do nothing + (when-not (and (group-chats.db/joined? my-public-key previous-chat) + (not (group-chats.db/joined? my-public-key new-chat))) (fx/merge cofx - {:group-chats/sign-membership {:chat-id chat-id - :from my-public-key - :events [event]}})))) + (transport.filters/upsert-group-chat-topics) + (transport.filters/load-members members))))) + +(fx/defn handle-chat-update + {:events [::chat-updated]} + [cofx {:keys [chats messages]}] + (let [{:keys [chat-id] :as chat} (-> chats + first + (data-store.chats/<-rpc)) + + previous-chat (get-in cofx [:chats chat-id]) + set-up-filter-fx (set-up-filter chat-id previous-chat) + chat-fx (models.chat/ensure-chat + (dissoc chat :unviewed-messages-count)) + messages-fx (map #(models.message/receive-one + (data-store.messages/<-rpc %)) + messages) + navigate-fx #(if (get-in % [:db :chats chat-id :is-active]) + (models.chat/navigate-to-chat % chat-id {:navigation-reset? true}) + (navigation/navigate-to-cofx % :home {}))] + + (apply fx/merge cofx (concat [chat-fx] + messages-fx + [navigate-fx])))) + +(fx/defn join-chat + [cofx chat-id] + {::json-rpc/call [{:method "shhext_confirmJoiningGroup" + :params [chat-id] + :on-success #(re-frame/dispatch [::chat-updated %])}]}) + +(fx/defn create + [{:keys [db] :as cofx} group-name] + (let [selected-contacts (:group/selected-contacts db)] + {::json-rpc/call [{:method "shhext_createGroupChatWithMembers" + :params [nil group-name (into [] selected-contacts)] + :on-success #(re-frame/dispatch [::chat-updated %])}]})) (fx/defn make-admin - "Format group update with make admin message and sign membership" [{:keys [db] :as cofx} chat-id member] - (let [my-public-key (multiaccounts.model/current-public-key cofx) - last-clock-value (get-last-clock-value cofx chat-id) - chat (get-in cofx [:db :chats chat-id]) - event {:type "admins-added" - :members [member] - :clock-value (utils.clocks/send last-clock-value)}] - (when (valid-event? chat (assoc event - :from - my-public-key)) - {:group-chats/sign-membership {:chat-id chat-id - :from my-public-key - :events [event]}}))) + {::json-rpc/call [{:method "shhext_addAdminsToGroupChat" + :params [nil chat-id [member]] + :on-success #(re-frame/dispatch [::chat-updated %])}]}) (fx/defn add-members "Add members to a group chat" [{{:keys [current-chat-id selected-participants]} :db :as cofx}] - (let [last-clock-value (get-last-clock-value cofx current-chat-id) - events [(members-added-event last-clock-value selected-participants)]] - - {:group-chats/sign-membership {:chat-id current-chat-id - :from (multiaccounts.model/current-public-key cofx) - :events events}})) + {::json-rpc/call [{:method "shhext_addMembersToGroupChat" + :params [nil current-chat-id selected-participants] + :on-success #(re-frame/dispatch [::chat-updated %])}]}) (fx/defn remove "Remove & leave chat" [{:keys [db] :as cofx} chat-id] - (let [my-public-key (multiaccounts.model/current-public-key cofx)] - (fx/merge cofx - (remove-member chat-id my-public-key) - (models.chat/remove-chat chat-id)))) + {::json-rpc/call [{:method "shhext_leaveGroupChat" + :params [nil chat-id] + :on-success #(re-frame/dispatch [::chat-updated %])}]}) (defn- valid-name? [name] (spec/valid? :profile/name name)) @@ -302,226 +119,10 @@ (fx/defn save "Save chat from edited profile" [{:keys [db] :as cofx}] - (let [current-chat-id (get-in cofx [:db :current-chat-id]) - my-public-key (get-in db [:multiaccount :public-key]) - last-clock-value (get-last-clock-value cofx current-chat-id) - new-name (get-in cofx [:db :group-chat-profile/profile :name]) - name-changed-event {:type "name-changed" - :name new-name - :clock-value (utils.clocks/send last-clock-value)}] + (let [new-name (get-in cofx [:db :group-chat-profile/profile :name]) + current-chat-id (:current-chat-id db)] (when (valid-name? new-name) - (fx/merge cofx - {:db (assoc db - :group-chat-profile/editing? - false) - :group-chats/sign-membership {:chat-id current-chat-id - :from my-public-key - :events [name-changed-event]}})))) + {::json-rpc/call [{:method "shhext_changeGroupChatName" + :params [nil current-chat-id new-name] + :on-success #(re-frame/dispatch [::chat-updated %])}]}))) -(defn process-event - "Add/remove an event to a group, carrying the clock-value at which it happened" - [group {:keys [type member members chat-id clock-value from name] :as event}] - (if (valid-event? group event) - (case type - "chat-created" {:name name - :created-at clock-value - :admins #{from} - :members-joined #{from} - :contacts #{from}} - "name-changed" (assoc group - :name name - :name-changed-by from - :name-changed-at clock-value) - "members-added" (as-> group $ - (update $ :contacts clojure.set/union (set members)) - (reduce (fn [acc member] (assoc-in acc [member :added] clock-value)) $ members)) - "member-joined" (-> group - (update :members-joined conj member) - (assoc-in [member :joined] clock-value)) - "admins-added" (as-> group $ - (update $ :admins clojure.set/union (set members)) - (reduce (fn [acc member] (assoc-in acc [member :admin-added] clock-value)) $ members)) - "member-removed" (-> group - (update :contacts disj member) - (update :admins disj member) - (update :members-joined disj member) - (assoc-in [member :removed] clock-value)) - "admin-removed" (-> group - (update :admins disj member) - (assoc-in [member :admin-removed] clock-value))) - group)) - -(defn build-group - "Given a list of already authenticated events build a group with contats/admin" - [events] - (->> events - sort-events - (reduce - process-event - {:admins #{} - :members-joined #{} - :contacts #{}}))) - -(defn membership-changes->system-messages [cofx - clock-values - {:keys [chat-id - chat-name - creator - members-added - members-joined - admins-added - name-changed? - members-removed]}] - (let [get-contact (partial models.contact/build-contact cofx) - format-message (fn [contact text clock-value] - {:chat-id chat-id - :text text - :clock-value clock-value - :from (:public-key contact)}) - creator-contact (when creator (get-contact creator)) - name-changed-author (when name-changed? (get-contact (:name-changed-by clock-values))) - admins-added (map - get-contact - (disj admins-added creator)) - contacts-added (map - get-contact - (disj members-added creator)) - contacts-joined (map - get-contact - (disj members-joined creator)) - contacts-removed (map - get-contact - members-removed)] - (cond-> [] - creator-contact (conj (format-message creator-contact - (i18n/label :t/group-chat-created - {:name chat-name - :member (multiaccounts/displayed-name creator-contact)}) - (:created-at clock-values))) - name-changed? (conj (format-message name-changed-author - (i18n/label :t/group-chat-name-changed - {:name chat-name - :member (multiaccounts/displayed-name name-changed-author)}) - (:name-changed-at clock-values))) - (seq members-added) (concat (map #(format-message - % - (i18n/label :t/group-chat-member-added {:member (multiaccounts/displayed-name %)}) - (get-in clock-values [(:public-key %) :added])) - contacts-added)) - (seq members-joined) (concat (map #(format-message - % - (i18n/label :t/group-chat-member-joined {:member (multiaccounts/displayed-name %)}) - (get-in clock-values [(:public-key %) :joined])) - contacts-joined)) - (seq admins-added) (concat (map #(format-message - % - (i18n/label :t/group-chat-admin-added {:member (multiaccounts/displayed-name %)}) - (get-in clock-values [(:public-key %) :admin-added])) - admins-added)) - (seq members-removed) (concat (map #(format-message - % - (i18n/label :t/group-chat-member-removed {:member (multiaccounts/displayed-name %)}) - (get-in clock-values [(:public-key %) :removed])) - contacts-removed))))) - -(fx/defn add-system-messages [cofx chat-id previous-chat clock-values] - (let [current-chat (get-in cofx [:db :chats chat-id]) - current-public-key (get-in cofx [:db :current-public-key]) - name-changed? (and (seq previous-chat) - (not= (:name previous-chat) (:name current-chat))) - members-added (clojure.set/difference (:contacts current-chat) (:contacts previous-chat)) - members-joined (clojure.set/difference (:members-joined current-chat) (:members-joined previous-chat)) - members-removed (clojure.set/difference (:contacts previous-chat) (:contacts current-chat)) - admins-added (clojure.set/difference (:admins current-chat) (:admins previous-chat)) - membership-changes (cond-> {:chat-id chat-id - :name-changed? name-changed? - :chat-name (:name current-chat) - :admins-added admins-added - :members-added members-added - :members-joined members-joined - :members-removed members-removed} - (nil? previous-chat) - (assoc :creator (extract-creator current-chat)))] - (when (or name-changed? - (seq admins-added) - (seq members-added) - (seq members-joined) - (seq members-removed)) - (->> membership-changes - (membership-changes->system-messages cofx clock-values) - (models.message/add-system-messages cofx))))) - -(fx/defn set-up-filter - "Listen/Tear down the shared topic/contact-codes. Stop listening for members who - have left the chat" - [cofx chat-id previous-chat] - (let [my-public-key (multiaccounts.model/current-public-key cofx) - new-chat (get-in cofx [:db :chats chat-id]) - members (:members-joined new-chat)] - ;; If we left the chat do nothing - (when-not (and (group-chats.db/joined? my-public-key previous-chat) - (not (group-chats.db/joined? my-public-key new-chat))) - (fx/merge - cofx - (transport.filters/upsert-group-chat-topics) - (transport.filters/load-members members))))) - -(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 - ;; so can be manipulated by the sending user. - [cofx {:keys [chat-id - message - membership-updates] :as membership-update} - {:keys [whisper-timestamp metadata]} - sender-signature] - (let [dev-mode? (get-in cofx [:db :multiaccount :dev-mode?])] - (when (valid-chat-id? chat-id (extract-creator membership-update)) - (let [previous-chat (get-in cofx [:db :chats chat-id]) - all-updates (clojure.set/union (set (:membership-updates previous-chat)) - (set (:membership-updates membership-update))) - my-public-key (multiaccounts.model/current-public-key cofx) - unwrapped-events (group-chats.db/unwrap-events all-updates) - new-group (build-group unwrapped-events) - member? (contains? (:contacts new-group) my-public-key)] - (fx/merge cofx - (models.chat/upsert-chat {:chat-id chat-id - :name (:name new-group) - :is-active (or member? - (get previous-chat :is-active true)) - :group-chat true - :membership-updates (into [] all-updates) - :admins (:admins new-group) - :members-joined (:members-joined new-group) - :contacts (:contacts new-group)}) - (add-system-messages chat-id previous-chat new-group) - - (set-up-filter chat-id previous-chat)))))) - -(defn handle-sign-success - "Upsert chat and send signed payload to group members" - [{:keys [db] :as cofx} {:keys [chat-id] :as signed-events}] - (let [old-chat (get-in db [:chats chat-id]) - updated-chat (update old-chat :membership-updates conj signed-events) - my-public-key (multiaccounts.model/current-public-key cofx) - group-update (chat->group-update chat-id updated-chat) - new-group-fx (handle-membership-update group-update nil my-public-key) - ;; We need to send to users who have been removed as well - recipients (clojure.set/union - (:contacts old-chat) - (get-in new-group-fx [:db :chats chat-id :contacts]))] - (fx/merge cofx - new-group-fx - #(when (get-in % [:db :chats chat-id :is-active]) - (models.chat/navigate-to-chat % chat-id {:navigation-reset? true})) - #(send-membership-update % group-update chat-id recipients)))) - -(re-frame/reg-fx - :group-chats/sign-membership - sign-membership) - -(re-frame/reg-fx - :group-chats/extract-membership-signature - (fn [signatures] - (doseq [[payload raw-payload sender] signatures] - (extract-membership-signature payload raw-payload sender)))) diff --git a/src/status_im/transport/impl/receive.cljs b/src/status_im/transport/impl/receive.cljs index d9b94d02a6..240a58a0e4 100644 --- a/src/status_im/transport/impl/receive.cljs +++ b/src/status_im/transport/impl/receive.cljs @@ -6,18 +6,11 @@ [status-im.ens.core :as ens] [status-im.pairing.core :as pairing] [status-im.transport.message.contact :as transport.contact] - [status-im.transport.message.group-chat :as transport.group-chat] [status-im.transport.message.pairing :as transport.pairing] [status-im.transport.message.core :as transport.message] [status-im.transport.message.protocol :as protocol])) -(extend-type transport.group-chat/GroupMembershipUpdate - protocol/StatusMessage - (receive [this _ signature timestamp {:keys [metadata js-obj] :as cofx}] - (group-chats/handle-membership-update-received cofx this signature {:whisper-timestamp timestamp - :metadata metadata}))) - (extend-type transport.contact/ContactRequest protocol/StatusMessage (receive [this _ signature timestamp cofx] diff --git a/src/status_im/transport/impl/send.cljs b/src/status_im/transport/impl/send.cljs index 4b970e6ff9..4042156fef 100644 --- a/src/status_im/transport/impl/send.cljs +++ b/src/status_im/transport/impl/send.cljs @@ -6,14 +6,8 @@ [status-im.transport.db :as transport.db] [status-im.transport.message.pairing :as transport.pairing] [status-im.transport.message.contact :as transport.contact] - [status-im.transport.message.group-chat :as transport.group-chat] [status-im.transport.message.protocol :as protocol])) -(extend-type transport.group-chat/GroupMembershipUpdate - protocol/StatusMessage - (send [this chat-id cofx] - (group-chats/send-membership-update cofx this chat-id))) - (extend-type transport.pairing/PairInstallation protocol/StatusMessage (send [this _ cofx] diff --git a/src/status_im/transport/message/group_chat.cljs b/src/status_im/transport/message/group_chat.cljs deleted file mode 100644 index b0abea5de6..0000000000 --- a/src/status_im/transport/message/group_chat.cljs +++ /dev/null @@ -1,12 +0,0 @@ -(ns status-im.transport.message.group-chat - (:require [cljs.spec.alpha :as spec] - [status-im.transport.message.protocol :as protocol] - [taoensso.timbre :as log])) - -(defrecord GroupMembershipUpdate - [chat-id membership-updates message] - protocol/StatusMessage - (validate [this] - (if (spec/valid? :message/group-membership-update this) - this - (log/warn "failed group membership validation" (spec/explain :message/group-membership-update this))))) diff --git a/src/status_im/transport/message/transit.cljs b/src/status_im/transport/message/transit.cljs index a8edd3a4e2..e0cd571b35 100644 --- a/src/status_im/transport/message/transit.cljs +++ b/src/status_im/transport/message/transit.cljs @@ -2,7 +2,6 @@ status-im.transport.message.transit (:require [status-im.transport.message.contact :as contact] [status-im.transport.message.protocol :as protocol] - [status-im.transport.message.group-chat :as group-chat] [status-im.transport.message.pairing :as pairing] [status-im.constants :as constants] [cognitect.transit :as transit])) @@ -50,12 +49,6 @@ ;; no need for legacy conversions for rest of the content types #js [content content-type message-type clock-value timestamp]))) -(deftype GroupMembershipUpdateHandler [] - Object - (tag [this v] "g5") - (rep [this {:keys [chat-id membership-updates message]}] - #js [chat-id membership-updates message])) - (deftype SyncInstallationHandler [] Object (tag [this v] "p1") @@ -74,7 +67,6 @@ contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.) contact/ContactUpdate (ContactUpdateHandler.) protocol/Message (MessageHandler.) - group-chat/GroupMembershipUpdate (GroupMembershipUpdateHandler.) pairing/SyncInstallation (SyncInstallationHandler.) pairing/PairInstallation (PairInstallationHandler.)}})) @@ -109,8 +101,6 @@ "c5" (fn []) "c6" (fn [[name profile-image address _ _]] (contact/ContactUpdate. name profile-image address nil nil)) - "g5" (fn [[chat-id membership-updates message]] - (group-chat/GroupMembershipUpdate. chat-id membership-updates message)) "p1" (fn [[contacts account chat]] (pairing/SyncInstallation. contacts account chat)) "p2" (fn [[installation-id device-type name _]] diff --git a/src/status_im/ui/components/colors.cljs b/src/status_im/ui/components/colors.cljs index ed8439ddef..0e01d307c9 100644 --- a/src/status_im/ui/components/colors.cljs +++ b/src/status_im/ui/components/colors.cljs @@ -67,6 +67,8 @@ purple "#8B3131"]) +(def mention-incoming "#0DA4C9") +(def mention-outgoing "#9EE8FA") (def text black) (def text-gray gray) diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 1aa18fda02..6de558f662 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -18,6 +18,10 @@ [status-im.utils.platform :as platform]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) +(defview mention-element [from] + (letsubs [{:keys [ens-name alias]} [:contacts/contact-name-by-identity from]] + (str "@" (or ens-name alias)))) + (defn message-timestamp [t justify-timestamp? outgoing content content-type] [react/text {:style (style/message-timestamp-text @@ -86,6 +90,14 @@ [:browser.ui/message-link-pressed destination])))} destination]) + "mention" + (conj acc [react/text-class + {:style {:color (if outgoing colors/mention-outgoing colors/mention-incoming)} + :on-press + #(re-frame/dispatch + [:chat.ui/start-chat literal {:navigation-reset? true}])} + [mention-element literal]]) + "status-tag" (conj acc [react/text-class {:style {:color (if outgoing colors/white colors/blue) diff --git a/status-go-version.json b/status-go-version.json index d8bb737f3c..1ca16f8b5d 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.36.2", - "commit-sha1": "4c0d8dedea10b02bbad476170cc0eef61a92ecbf", - "src-sha256": "0a13rk9p13s3p1dz3n7wbb3s343dlqsidmphxz57xw6di2s40nzx" + "version": "v0.38.0", + "commit-sha1": "baa0767c263fa25fdd8d90a42d13f1f1fffce804", + "src-sha256": "0m7baga353vyjqb8zlzd22w68kkm24baq8059a89xdv57ln35axi" } diff --git a/test/appium/tests/atomic/account_management/test_profile.py b/test/appium/tests/atomic/account_management/test_profile.py index 8c3713391c..95d01d02b2 100644 --- a/test/appium/tests/atomic/account_management/test_profile.py +++ b/test/appium/tests/atomic/account_management/test_profile.py @@ -861,14 +861,13 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase): self.errors.append('Public chat "%s" doesn\'t appear on other device when devices are paired' % public_chat_before_sync_name) - # 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() + 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') diff --git a/test/appium/tests/atomic/chats/test_group_chat.py b/test/appium/tests/atomic/chats/test_group_chat.py index c3558a0d8b..c13aef529e 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 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/data_store/chats.cljs b/test/cljs/status_im/test/data_store/chats.cljs index 23868c4039..8dfb1e2ea1 100644 --- a/test/cljs/status_im/test/data_store/chats.cljs +++ b/test/cljs/status_im/test/data_store/chats.cljs @@ -13,15 +13,7 @@ :admins #{"a" "b"} :members-joined #{"a" "c"} :name "name" - :membership-updates [{:chat-id "chat-id" - :from "a" - :signature "b" - :events [{:type "chat-created" - :name "test" - :clock-value 1} - {:type "members-added" - :clock-value 2 - :members ["a" "b"]}]}] + :membership-update-events :events :gaps-loaded? true :unviewed-messages-count 2 :is-active true @@ -49,25 +41,13 @@ :admin false :joined false}} :lastClockValue 10 - :membershipUpdates #{{:type "chat-created" - :name "test" - :clockValue 1 - :id "0xcdf4a63e0c98d0018cf532b3a48350bb80e292cc46249e3f876aaa65eb97a231" - :signature "b" - :from "a"} - {:type "members-added" - :clockValue 2 - :members ["a" "b"] - :id "0x1c34d6b4d022c432b7eb6b645e095791af0c6bdb626db7d705e6db0d7cd74b56" - :signature "b" - :from "a"}} + :membershipUpdateEvents :events :unviewedMessagesCount 2 :active true :timestamp 2}] (testing "marshaling chat" (is (= expected-chat (-> (chats/->rpc chat) - (update :members #(into #{} %)) - (update :membershipUpdates #(into #{} %)))))))) + (update :members #(into #{} %)))))))) (deftest normalize-chat-test (let [chat {:id "chat-id" @@ -87,18 +67,7 @@ :admin false :joined false}] :lastClockValue 10 - :membershipUpdates [{:type "chat-created" - :name "test" - :clockValue 1 - :id "0xcdf4a63e0c98d0018cf532b3a48350bb80e292cc46249e3f876aaa65eb97a231" - :signature "b" - :from "a"} - {:type "members-added" - :clockValue 2 - :members ["a" "b"] - :id "0x1c34d6b4d022c432b7eb6b645e095791af0c6bdb626db7d705e6db0d7cd74b56" - :signature "b" - :from "a"}] + :membershipUpdateEvents :events :unviewedMessagesCount 2 :active true :timestamp 2} @@ -111,34 +80,10 @@ :admins #{"a" "b"} :members-joined #{"a" "c"} :name "name" - :membership-updates #{{:chat-id "chat-id" - :from "a" - :signature "b" - :events [{:type "chat-created" - :name "test" - :clock-value 1} - {:type "members-added" - :clock-value 2 - :members #{"a" "b"}}]}} + :membership-update-events :events :unviewed-messages-count 2 :is-active true :chat-id "chat-id" :timestamp 2}] (testing "from-rpc" (is (= expected-chat (chats/<-rpc chat)))))) - -(deftest marshal-membership-updates-test - (let [raw-updates [{:chat-id "chat-id" - :signature "b" - :from "id-1" - :events [{:type "chat-created" :clock-value 0 :name "blah"}]} - {:chat-id "chat-id" - :signature "a" - :from "id-2" - :events [{:type "members-added" :clock-value 10 :members [1 2]} - {:type "member-removed" :clock-value 11 :member 1}]}] - expected #{{:type "members-added" :clockValue 10 :from "id-2" :members [1 2] :signature "a" :id "0xb7690375de21da4890d2d5acca8b56e327d9eb75fd3b4bcceca4bf1679c2f830"} - {:type "member-removed" :clockValue 11 :from "id-2" :member 1 :signature "a" :id "0x2a66f195abf6e6903c4245e372e1e2e6aea2b2c0a74ad03080a313e94197a64f"} - {:type "chat-created" :clockValue 0 :from "id-1" :name "blah" :signature "b" :id "0x7fad22accf1dec64daedf83e7af19b0dcde8c5facfb479874a48da5fb6967e07"}} - actual (into #{} (chats/marshal-membership-updates raw-updates))] - (is (= expected actual)))) diff --git a/test/cljs/status_im/test/group_chats/core.cljs b/test/cljs/status_im/test/group_chats/core.cljs deleted file mode 100644 index 2f1cb6d1da..0000000000 --- a/test/cljs/status_im/test/group_chats/core.cljs +++ /dev/null @@ -1,553 +0,0 @@ -(ns status-im.test.group-chats.core - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.utils.gfycat.core :as gfycat] - [status-im.utils.identicon :as identicon] - [status-im.ethereum.json-rpc :as json-rpc] - [status-im.utils.clocks :as utils.clocks] - [status-im.utils.config :as config] - [status-im.group-chats.core :as group-chats])) - -(def random-id "685a9351-417e-587c-8bc1-191ac2a57ef8") -(def chat-name "chat-name") - -(def member-1 "member-1") -(def member-2 "member-2") -(def member-3 "member-3") -(def member-4 "member-4") - -(def admin member-1) - -(def chat-id (str random-id admin)) - -(def initial-message {:chat-id chat-id - :membership-updates [{:from admin - :events [{:type "chat-created" - :name "chat-name" - :clock-value 1} - {:type "members-added" - :clock-value 3 - :members [member-2 member-3]}]}]}) - -(deftest get-last-clock-value-test - (is (= 3 (group-chats/get-last-clock-value {:db {:chats {chat-id {:last-clock-value 3}}}} chat-id)))) - -(deftest handle-group-membership-update-test - (with-redefs [gfycat/generate-gfy (constantly "generated") - identicon/identicon (constantly "generated")] - - (testing "a brand new chat" - (let [actual (-> - (group-chats/handle-membership-update {:now 0 :db {}} initial-message "payload" admin) - :db - :chats - (get chat-id))] - (testing "it creates a new chat" - (is actual)) - (testing "it sets the right chat-name" - (is (= "chat-name" - (:name actual)))) - (testing "it sets the right chat-id" - (is (= chat-id - (:chat-id actual)))) - (testing "it sets the right participants" - (is (= #{member-1 member-2 member-3} - (:contacts actual)))) - (testing "it sets the updates" - (is (= (:membership-updates initial-message) - (:membership-updates actual)))) - (testing "it sets the right admins" - (is (= #{admin} - (:admins actual)))))) - (testing "a chat with the wrong id" - (let [bad-chat-id (str random-id member-2) - actual (-> - (group-chats/handle-membership-update - {:now 0 :db {}} - (assoc initial-message :chat-id bad-chat-id) - "payload" - admin) - :db - :chats - (get bad-chat-id))] - (testing "it does not create a chat" - (is (not actual))))) - (testing "an already existing chat" - (let [cofx (assoc - (group-chats/handle-membership-update {:now 0 :db {:multiaccount {:public-key member-3}}} initial-message "payload" admin) - :now 0)] - (testing "the message has already been received" - (let [actual (group-chats/handle-membership-update cofx initial-message "payload" admin)] - (testing "it noops" - (is (= - (get-in cofx [:db :chats chat-id]) - (get-in actual [:db :chats chat-id])))))) - (testing "a chat we have deleted" - (let [after-leaving-cofx (-> (group-chats/handle-membership-update cofx - {:chat-id chat-id - :membership-updates [{:from member-1 - :events [{:type "chat-created" - :clock-value 1 - :name "group-name"} - {:type "admins-added" - :clock-value 10 - :members [member-2]} - {:type "admin-removed" - :clock-value 11 - :member member-1}]} - {:from member-3 - :events [{:type "member-removed" - :clock-value 12 - :member member-3}]}]} - "payload" - member-3) - (assoc-in [:db :chats chat-id :is-active] false)) - after-been-invited-again-cofx (group-chats/handle-membership-update (assoc after-leaving-cofx :now 0) - {:chat-id chat-id - :membership-updates [{:from member-1 - :events [{:type "chat-created" - :clock-value 1 - :name "group-name"} - {:type "admins-added" - :clock-value 10 - :members [member-2]} - {:type "admin-removed" - :clock-value 11 - :member member-1}]} - {:from member-2 - :events [{:type "members-added" - :clock-value 13 - :members [member-3]}]}]} - "payload" - member-2)] - - (testing "it sets the chat active after been invited again" - (is (get-in after-been-invited-again-cofx [:db :chats chat-id :is-active]))))) - (testing "a new message comes in" - (let [actual (group-chats/handle-membership-update cofx - {:chat-id chat-id - :membership-updates [{:from member-1 - :events [{:type "chat-created" - :clock-value 1 - :name "group-name"} - {:type "admins-added" - :clock-value 10 - :members [member-2]} - {:type "admin-removed" - :clock-value 11 - :member member-1}]} - {:from member-2 - :events [{:type "member-removed" - :clock-value 12 - :member member-3} - {:type "members-added" - :clock-value 12 - :members [member-4]} - {:type "name-changed" - :clock-value 13 - :name "new-name"}]}]} - "payload" - member-3) - actual-chat (get-in actual [:db :chats chat-id])] - (testing "the chat is updated" - (is actual-chat)) - (testing "admins are updated" - (is (= #{member-2} (:admins actual-chat)))) - (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)))))))))) - -(deftest build-group-test - (testing "only adds" - (let [events [{:type "chat-created" - :clock-value 0 - :name "chat-name" - :from "1"} - {:type "members-added" - :clock-value 1 - :from "1" - :members ["2"]} - {:type "admins-added" - :clock-value 2 - :from "1" - :members ["2"]} - {:type "members-added" - :clock-value 3 - :from "2" - :members ["3"]} - {:type "member-joined" - :clock-value 4 - :from "3" - :member "3"}] - expected {:name "chat-name" - :created-at 0 - "2" {:added 1 - :admin-added 2} - "3" {:added 3 - :joined 4} - :admins #{"1" "2"} - :members-joined #{"1" "3"} - :contacts #{"1" "2" "3"}}] - (is (= expected (group-chats/build-group events))))) - (testing "adds and removes" - (let [events [{:type "chat-created" - :clock-value 0 - :name "chat-name" - :from "1"} - {:type "members-added" - :clock-value 1 - :from "1" - :members ["2"]} - {:type "member-joined" - :clock-value 3 - :from "2" - :member "2"} - {:type "admins-added" - :clock-value 4 - :from "1" - :members ["2"]} - {:type "admin-removed" - :clock-value 5 - :from "2" - :member "2"} - {:type "member-removed" - :clock-value 6 - :from "2" - :member "2"}] - expected {:name "chat-name" - :created-at 0 - "2" {:added 1 - :joined 3 - :admin-added 4 - :admin-removed 5 - :removed 6} - :admins #{"1"} - :members-joined #{"1"} - :contacts #{"1"}}] - (is (= expected (group-chats/build-group events))))) - (testing "an admin removing themselves" - (let [events [{:type "chat-created" - :clock-value 0 - :name "chat-name" - :from "1"} - {:type "members-added" - :clock-value 1 - :from "1" - :members ["2"]} - {:type "admins-added" - :clock-value 2 - :from "1" - :members ["2"]} - {:type "member-removed" - :clock-value 3 - :from "2" - :member "2"}] - expected {:name "chat-name" - :created-at 0 - :members-joined #{"1"} - "2" {:added 1 - :admin-added 2 - :removed 3} - :admins #{"1"} - :contacts #{"1"}}] - (is (= expected (group-chats/build-group events))))) - (testing "name changed" - (let [events [{:type "chat-created" - :clock-value 0 - :name "chat-name" - :from "1"} - {:type "members-added" - :clock-value 1 - :from "1" - :members ["2"]} - {:type "admins-added" - :clock-value 2 - :from "1" - :members ["2"]} - {:type "name-changed" - :clock-value 3 - :from "2" - :name "new-name"}] - expected {:name "new-name" - :created-at 0 - :members-joined #{"1"} - :name-changed-by "2" - :name-changed-at 3 - "2" {:added 1 - :admin-added 2} - :admins #{"1" "2"} - :contacts #{"1" "2"}}] - (is (= expected (group-chats/build-group events))))) - (testing "invalid events" - (let [events [{:type "chat-created" - :name "chat-name" - :clock-value 0 - :from "1"} - {:type "admins-added" ; can't make an admin a user not in the group - :clock-value 1 - :from "1" - :members ["non-existing"]} - {:type "members-added" - :clock-value 2 - :from "1" - :members ["2"]} - {:type "member-joined" ; non-invited user joining - :clock-value 2 - :from "non-invited" - :member "non-invited"} - {:type "admins-added" - :clock-value 3 - :from "1" - :members ["2"]} - {:type "members-added" - :clock-value 4 - :from "2" - :members ["3"]} - {:type "admin-removed" ; can't remove an admin from admins unless it's the same user - :clock-value 5 - :from "1" - :member "2"} - {:type "member-joined" - :clock-value 5 - :from "2" - :member "2"} - {:type "member-removed" ; can't remove an admin from the group - :clock-value 6 - :from "1" - :member "2"} - {:type "members-added" - :clock-value 7 - :from "2" - :members ["4"]} - {:type "member-joined" - :clock-value 8 - :from "4" - :member "4"} - {:type "member-removed" - :clock-value 9 - :from "1" - :member "4"} - {:type "member-joined" ; join after being removed - :clock-value 10 - :from "4" - :member "4"}] - expected {:name "chat-name" - :members-joined #{"1" "2"} - :created-at 0 - "2" {:added 2 - :admin-added 3 - :joined 5} - "3" {:added 4} - "4" {:added 7 - :joined 8 - :removed 9} - :admins #{"1" "2"} - :contacts #{"1" "2" "3"}}] - (is (= expected (group-chats/build-group events))))) - (testing "out of order-events" - (let [events [{:type "chat-created" - :name "chat-name" - :clock-value 0 - :from "1"} - {:type "admins-added" - :clock-value 2 - :from "1" - :members ["2"]} - {:type "members-added" - :clock-value 1 - :from "1" - :members ["2"]} - {:type "members-added" - :clock-value 3 - :from "2" - :members ["3"]}] - expected {:name "chat-name" - :created-at 0 - :members-joined #{"1"} - "2" {:added 1 - :admin-added 2} - "3" {:added 3} - :admins #{"1" "2"} - :contacts #{"1" "2" "3"}}] - (is (= expected (group-chats/build-group events)))))) - -(deftest valid-event-test - (let [multi-admin-group {:admins #{"1" "2"} - :contacts #{"1" "2" "3"}} - single-admin-group {:admins #{"1"} - :contacts #{"1" "2" "3"}}] - (testing "members-added" - (testing "admins can add members" - (is (group-chats/valid-event? multi-admin-group - {:type "members-added" :clock-value 6 :from "1" :members ["4"]}))) - (testing "non-admin members cannot add members" - (is (not (group-chats/valid-event? multi-admin-group - {:type "members-added" :clock-value 6 :from "3" :members ["4"]}))))) - (testing "admins-added" - (testing "admins can make other member admins" - (is (group-chats/valid-event? multi-admin-group - {:type "admins-added" :clock-value 6 :from "1" :members ["3"]}))) - (testing "non-admins can't make other member admins" - (is (not (group-chats/valid-event? multi-admin-group - {:type "admins-added" :clock-value 6 :from "3" :members ["3"]})))) - (testing "non-existing users can't be made admin" - (is (not (group-chats/valid-event? multi-admin-group - {:type "admins-added" :clock-value 6 :from "1" :members ["not-existing"]}))))) - (testing "member-removed" - (testing "admins can remove non-admin members" - (is (group-chats/valid-event? multi-admin-group - {:type "member-removed" :clock-value 6 :from "1" :member "3"}))) - (testing "admins can remove themselves" - (is (group-chats/valid-event? multi-admin-group - {:type "member-removed" :clock-value 6 :from "1" :member "1"}))) - (testing "participants non-admin can remove themselves" - (is (group-chats/valid-event? multi-admin-group - {:type "member-removed" :clock-value 6 :from "3" :member "3"}))) - (testing "non-admin can't remove other members" - (is (not (group-chats/valid-event? multi-admin-group - {:type "member-removed" :clock-value 6 :from "3" :member "1"}))))) - (testing "admin-removed" - (testing "admins can remove themselves" - (is (group-chats/valid-event? multi-admin-group - {:type "admin-removed" :clock-value 6 :from "1" :member "1"}))) - (testing "admins can't remove other admins" - (is (not (group-chats/valid-event? multi-admin-group - {:type "admin-removed" :clock-value 6 :from "1" :member "2"})))) - (testing "participants non-admin can't remove other admins" - (is (not (group-chats/valid-event? multi-admin-group - {:type "admin-removed" :clock-value 6 :from "3" :member "1"})))) - (testing "the last admin can be removed" - (is (group-chats/valid-event? single-admin-group - {:type "admin-removed" :clock-value 6 :from "1" :member "1"}))) - (testing "name-changed" - (testing "a change from an admin" - (is (group-chats/valid-event? multi-admin-group - {:type "name-changed" :clock-value 6 :from "1" :name "new-name"})))) - (testing "a change from an non-admin" - (is (not (group-chats/valid-event? multi-admin-group - {:type "name-changed" :clock-value 6 :from "3" :name "new-name"})))) - (testing "an empty name" - (is (not (group-chats/valid-event? multi-admin-group - {:type "name-changed" :clock-value 6 :from "1" :name " "}))))))) - -(deftest create-test - (testing "create a new chat" - (with-redefs [utils.clocks/send inc] - (let [cofx {:random-guid-generator (constantly "random") - :db {:multiaccount {:public-key "me"} - :group/selected-contacts #{"1" "2"}}}] - (is (= {:chat-id "randomme" - :from "me" - :events [{:type "chat-created" - :clock-value 1 - :name "group-name"} - {:type "members-added" - :clock-value 2 - :members #{"1" "2"}}]} - (:group-chats/sign-membership (group-chats/create cofx "group-name")))))))) - -(deftest signature-pairs-test - (let [event-1 {:from "1" - :signature "signature-1" - :events [{:type "a" :name "a" :clock-value 1} - {:type "b" :name "b" :clock-value 2}]} - event-2 {:from "2" - :signature "signature-2" - :events [{:type "c" :name "c" :clock-value 1} - {:type "d" :name "d" :clock-value 2}]} - message {:chat-id "randomme" - - :membership-updates [event-1 - event-2]} - expected (js/JSON.stringify - (clj->js [[(group-chats/signature-material "randomme" (:events event-1)) - "signature-1"] - [(group-chats/signature-material "randomme" (:events event-2)) - "signature-2"]]))] - - (is (= expected (group-chats/signature-pairs message))))) - -(deftest signature-material-test - (is (= (js/JSON.stringify (clj->js [[[["a" "a-value"] - ["b" "b-value"] - ["c" "c-value"]] - [["a" "a-value"] - ["e" "e-value"]]] "chat-id"])) - (group-chats/signature-material "chat-id" [{:b "b-value" - :a "a-value" - :c "c-value"} - {:e "e-value" - :a "a-value"}])))) - -(deftest remove-group-chat-test - (with-redefs [json-rpc/call (constantly nil) - utils.clocks/send inc] - (let [cofx {:db {:chats {chat-id {:admins #{member-1 member-2} - :name "chat-name" - :chat-id chat-id - :last-clock-value 3 - :is-active true - :group-chat true - :contacts #{member-1 member-2 member-3} - :membership-updates (:membership-updates initial-message)}}}}] - (testing "removing a member" - (is (= {:from member-3 - :chat-id chat-id - :events [{:type "member-removed" :member member-3 :clock-value 4}]} - (:group-chats/sign-membership - (group-chats/remove - (assoc-in cofx [:db :multiaccount :public-key] member-3) - chat-id))))) - (testing "removing an admin" - (is (= {:from member-1 - :chat-id chat-id - :events [{:type "member-removed" :member member-1 :clock-value 4}]} - (:group-chats/sign-membership - (group-chats/remove - (assoc-in cofx [:db :multiaccount :public-key] member-1) - chat-id)))))))) - -(deftest add-members-test - (with-redefs [utils.clocks/send inc] - (testing "add-members" - (let [cofx {:db {:current-chat-id chat-id - :selected-participants ["new-member"] - :multiaccount {:public-key "me"} - :chats {chat-id {:last-clock-value 1 - :membership-updates [{:events [{:clock-value 1}]}]}}}}] - (is (= {:chat-id chat-id - :from "me" - :events [{:type "members-added" - :clock-value 2 - :members ["new-member"]}]} - (:group-chats/sign-membership (group-chats/add-members cofx)))))))) - -(deftest remove-member-test - (with-redefs [utils.clocks/send inc] - (testing "remove-member" - (let [cofx {:db {:multiaccount {:public-key "me"} - :chats {chat-id {:admins #{"me"} - :last-clock-value 1 - :contacts #{"member"} - :membership-updates [{:events [{:clock-value 1}]}]}}}}] - (is (= {:chat-id chat-id - :from "me" - :events [{:type "member-removed" - :clock-value 2 - :member "member"}]} - (:group-chats/sign-membership (group-chats/remove-member cofx chat-id "member")))))))) - -(deftest make-admin-test - (with-redefs [utils.clocks/send inc] - (testing "make-admin" - (let [cofx {:db {:multiaccount {:public-key "me"} - :chats {chat-id {:admins #{"me"} - :last-clock-value 1 - :contacts #{"member"} - :membership-updates [{:events [{:clock-value 1}]}]}}}}] - (is (= {:chat-id chat-id - :from "me" - :events [{:type "admins-added" - :clock-value 2 - :members ["member"]}]} - (:group-chats/sign-membership (group-chats/make-admin cofx chat-id "member")))))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index ba293f37d9..ef71c28100 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -20,7 +20,6 @@ [status-im.test.ethereum.mnemonic] [status-im.test.ethereum.stateofus] [status-im.test.fleet.core] - [status-im.test.group-chats.core] [status-im.test.hardwallet.core] [status-im.test.i18n] [status-im.test.mailserver.core] @@ -93,7 +92,6 @@ 'status-im.test.ethereum.mnemonic 'status-im.test.ethereum.stateofus 'status-im.test.fleet.core - 'status-im.test.group-chats.core 'status-im.test.hardwallet.core 'status-im.test.i18n 'status-im.test.mailserver.core