mirror of
https://github.com/status-im/status-react.git
synced 2025-01-09 10:42:53 +00:00
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:
parent
4a1842285f
commit
54cf783d5b
@ -13,7 +13,6 @@
|
|||||||
[status-im.ethereum.core :as ethereum]
|
[status-im.ethereum.core :as ethereum]
|
||||||
[status-im.mailserver.core :as mailserver]
|
[status-im.mailserver.core :as mailserver]
|
||||||
[status-im.native-module.core :as status]
|
[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.protocol :as protocol]
|
||||||
[status-im.transport.message.transit :as transit]
|
[status-im.transport.message.transit :as transit]
|
||||||
[status-im.transport.utils :as transport.utils]
|
[status-im.transport.utils :as transport.utils]
|
||||||
@ -116,29 +115,32 @@
|
|||||||
(get-in cofx [:db :chats chat-id :public?])) chat-id
|
(get-in cofx [:db :chats chat-id :public?])) chat-id
|
||||||
(and (= constants/message-type-one-to-one message-type)
|
(and (= constants/message-type-one-to-one message-type)
|
||||||
(= (multiaccounts.model/current-public-key cofx) from)) chat-id
|
(= (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))
|
(= constants/message-type-one-to-one message-type) from))
|
||||||
|
|
||||||
(fx/defn update-unviewed-count
|
(fx/defn update-unviewed-count
|
||||||
[{:keys [now db] :as cofx} {:keys [chat-id
|
[{:keys [now db] :as cofx} {:keys [chat-id
|
||||||
from
|
from
|
||||||
|
message-type
|
||||||
message-id] :as message}]
|
message-id] :as message}]
|
||||||
(let [{:keys [current-chat-id view-id]} db
|
(when-not (= message-type constants/message-type-private-group-system-message)
|
||||||
chat-view? (or (= :chat view-id)
|
(let [{:keys [current-chat-id view-id]} db
|
||||||
(= :chat-modal view-id))
|
chat-view? (or (= :chat view-id)
|
||||||
current-count (get-in db [:chats chat-id :unviewed-messages-count])]
|
(= :chat-modal view-id))
|
||||||
(cond
|
current-count (get-in db [:chats chat-id :unviewed-messages-count])]
|
||||||
(= from (multiaccounts.model/current-public-key cofx))
|
(cond
|
||||||
;; nothing to do
|
(= from (multiaccounts.model/current-public-key cofx))
|
||||||
nil
|
;; nothing to do
|
||||||
|
nil
|
||||||
|
|
||||||
(and chat-view? (= current-chat-id chat-id))
|
(and chat-view? (= current-chat-id chat-id))
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(data-store.messages/mark-messages-seen current-chat-id [message-id]))
|
(data-store.messages/mark-messages-seen current-chat-id [message-id]))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
{:db (update-in db [:chats chat-id]
|
{:db (update-in db [:chats chat-id]
|
||||||
assoc
|
assoc
|
||||||
:unviewed-messages-count (inc current-count))})))
|
:unviewed-messages-count (inc current-count))}))))
|
||||||
|
|
||||||
(fx/defn receive-one
|
(fx/defn receive-one
|
||||||
[cofx message]
|
[cofx message]
|
||||||
|
@ -7,15 +7,15 @@
|
|||||||
|
|
||||||
(def ms-in-bg-for-require-bioauth 5000)
|
(def ms-in-bg-for-require-bioauth 5000)
|
||||||
|
|
||||||
(def content-type-text 0)
|
(def content-type-text 1)
|
||||||
(def content-type-sticker 1)
|
(def content-type-sticker 2)
|
||||||
(def content-type-status 2)
|
(def content-type-status 3)
|
||||||
(def content-type-emoji 3)
|
(def content-type-emoji 4)
|
||||||
|
|
||||||
(def message-type-one-to-one 0)
|
(def message-type-one-to-one 1)
|
||||||
(def message-type-public-group 1)
|
(def message-type-public-group 2)
|
||||||
(def message-type-private-group 2)
|
(def message-type-private-group 3)
|
||||||
(def message-type-private-group-system-message 3)
|
(def message-type-private-group-system-message 4)
|
||||||
|
|
||||||
(def desktop-content-types
|
(def desktop-content-types
|
||||||
#{content-type-text content-type-emoji content-type-status})
|
#{content-type-text content-type-emoji content-type-status})
|
||||||
|
@ -34,32 +34,6 @@
|
|||||||
; Build an event id from a message
|
; Build an event id from a message
|
||||||
(def event-id (comp ethereum/sha3 event->string))
|
(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}]
|
(defn type->rpc [{:keys [public? group-chat] :as chat}]
|
||||||
(assoc chat :chatType (cond
|
(assoc chat :chatType (cond
|
||||||
public? public-chat-type
|
public? public-chat-type
|
||||||
@ -110,10 +84,9 @@
|
|||||||
(-> chat
|
(-> chat
|
||||||
type->rpc
|
type->rpc
|
||||||
marshal-members
|
marshal-members
|
||||||
(update :membership-updates marshal-membership-updates)
|
|
||||||
(update :last-message messages/->rpc)
|
(update :last-message messages/->rpc)
|
||||||
(clojure.set/rename-keys {:chat-id :id
|
(clojure.set/rename-keys {:chat-id :id
|
||||||
:membership-updates :membershipUpdates
|
:membership-update-events :membershipUpdateEvents
|
||||||
:unviewed-messages-count :unviewedMessagesCount
|
:unviewed-messages-count :unviewedMessagesCount
|
||||||
:last-message :lastMessage
|
:last-message :lastMessage
|
||||||
:deleted-at-clock-value :deletedAtClockValue
|
:deleted-at-clock-value :deletedAtClockValue
|
||||||
@ -130,13 +103,12 @@
|
|||||||
rpc->type
|
rpc->type
|
||||||
unmarshal-members
|
unmarshal-members
|
||||||
(clojure.set/rename-keys {:id :chat-id
|
(clojure.set/rename-keys {:id :chat-id
|
||||||
:membershipUpdates :membership-updates
|
:membershipUpdateEvents :membership-update-events
|
||||||
:deletedAtClockValue :deleted-at-clock-value
|
:deletedAtClockValue :deleted-at-clock-value
|
||||||
:unviewedMessagesCount :unviewed-messages-count
|
:unviewedMessagesCount :unviewed-messages-count
|
||||||
:lastMessage :last-message
|
:lastMessage :last-message
|
||||||
:active :is-active
|
:active :is-active
|
||||||
:lastClockValue :last-clock-value})
|
:lastClockValue :last-clock-value})
|
||||||
(update :membership-updates (partial unmarshal-membership-updates (:id chat)))
|
|
||||||
(update :last-message #(when % (messages/<-rpc %)))
|
(update :last-message #(when % (messages/<-rpc %)))
|
||||||
(dissoc :chatType :members)))
|
(dissoc :chatType :members)))
|
||||||
|
|
||||||
|
@ -36,6 +36,13 @@
|
|||||||
"shhext_enableInstallation" {}
|
"shhext_enableInstallation" {}
|
||||||
"shhext_disableInstallation" {}
|
"shhext_disableInstallation" {}
|
||||||
"shhext_sendChatMessage" {}
|
"shhext_sendChatMessage" {}
|
||||||
|
"shhext_confirmJoiningGroup" {}
|
||||||
|
"shhext_addAdminsToGroupChat" {}
|
||||||
|
"shhext_addMembersToGroupChat" {}
|
||||||
|
"shhext_removeMemberFromGroupChat" {}
|
||||||
|
"shhext_leaveGroupChat" {}
|
||||||
|
"shhext_changeGroupChatName" {}
|
||||||
|
"shhext_createGroupChatWithMembers" {}
|
||||||
"shhext_reSendChatMessage" {}
|
"shhext_reSendChatMessage" {}
|
||||||
"shhext_getOurInstallations" {}
|
"shhext_getOurInstallations" {}
|
||||||
"shhext_setInstallationMetadata" {}
|
"shhext_setInstallationMetadata" {}
|
||||||
|
@ -1170,17 +1170,6 @@
|
|||||||
(fn [cofx [_ chat-id]]
|
(fn [cofx [_ chat-id]]
|
||||||
(group-chats/join-chat 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
|
;; profile module
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
[status-im.chat.models.message-content :as message-content]
|
[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.core :as multiaccounts]
|
||||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||||
[status-im.utils.pairing :as pairing.utils]
|
[status-im.utils.pairing :as pairing.utils]
|
||||||
@ -17,7 +20,6 @@
|
|||||||
[status-im.group-chats.db :as group-chats.db]
|
[status-im.group-chats.db :as group-chats.db]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.native-module.core :as native-module]
|
[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.transport.message.protocol :as protocol]
|
||||||
[status-im.utils.clocks :as utils.clocks]
|
[status-im.utils.clocks :as utils.clocks]
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
@ -25,266 +27,81 @@
|
|||||||
[status-im.mailserver.topics :as mailserver.topics]
|
[status-im.mailserver.topics :as mailserver.topics]
|
||||||
[taoensso.timbre :as log]))
|
[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
|
(fx/defn remove-member
|
||||||
"Format group update message and sign membership"
|
"Format group update message and sign membership"
|
||||||
[{:keys [db] :as cofx} chat-id member]
|
[{:keys [db] :as cofx} chat-id member]
|
||||||
(let [my-public-key (multiaccounts.model/current-public-key cofx)
|
{::json-rpc/call [{:method "shhext_removeMemberFromGroupChat"
|
||||||
last-clock-value (get-last-clock-value cofx chat-id)
|
:params [nil chat-id member]
|
||||||
chat (get-in cofx [:db :chats chat-id])
|
:on-success #(re-frame/dispatch [::chat-updated %])}]})
|
||||||
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]}})))
|
|
||||||
|
|
||||||
(fx/defn join-chat
|
(fx/defn set-up-filter
|
||||||
"Format group update message and sign membership"
|
"Listen/Tear down the shared topic/contact-codes. Stop listening for members who
|
||||||
[cofx chat-id]
|
have left the chat"
|
||||||
(let [my-public-key (multiaccounts.model/current-public-key cofx)
|
[cofx chat-id previous-chat]
|
||||||
last-clock-value (get-last-clock-value cofx chat-id)
|
(let [my-public-key (multiaccounts.model/current-public-key cofx)
|
||||||
chat (get-in cofx [:db :chats chat-id])
|
new-chat (get-in cofx [:db :chats chat-id])
|
||||||
event (member-joined-event last-clock-value my-public-key)]
|
members (:members-joined new-chat)]
|
||||||
(when (valid-event? chat (assoc event
|
;; If we left the chat do nothing
|
||||||
:from
|
(when-not (and (group-chats.db/joined? my-public-key previous-chat)
|
||||||
my-public-key))
|
(not (group-chats.db/joined? my-public-key new-chat)))
|
||||||
(fx/merge
|
(fx/merge
|
||||||
cofx
|
cofx
|
||||||
{:group-chats/sign-membership {:chat-id chat-id
|
(transport.filters/upsert-group-chat-topics)
|
||||||
:from my-public-key
|
(transport.filters/load-members members)))))
|
||||||
:events [event]}}))))
|
|
||||||
|
(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
|
(fx/defn make-admin
|
||||||
"Format group update with make admin message and sign membership"
|
|
||||||
[{:keys [db] :as cofx} chat-id member]
|
[{:keys [db] :as cofx} chat-id member]
|
||||||
(let [my-public-key (multiaccounts.model/current-public-key cofx)
|
{::json-rpc/call [{:method "shhext_addAdminsToGroupChat"
|
||||||
last-clock-value (get-last-clock-value cofx chat-id)
|
:params [nil chat-id [member]]
|
||||||
chat (get-in cofx [:db :chats chat-id])
|
:on-success #(re-frame/dispatch [::chat-updated %])}]})
|
||||||
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]}})))
|
|
||||||
|
|
||||||
(fx/defn add-members
|
(fx/defn add-members
|
||||||
"Add members to a group chat"
|
"Add members to a group chat"
|
||||||
[{{:keys [current-chat-id selected-participants]} :db :as cofx}]
|
[{{:keys [current-chat-id selected-participants]} :db :as cofx}]
|
||||||
(let [last-clock-value (get-last-clock-value cofx current-chat-id)
|
{::json-rpc/call [{:method "shhext_addMembersToGroupChat"
|
||||||
events [(members-added-event last-clock-value selected-participants)]]
|
:params [nil current-chat-id selected-participants]
|
||||||
|
:on-success #(re-frame/dispatch [::chat-updated %])}]})
|
||||||
{:group-chats/sign-membership {:chat-id current-chat-id
|
|
||||||
:from (multiaccounts.model/current-public-key cofx)
|
|
||||||
:events events}}))
|
|
||||||
(fx/defn remove
|
(fx/defn remove
|
||||||
"Remove & leave chat"
|
"Remove & leave chat"
|
||||||
[{:keys [db] :as cofx} chat-id]
|
[{:keys [db] :as cofx} chat-id]
|
||||||
(let [my-public-key (multiaccounts.model/current-public-key cofx)]
|
{::json-rpc/call [{:method "shhext_leaveGroupChat"
|
||||||
(fx/merge cofx
|
:params [nil chat-id]
|
||||||
(remove-member chat-id my-public-key)
|
:on-success #(re-frame/dispatch [::chat-updated %])}]})
|
||||||
(models.chat/remove-chat chat-id))))
|
|
||||||
|
|
||||||
(defn- valid-name? [name]
|
(defn- valid-name? [name]
|
||||||
(spec/valid? :profile/name name))
|
(spec/valid? :profile/name name))
|
||||||
@ -302,226 +119,10 @@
|
|||||||
(fx/defn save
|
(fx/defn save
|
||||||
"Save chat from edited profile"
|
"Save chat from edited profile"
|
||||||
[{:keys [db] :as cofx}]
|
[{:keys [db] :as cofx}]
|
||||||
(let [current-chat-id (get-in cofx [:db :current-chat-id])
|
(let [new-name (get-in cofx [:db :group-chat-profile/profile :name])
|
||||||
my-public-key (get-in db [:multiaccount :public-key])
|
current-chat-id (:current-chat-id db)]
|
||||||
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)}]
|
|
||||||
(when (valid-name? new-name)
|
(when (valid-name? new-name)
|
||||||
(fx/merge cofx
|
{::json-rpc/call [{:method "shhext_changeGroupChatName"
|
||||||
{:db (assoc db
|
:params [nil current-chat-id new-name]
|
||||||
:group-chat-profile/editing?
|
:on-success #(re-frame/dispatch [::chat-updated %])}]})))
|
||||||
false)
|
|
||||||
:group-chats/sign-membership {:chat-id current-chat-id
|
|
||||||
:from my-public-key
|
|
||||||
:events [name-changed-event]}}))))
|
|
||||||
|
|
||||||
(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))))
|
|
||||||
|
@ -6,18 +6,11 @@
|
|||||||
[status-im.ens.core :as ens]
|
[status-im.ens.core :as ens]
|
||||||
[status-im.pairing.core :as pairing]
|
[status-im.pairing.core :as pairing]
|
||||||
[status-im.transport.message.contact :as transport.contact]
|
[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.pairing :as transport.pairing]
|
||||||
[status-im.transport.message.core :as transport.message]
|
[status-im.transport.message.core :as transport.message]
|
||||||
|
|
||||||
[status-im.transport.message.protocol :as protocol]))
|
[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
|
(extend-type transport.contact/ContactRequest
|
||||||
protocol/StatusMessage
|
protocol/StatusMessage
|
||||||
(receive [this _ signature timestamp cofx]
|
(receive [this _ signature timestamp cofx]
|
||||||
|
@ -6,14 +6,8 @@
|
|||||||
[status-im.transport.db :as transport.db]
|
[status-im.transport.db :as transport.db]
|
||||||
[status-im.transport.message.pairing :as transport.pairing]
|
[status-im.transport.message.pairing :as transport.pairing]
|
||||||
[status-im.transport.message.contact :as transport.contact]
|
[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]))
|
[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
|
(extend-type transport.pairing/PairInstallation
|
||||||
protocol/StatusMessage
|
protocol/StatusMessage
|
||||||
(send [this _ cofx]
|
(send [this _ cofx]
|
||||||
|
@ -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)))))
|
|
@ -2,7 +2,6 @@
|
|||||||
status-im.transport.message.transit
|
status-im.transport.message.transit
|
||||||
(:require [status-im.transport.message.contact :as contact]
|
(:require [status-im.transport.message.contact :as contact]
|
||||||
[status-im.transport.message.protocol :as protocol]
|
[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.transport.message.pairing :as pairing]
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
[cognitect.transit :as transit]))
|
[cognitect.transit :as transit]))
|
||||||
@ -50,12 +49,6 @@
|
|||||||
;; no need for legacy conversions for rest of the content types
|
;; no need for legacy conversions for rest of the content types
|
||||||
#js [content content-type message-type clock-value timestamp])))
|
#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 []
|
(deftype SyncInstallationHandler []
|
||||||
Object
|
Object
|
||||||
(tag [this v] "p1")
|
(tag [this v] "p1")
|
||||||
@ -74,7 +67,6 @@
|
|||||||
contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.)
|
contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.)
|
||||||
contact/ContactUpdate (ContactUpdateHandler.)
|
contact/ContactUpdate (ContactUpdateHandler.)
|
||||||
protocol/Message (MessageHandler.)
|
protocol/Message (MessageHandler.)
|
||||||
group-chat/GroupMembershipUpdate (GroupMembershipUpdateHandler.)
|
|
||||||
pairing/SyncInstallation (SyncInstallationHandler.)
|
pairing/SyncInstallation (SyncInstallationHandler.)
|
||||||
pairing/PairInstallation (PairInstallationHandler.)}}))
|
pairing/PairInstallation (PairInstallationHandler.)}}))
|
||||||
|
|
||||||
@ -109,8 +101,6 @@
|
|||||||
"c5" (fn [])
|
"c5" (fn [])
|
||||||
"c6" (fn [[name profile-image address _ _]]
|
"c6" (fn [[name profile-image address _ _]]
|
||||||
(contact/ContactUpdate. name profile-image address nil nil))
|
(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]]
|
"p1" (fn [[contacts account chat]]
|
||||||
(pairing/SyncInstallation. contacts account chat))
|
(pairing/SyncInstallation. contacts account chat))
|
||||||
"p2" (fn [[installation-id device-type name _]]
|
"p2" (fn [[installation-id device-type name _]]
|
||||||
|
@ -67,6 +67,8 @@
|
|||||||
purple
|
purple
|
||||||
"#8B3131"])
|
"#8B3131"])
|
||||||
|
|
||||||
|
(def mention-incoming "#0DA4C9")
|
||||||
|
(def mention-outgoing "#9EE8FA")
|
||||||
(def text black)
|
(def text black)
|
||||||
(def text-gray gray)
|
(def text-gray gray)
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
[status-im.utils.platform :as platform])
|
[status-im.utils.platform :as platform])
|
||||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
(: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
|
(defn message-timestamp
|
||||||
[t justify-timestamp? outgoing content content-type]
|
[t justify-timestamp? outgoing content content-type]
|
||||||
[react/text {:style (style/message-timestamp-text
|
[react/text {:style (style/message-timestamp-text
|
||||||
@ -86,6 +90,14 @@
|
|||||||
[:browser.ui/message-link-pressed destination])))}
|
[:browser.ui/message-link-pressed destination])))}
|
||||||
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"
|
"status-tag"
|
||||||
(conj acc [react/text-class
|
(conj acc [react/text-class
|
||||||
{:style {:color (if outgoing colors/white colors/blue)
|
{:style {:color (if outgoing colors/white colors/blue)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
||||||
"owner": "status-im",
|
"owner": "status-im",
|
||||||
"repo": "status-go",
|
"repo": "status-go",
|
||||||
"version": "v0.36.2",
|
"version": "v0.38.0",
|
||||||
"commit-sha1": "4c0d8dedea10b02bbad476170cc0eef61a92ecbf",
|
"commit-sha1": "baa0767c263fa25fdd8d90a42d13f1f1fffce804",
|
||||||
"src-sha256": "0a13rk9p13s3p1dz3n7wbb3s343dlqsidmphxz57xw6di2s40nzx"
|
"src-sha256": "0m7baga353vyjqb8zlzd22w68kkm24baq8059a89xdv57ln35axi"
|
||||||
}
|
}
|
||||||
|
@ -861,14 +861,13 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
|
|||||||
self.errors.append('Public chat "%s" doesn\'t appear on other device when devices are paired'
|
self.errors.append('Public chat "%s" doesn\'t appear on other device when devices are paired'
|
||||||
% public_chat_before_sync_name)
|
% 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_home.element_by_text(group_chat_name).click()
|
device_2_group_chat = device_2_home.get_chat_view()
|
||||||
#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():
|
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)
|
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()
|
device_2_home.profile_button.click()
|
||||||
if not device_2_profile.profile_picture.is_element_image_equals_template('sauce_logo_red_profile.png'):
|
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')
|
self.errors.append('Profile picture was not updated after changing when devices are paired')
|
||||||
|
@ -5,19 +5,19 @@ from views.sign_in_view import SignInView
|
|||||||
|
|
||||||
|
|
||||||
def return_left_chat_system_message(username):
|
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):
|
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):
|
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):
|
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):
|
def create_users(driver_1, driver_2):
|
||||||
|
@ -13,15 +13,7 @@
|
|||||||
:admins #{"a" "b"}
|
:admins #{"a" "b"}
|
||||||
:members-joined #{"a" "c"}
|
:members-joined #{"a" "c"}
|
||||||
:name "name"
|
:name "name"
|
||||||
:membership-updates [{:chat-id "chat-id"
|
:membership-update-events :events
|
||||||
:from "a"
|
|
||||||
:signature "b"
|
|
||||||
:events [{:type "chat-created"
|
|
||||||
:name "test"
|
|
||||||
:clock-value 1}
|
|
||||||
{:type "members-added"
|
|
||||||
:clock-value 2
|
|
||||||
:members ["a" "b"]}]}]
|
|
||||||
:gaps-loaded? true
|
:gaps-loaded? true
|
||||||
:unviewed-messages-count 2
|
:unviewed-messages-count 2
|
||||||
:is-active true
|
:is-active true
|
||||||
@ -49,25 +41,13 @@
|
|||||||
:admin false
|
:admin false
|
||||||
:joined false}}
|
:joined false}}
|
||||||
:lastClockValue 10
|
:lastClockValue 10
|
||||||
:membershipUpdates #{{:type "chat-created"
|
:membershipUpdateEvents :events
|
||||||
:name "test"
|
|
||||||
:clockValue 1
|
|
||||||
:id "0xcdf4a63e0c98d0018cf532b3a48350bb80e292cc46249e3f876aaa65eb97a231"
|
|
||||||
:signature "b"
|
|
||||||
:from "a"}
|
|
||||||
{:type "members-added"
|
|
||||||
:clockValue 2
|
|
||||||
:members ["a" "b"]
|
|
||||||
:id "0x1c34d6b4d022c432b7eb6b645e095791af0c6bdb626db7d705e6db0d7cd74b56"
|
|
||||||
:signature "b"
|
|
||||||
:from "a"}}
|
|
||||||
:unviewedMessagesCount 2
|
:unviewedMessagesCount 2
|
||||||
:active true
|
:active true
|
||||||
:timestamp 2}]
|
:timestamp 2}]
|
||||||
(testing "marshaling chat"
|
(testing "marshaling chat"
|
||||||
(is (= expected-chat (-> (chats/->rpc chat)
|
(is (= expected-chat (-> (chats/->rpc chat)
|
||||||
(update :members #(into #{} %))
|
(update :members #(into #{} %))))))))
|
||||||
(update :membershipUpdates #(into #{} %))))))))
|
|
||||||
|
|
||||||
(deftest normalize-chat-test
|
(deftest normalize-chat-test
|
||||||
(let [chat {:id "chat-id"
|
(let [chat {:id "chat-id"
|
||||||
@ -87,18 +67,7 @@
|
|||||||
:admin false
|
:admin false
|
||||||
:joined false}]
|
:joined false}]
|
||||||
:lastClockValue 10
|
:lastClockValue 10
|
||||||
:membershipUpdates [{:type "chat-created"
|
:membershipUpdateEvents :events
|
||||||
:name "test"
|
|
||||||
:clockValue 1
|
|
||||||
:id "0xcdf4a63e0c98d0018cf532b3a48350bb80e292cc46249e3f876aaa65eb97a231"
|
|
||||||
:signature "b"
|
|
||||||
:from "a"}
|
|
||||||
{:type "members-added"
|
|
||||||
:clockValue 2
|
|
||||||
:members ["a" "b"]
|
|
||||||
:id "0x1c34d6b4d022c432b7eb6b645e095791af0c6bdb626db7d705e6db0d7cd74b56"
|
|
||||||
:signature "b"
|
|
||||||
:from "a"}]
|
|
||||||
:unviewedMessagesCount 2
|
:unviewedMessagesCount 2
|
||||||
:active true
|
:active true
|
||||||
:timestamp 2}
|
:timestamp 2}
|
||||||
@ -111,34 +80,10 @@
|
|||||||
:admins #{"a" "b"}
|
:admins #{"a" "b"}
|
||||||
:members-joined #{"a" "c"}
|
:members-joined #{"a" "c"}
|
||||||
:name "name"
|
:name "name"
|
||||||
:membership-updates #{{:chat-id "chat-id"
|
:membership-update-events :events
|
||||||
:from "a"
|
|
||||||
:signature "b"
|
|
||||||
:events [{:type "chat-created"
|
|
||||||
:name "test"
|
|
||||||
:clock-value 1}
|
|
||||||
{:type "members-added"
|
|
||||||
:clock-value 2
|
|
||||||
:members #{"a" "b"}}]}}
|
|
||||||
:unviewed-messages-count 2
|
:unviewed-messages-count 2
|
||||||
:is-active true
|
:is-active true
|
||||||
:chat-id "chat-id"
|
:chat-id "chat-id"
|
||||||
:timestamp 2}]
|
:timestamp 2}]
|
||||||
(testing "from-rpc"
|
(testing "from-rpc"
|
||||||
(is (= expected-chat (chats/<-rpc chat))))))
|
(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))))
|
|
||||||
|
@ -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"))))))))
|
|
@ -20,7 +20,6 @@
|
|||||||
[status-im.test.ethereum.mnemonic]
|
[status-im.test.ethereum.mnemonic]
|
||||||
[status-im.test.ethereum.stateofus]
|
[status-im.test.ethereum.stateofus]
|
||||||
[status-im.test.fleet.core]
|
[status-im.test.fleet.core]
|
||||||
[status-im.test.group-chats.core]
|
|
||||||
[status-im.test.hardwallet.core]
|
[status-im.test.hardwallet.core]
|
||||||
[status-im.test.i18n]
|
[status-im.test.i18n]
|
||||||
[status-im.test.mailserver.core]
|
[status-im.test.mailserver.core]
|
||||||
@ -93,7 +92,6 @@
|
|||||||
'status-im.test.ethereum.mnemonic
|
'status-im.test.ethereum.mnemonic
|
||||||
'status-im.test.ethereum.stateofus
|
'status-im.test.ethereum.stateofus
|
||||||
'status-im.test.fleet.core
|
'status-im.test.fleet.core
|
||||||
'status-im.test.group-chats.core
|
|
||||||
'status-im.test.hardwallet.core
|
'status-im.test.hardwallet.core
|
||||||
'status-im.test.i18n
|
'status-im.test.i18n
|
||||||
'status-im.test.mailserver.core
|
'status-im.test.mailserver.core
|
||||||
|
Loading…
x
Reference in New Issue
Block a user