Move group chats to status-go

This commit completely remove transit for group chats. All the
processing is now done in status-go.
Also introuduces parsing and handling of mentions, needed so that system
messages can be easily built in status-go.

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2019-12-09 15:55:42 +01:00
parent 4a1842285f
commit 54cf783d5b
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
18 changed files with 133 additions and 1194 deletions

View File

@ -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]

View File

@ -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})

View File

@ -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)))

View File

@ -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" {}

View File

@ -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

View File

@ -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))))

View File

@ -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]

View File

@ -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]

View File

@ -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)))))

View File

@ -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 _]]

View File

@ -67,6 +67,8 @@
purple
"#8B3131"])
(def mention-incoming "#0DA4C9")
(def mention-outgoing "#9EE8FA")
(def text black)
(def text-gray gray)

View File

@ -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)

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' 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"
}

View File

@ -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')

View File

@ -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):

View File

@ -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))))

View File

@ -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"))))))))

View File

@ -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