Add new protocol
Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
parent
66f90cfdbc
commit
df17c50612
|
@ -22,11 +22,11 @@
|
|||
:function :subscription
|
||||
:parameters {:name sub-name
|
||||
:subscriptions (subscription-values sub-params (get bot-db bot))}
|
||||
:callback-events-creator (fn [jail-response]
|
||||
[[::calculated-subscription
|
||||
{:bot bot
|
||||
:path [sub-name]
|
||||
:result jail-response}]])})}))
|
||||
:callback-event-creator (fn [jail-response]
|
||||
[::calculated-subscription
|
||||
{:bot bot
|
||||
:path [sub-name]
|
||||
:result jail-response}])})}))
|
||||
|
||||
(defn set-in-bot-db
|
||||
"Associates value at specified path in bot-db and checks if there are any subscriptions
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
:photo-path (str "contacts://" constants/console-chat-id)
|
||||
:contacts [{:identity constants/console-chat-id
|
||||
:text-color "#FFFFFF"
|
||||
:background-color "#AB7967"}]})
|
||||
:background-color "#AB7967"}]
|
||||
:last-to-clock-value 0
|
||||
:last-from-clock-value 0})
|
||||
|
||||
(def contact
|
||||
{:whisper-identity constants/console-chat-id
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
(ns status-im.chat.core)
|
||||
|
||||
;; Seen messages
|
||||
(defn receive-seen
|
||||
[chat-id sender message-ids {:keys [db]}]
|
||||
(when-let [seen-messages-ids (-> (get-in db [:chats chat-id :messages])
|
||||
(select-keys message-ids)
|
||||
keys)]
|
||||
{:db (reduce (fn [new-db message-id]
|
||||
(assoc-in new-db [:chats chat-id :messages message-id :user-statuses sender] :seen))
|
||||
db
|
||||
seen-messages-ids)}))
|
|
@ -1,19 +1,19 @@
|
|||
(ns status-im.chat.events
|
||||
(:require [clojure.set :as set]
|
||||
[cljs.core.async :as async]
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.chat.models :as models]
|
||||
[status-im.chat.console :as console]
|
||||
[status-im.data-store.chats :as chats]
|
||||
[status-im.data-store.messages :as messages]
|
||||
[status-im.data-store.pending-messages :as pending-messages]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.utils.async :as utils.async]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.message.v1.public-chat :as public-chat]
|
||||
[status-im.transport.message.v1.group-chat :as group-chat]
|
||||
status-im.chat.events.commands
|
||||
status-im.chat.events.requests
|
||||
status-im.chat.events.send-message
|
||||
|
@ -22,88 +22,8 @@
|
|||
status-im.chat.events.console
|
||||
status-im.chat.events.webview-bridge))
|
||||
|
||||
;;;; Coeffects
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:stored-unviewed-messages
|
||||
(fn [cofx _]
|
||||
(assoc cofx :stored-unviewed-messages
|
||||
(messages/get-unviewed (-> cofx :db :current-public-key)))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-stored-message
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-message messages/get-by-id)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-stored-messages
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-messages messages/get-by-chat-id)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:stored-message-ids
|
||||
(fn [cofx _]
|
||||
(assoc cofx :stored-message-ids (messages/get-stored-message-ids))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:all-stored-chats
|
||||
(fn [cofx _]
|
||||
(assoc cofx :all-stored-chats (chats/get-all))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-stored-chat
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-chat chats/get-by-id)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:inactive-chat-ids
|
||||
(fn [cofx _]
|
||||
(assoc cofx :inactive-chat-ids (chats/get-inactive-ids))))
|
||||
|
||||
;;;; Effects
|
||||
|
||||
(def ^:private realm-queue (utils.async/task-queue 2000))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:update-message
|
||||
(fn [message]
|
||||
(async/go (async/>! realm-queue #(messages/update-message message)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:save-message
|
||||
(fn [message]
|
||||
(async/go (async/>! realm-queue #(messages/save message)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:delete-messages
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! realm-queue #(messages/delete-by-chat-id chat-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:delete-pending-messages
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! realm-queue #(pending-messages/delete-all-by-chat-id chat-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:save-chat
|
||||
(fn [chat]
|
||||
(async/go (async/>! realm-queue #(chats/save chat)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:deactivate-chat
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! realm-queue #(chats/set-inactive chat-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:delete-chat
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! realm-queue #(chats/delete chat-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:protocol-send-seen
|
||||
(fn [params]
|
||||
(protocol/send-seen! params)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:browse
|
||||
(fn [link]
|
||||
|
@ -134,7 +54,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:load-more-messages
|
||||
[(re-frame/inject-cofx :get-stored-messages)]
|
||||
[(re-frame/inject-cofx :data-store/get-messages)]
|
||||
(fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _]
|
||||
(when-not (get-in db [:chats current-chat-id :all-loaded?])
|
||||
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
|
||||
|
@ -151,16 +71,25 @@
|
|||
(fn [db [{:keys [chat-id message-id]}]]
|
||||
(update-in db [:chats chat-id :messages message-id] assoc :appearing? false)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:update-message-status
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [chat-id message-id user-id status]]
|
||||
(let [msg-path [:chats chat-id :messages message-id]
|
||||
new-db (update-in db (conj msg-path :user-statuses) assoc user-id status)]
|
||||
{:db new-db
|
||||
:data-store/update-message (-> (get-in new-db msg-path) (select-keys [:message-id :user-statuses]))})))
|
||||
|
||||
(defn init-console-chat
|
||||
[{:keys [chats] :as db}]
|
||||
(if (chats constants/console-chat-id)
|
||||
{:db db}
|
||||
{:db (-> db
|
||||
(assoc :current-chat-id constants/console-chat-id)
|
||||
(update :chats assoc constants/console-chat-id console/chat))
|
||||
:dispatch [:add-contacts [console/contact]]
|
||||
:save-chat console/chat
|
||||
:save-all-contacts [console/contact]}))
|
||||
{:db (-> db
|
||||
(assoc :current-chat-id constants/console-chat-id)
|
||||
(update :chats assoc constants/console-chat-id console/chat))
|
||||
:dispatch [:add-contacts [console/contact]]
|
||||
:data-store/save-chat console/chat
|
||||
:data-store/save-contact console/contact}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:init-console-chat
|
||||
|
@ -169,12 +98,12 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:initialize-chats
|
||||
[(re-frame/inject-cofx :all-stored-chats)
|
||||
(re-frame/inject-cofx :inactive-chat-ids)
|
||||
(re-frame/inject-cofx :get-stored-messages)
|
||||
(re-frame/inject-cofx :stored-unviewed-messages)
|
||||
(re-frame/inject-cofx :stored-message-ids)
|
||||
(re-frame/inject-cofx :get-stored-unanswered-requests)]
|
||||
[(re-frame/inject-cofx :data-store/all-chats)
|
||||
(re-frame/inject-cofx :data-store/inactive-chat-ids)
|
||||
(re-frame/inject-cofx :data-store/get-messages)
|
||||
(re-frame/inject-cofx :data-store/unviewed-messages)
|
||||
(re-frame/inject-cofx :data-store/message-ids)
|
||||
(re-frame/inject-cofx :data-store/get-unanswered-requests)]
|
||||
(fn [{:keys [db
|
||||
all-stored-chats
|
||||
inactive-chat-ids
|
||||
|
@ -189,12 +118,12 @@
|
|||
chats (reduce (fn [acc {:keys [chat-id] :as chat}]
|
||||
(let [chat-messages (index-messages (get-stored-messages chat-id))]
|
||||
(assoc acc chat-id
|
||||
(assoc chat
|
||||
:unviewed-messages (get stored-unviewed-messages chat-id)
|
||||
:requests (get chat->message-id->request chat-id)
|
||||
:messages chat-messages
|
||||
:not-loaded-message-ids (set/difference (get stored-message-ids chat-id)
|
||||
(-> chat-messages keys set))))))
|
||||
(assoc chat
|
||||
:unviewed-messages (get stored-unviewed-messages chat-id)
|
||||
:requests (get chat->message-id->request chat-id)
|
||||
:messages chat-messages
|
||||
:not-loaded-message-ids (set/difference (get stored-message-ids chat-id)
|
||||
(-> chat-messages keys set))))))
|
||||
{}
|
||||
all-stored-chats)]
|
||||
(-> db
|
||||
|
@ -203,153 +132,213 @@
|
|||
init-console-chat
|
||||
(update :dispatch-n conj [:load-default-contacts!])))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-seen!
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [{:keys [chat-id from me message-id]}]]
|
||||
(let [{:keys [web3 chats] :contacts/keys [contacts]} db
|
||||
{:keys [group-chat public? messages]} (get chats chat-id)
|
||||
statuses (assoc (get-in messages [message-id :user-statuses]) me :seen)]
|
||||
(cond-> {:db (-> db
|
||||
(update-in [:chats chat-id :unviewed-messages] disj message-id)
|
||||
(assoc-in [:chats chat-id :messages message-id :user-statuses] statuses))
|
||||
:update-message {:message-id message-id
|
||||
:user-statuses statuses}}
|
||||
;; for public chats and 1-1 bot/dapp chats, it makes no sense to signalise `:seen` msg
|
||||
(not (or public? (get-in contacts [chat-id :dapp?])))
|
||||
(assoc :protocol-send-seen {:web3 web3
|
||||
:message (cond-> {:from me
|
||||
:to from
|
||||
:message-id message-id}
|
||||
group-chat (assoc :group-id chat-id))})))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:browse-link-from-message
|
||||
(fn [_ [_ link]]
|
||||
{:browse link}))
|
||||
|
||||
(defn preload-chat-data
|
||||
"Takes coeffects map and chat-id, returns effects necessary when navigating to chat"
|
||||
[{:keys [db]} chat-id]
|
||||
(let [chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event])]
|
||||
(cond-> {:db (-> db
|
||||
(assoc :current-chat-id chat-id)
|
||||
(assoc-in [:chats chat-id :was-opened?] true)
|
||||
(models/set-chat-ui-props {:validation-messages nil})
|
||||
(update-in [:chats chat-id] dissoc :chat-loaded-event))}
|
||||
(defn- persist-seen-messages
|
||||
[chat-id unseen-messages-ids {:keys [db]}]
|
||||
{:data-store/update-messages (map (fn [message-id]
|
||||
(-> (get-in db [:chats chat-id :messages message-id])
|
||||
(select-keys [:message-id :user-statuses])))
|
||||
unseen-messages-ids)})
|
||||
|
||||
chat-loaded-event
|
||||
(assoc :dispatch chat-loaded-event))))
|
||||
(defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
|
||||
(when (and (seq message-ids)
|
||||
(not (models/bot-only-chat? db chat-id)))
|
||||
(transport/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
|
||||
|
||||
;;TODO (yenda) find a more elegant solution for system messages
|
||||
(defn- mark-messages-seen
|
||||
[chat-id {:keys [db] :as cofx}]
|
||||
(let [me (:current-public-key db)
|
||||
messages-path [:chats chat-id :messages]
|
||||
unseen-messages-ids (into #{}
|
||||
(comp (filter (fn [[_ {:keys [from user-statuses outgoing]}]]
|
||||
(and (not outgoing)
|
||||
(not= constants/system from)
|
||||
(not= (get user-statuses me) :seen))))
|
||||
(map first))
|
||||
(get-in db messages-path))
|
||||
unseen-system-messages-ids (into #{}
|
||||
(comp (filter (fn [[_ {:keys [from user-statuses]}]]
|
||||
(and (= constants/system from)
|
||||
(not= (get user-statuses me) :seen))))
|
||||
(map first))
|
||||
(get-in db messages-path))]
|
||||
(when (or (seq unseen-messages-ids)
|
||||
(seq unseen-system-messages-ids))
|
||||
(handlers/merge-fx cofx
|
||||
{:db (-> (reduce (fn [new-db message-id]
|
||||
(assoc-in new-db (into messages-path [message-id :user-statuses me]) :seen))
|
||||
db
|
||||
(into unseen-messages-ids unseen-system-messages-ids))
|
||||
(update-in [:chats chat-id :unviewed-messages] set/difference unseen-messages-ids unseen-system-messages-ids))}
|
||||
(persist-seen-messages chat-id (into unseen-messages-ids unseen-system-messages-ids))
|
||||
(send-messages-seen chat-id unseen-messages-ids)))))
|
||||
|
||||
(defn- fire-off-chat-loaded-event
|
||||
[chat-id {:keys [db]}]
|
||||
(when-let [event (get-in db [:chats chat-id :chat-loaded-event])]
|
||||
{:db (update-in db [:chats chat-id] dissoc :chat-loaded-event)
|
||||
:dispatch event}))
|
||||
|
||||
(defn- preload-chat-data
|
||||
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
|
||||
[chat-id {:keys [db] :as cofx}]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (-> (assoc db :current-chat-id chat-id)
|
||||
(models/set-chat-ui-props {:validation-messages nil}))}
|
||||
(fire-off-chat-loaded-event chat-id)
|
||||
(mark-messages-seen chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-chat-loaded-event
|
||||
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]
|
||||
[(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
|
||||
(fn [{:keys [db] :as cofx} [chat-id event]]
|
||||
(if (get (:chats db) chat-id)
|
||||
{:db (assoc-in db [:chats chat-id :chat-loaded-event] event)}
|
||||
(-> (models/add-chat cofx chat-id) ; chat not created yet, we have to create it
|
||||
(-> (models/add-chat chat-id cofx) ; chat not created yet, we have to create it
|
||||
(assoc-in [:db :chats chat-id :chat-loaded-event] event)))))
|
||||
|
||||
;; TODO(janherich): remove this unnecessary event in the future (only model function `add-chat` will stay)
|
||||
(handlers/register-handler-fx
|
||||
:add-chat
|
||||
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]
|
||||
[(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
|
||||
(fn [cofx [chat-id chat-props]]
|
||||
(models/add-chat cofx chat-id chat-props)))
|
||||
(models/add-chat chat-id chat-props cofx)))
|
||||
|
||||
(defn- ensure-chat-exists
|
||||
"Takes chat-id and coeffects map and returns fx to create chat if it doesn't exist"
|
||||
[chat-id cofx]
|
||||
[chat-id {:keys [db] :as cofx}]
|
||||
(when-not (get-in cofx [:db :chats chat-id])
|
||||
(models/add-chat cofx chat-id)))
|
||||
(models/add-chat chat-id cofx)))
|
||||
|
||||
(defn- navigate-to-chat
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
[chat-id navigation-replace? cofx]
|
||||
(let [nav-fn (if navigation-replace?
|
||||
#(navigation/replace-view % :chat)
|
||||
#(navigation/navigate-to % :chat))]
|
||||
(-> (preload-chat-data cofx chat-id)
|
||||
(update :db nav-fn))))
|
||||
[chat-id {:keys [navigation-replace?]} {:keys [db] :as cofx}]
|
||||
(if navigation-replace?
|
||||
(handlers/merge-fx cofx
|
||||
(navigation/replace-view :chat)
|
||||
(preload-chat-data chat-id))
|
||||
(handlers/merge-fx cofx
|
||||
;; TODO janherich - refactor `navigate-to` so it can be used with `merge-fx` macro
|
||||
{:db (navigation/navigate-to db :chat)}
|
||||
(preload-chat-data chat-id))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:navigate-to-chat
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id {:keys [navigation-replace?]}]]
|
||||
(navigate-to-chat chat-id navigation-replace? cofx)))
|
||||
(fn [cofx [chat-id opts]]
|
||||
(navigate-to-chat chat-id opts cofx)))
|
||||
|
||||
(defn start-chat
|
||||
"Start a chat, making sure it exists"
|
||||
[chat-id navigation-replace? {:keys [db] :as cofx}]
|
||||
[chat-id opts {:keys [db] :as cofx}]
|
||||
(when (not= (:current-public-key db) chat-id) ; don't allow to open chat with yourself
|
||||
(handlers/merge-fx
|
||||
cofx
|
||||
(ensure-chat-exists chat-id)
|
||||
(navigate-to-chat chat-id navigation-replace?))))
|
||||
(handlers/merge-fx cofx
|
||||
(ensure-chat-exists chat-id)
|
||||
(navigate-to-chat chat-id opts))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:start-chat
|
||||
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]
|
||||
(fn [cofx [contact-id {:keys [navigation-replace?]}]]
|
||||
(start-chat contact-id navigation-replace? cofx)))
|
||||
[(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
|
||||
(fn [cofx [contact-id opts]]
|
||||
(start-chat contact-id opts cofx)))
|
||||
|
||||
;; TODO(janherich): remove this unnecessary event in the future (only model function `update-chat` will stay)
|
||||
(handlers/register-handler-fx
|
||||
:update-chat!
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat]]
|
||||
(models/update-chat cofx chat)))
|
||||
|
||||
(models/update-chat chat cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [chat-id]]
|
||||
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])]
|
||||
(cond-> {:db (-> db
|
||||
(update :chats dissoc chat-id)
|
||||
(update :deleted-chats (fnil conj #{}) chat-id))
|
||||
:delete-pending-messages chat-id}
|
||||
(or group-chat debug?)
|
||||
(assoc :delete-messages chat-id)
|
||||
debug?
|
||||
(assoc :delete-chat chat-id)
|
||||
(not debug?)
|
||||
(assoc :deactivate-chat chat-id)))))
|
||||
(fn [cofx [chat-id]]
|
||||
(models/remove-chat chat-id cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:delete-chat
|
||||
:remove-chat-and-navigate-home
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id]]
|
||||
(-> (models/remove-chat cofx chat-id)
|
||||
(update :db navigation/replace-view :home))))
|
||||
(handlers/merge-fx cofx
|
||||
(models/remove-chat chat-id)
|
||||
(navigation/replace-view :home))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:delete-chat?
|
||||
:remove-chat-and-navigate-home?
|
||||
[re-frame/trim-v]
|
||||
(fn [_ [chat-id group?]]
|
||||
{:show-confirmation {:title (i18n/label :t/delete-confirmation)
|
||||
:content (i18n/label (if group? :t/delete-group-chat-confirmation :t/delete-chat-confirmation))
|
||||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(re-frame/dispatch [:delete-chat chat-id])}}))
|
||||
|
||||
(defn remove-chats [db chat-id]
|
||||
(let [chat (get-in db [:chats chat-id])]
|
||||
{:db (-> db
|
||||
(update :chats dissoc chat-id)
|
||||
(update :deleted-chats (fnil conj #{}) chat-id))
|
||||
:delete-chat chat
|
||||
:delete-chat-messages chat}))
|
||||
:on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat
|
||||
:create-new-public-chat
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [chat-id]]
|
||||
(remove-chats db chat-id)))
|
||||
(fn [{:keys [db now] :as cofx} [topic]]
|
||||
(if (get-in db [:chats topic])
|
||||
(handlers/merge-fx cofx
|
||||
(navigation/navigate-to-clean :home)
|
||||
(navigate-to-chat topic {}))
|
||||
(handlers/merge-fx cofx
|
||||
(models/add-public-chat topic)
|
||||
(navigation/navigate-to-clean :home)
|
||||
(navigate-to-chat topic {})
|
||||
(public-chat/join-public-chat topic)))))
|
||||
|
||||
(defn- group-name-from-contacts [selected-contacts all-contacts username]
|
||||
(->> selected-contacts
|
||||
(map (comp :name (partial get all-contacts)))
|
||||
(cons username)
|
||||
(string/join ", ")))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat-and-navigate-home
|
||||
:create-new-group-chat-and-open
|
||||
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
|
||||
(fn [{:keys [db now random-id] :as cofx} [group-name]]
|
||||
(let [selected-contacts (:group/selected-contacts db)
|
||||
chat-name (if-not (string/blank? group-name)
|
||||
group-name
|
||||
(group-name-from-contacts selected-contacts
|
||||
(:contacts/contacts db)
|
||||
(:username db)))]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc db :group/selected-contacts #{})}
|
||||
(models/add-group-chat random-id chat-name (:current-public-key db) selected-contacts)
|
||||
(navigation/navigate-to-clean :home)
|
||||
(navigate-to-chat random-id {})
|
||||
(transport/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
|
||||
|
||||
(defn- broadcast-leave [{:keys [public? chat-id]} cofx]
|
||||
(when-not public?
|
||||
(transport/send (group-chat/GroupLeave.) chat-id cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:leave-group-chat
|
||||
;; stop listening to group here
|
||||
(fn [{{:keys [current-chat-id chats] :as db} :db :as cofx} _]
|
||||
(handlers/merge-fx cofx
|
||||
(models/remove-chat current-chat-id)
|
||||
(navigation/replace-view :home)
|
||||
(broadcast-leave (get chats current-chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:leave-group-chat?
|
||||
(fn [_ _]
|
||||
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
|
||||
:content (i18n/label :t/leave-group-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/leave)
|
||||
:on-accept #(re-frame/dispatch [:leave-group-chat])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-profile
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [chat-id]]
|
||||
(merge (remove-chats db chat-id)
|
||||
{:dispatch [:navigation-replace :home]})))
|
||||
(fn [{:keys [db] :as cofx} [identity]]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc db :contacts/identity identity)}
|
||||
(navigation/navigate-forget :profile))))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.chat.events.commands
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
@ -9,45 +10,38 @@
|
|||
|
||||
(defn- generate-context
|
||||
"Generates context for jail call"
|
||||
[current-account-id chat-id to group-id]
|
||||
[current-account-id chat-id group-chat? to]
|
||||
(merge {:platform platform/os
|
||||
:from current-account-id
|
||||
:to to
|
||||
:chat {:chat-id chat-id
|
||||
:group-chat (not (nil? group-id))}}
|
||||
:group-chat (boolean group-chat?)}}
|
||||
i18n/delimeters))
|
||||
|
||||
(defn request-command-message-data
|
||||
"Requests command message data from jail"
|
||||
[db
|
||||
{{command-name :command
|
||||
content-command-name :content-command
|
||||
:keys [content-command-scope-bitmask bot scope-bitmask params type]} :content
|
||||
:keys [chat-id group-id jail-id] :as message}
|
||||
[{:accounts/keys [current-account-id] :contacts/keys [contacts] :as db}
|
||||
{{:keys [command command-scope-bitmask bot params type]} :content
|
||||
:keys [chat-id group-id] :as message}
|
||||
{:keys [data-type] :as opts}]
|
||||
(let [{:accounts/keys [current-account-id]
|
||||
:contacts/keys [contacts]} db
|
||||
jail-id (or bot jail-id chat-id)
|
||||
jail-command-name (or content-command-name command-name)]
|
||||
(if-not (get contacts jail-id) ;; bot is not even in contacts, do nothing
|
||||
{:db db}
|
||||
(if (get-in contacts [jail-id :jail-loaded?])
|
||||
(let [path [(if (= :response (keyword type)) :responses :commands)
|
||||
[jail-command-name
|
||||
(or content-command-scope-bitmask scope-bitmask)]
|
||||
data-type]
|
||||
to (get-in contacts [chat-id :address])
|
||||
jail-params {:parameters params
|
||||
:context (generate-context current-account-id chat-id to group-id)}]
|
||||
{:db db
|
||||
:call-jail {:jail-id jail-id
|
||||
:path path
|
||||
:params jail-params
|
||||
:callback-events-creator (fn [jail-response]
|
||||
[[::jail-command-data-response
|
||||
jail-response message opts]])}})
|
||||
{:db (update-in db [:contacts/contacts jail-id :jail-loaded-events]
|
||||
conj [:request-command-message-data message opts])}))))
|
||||
(if-not (get contacts bot) ;; bot is not even in contacts, do nothing
|
||||
{:db db}
|
||||
(if (get-in contacts [bot :jail-loaded?])
|
||||
(let [path [(if (= :response (keyword type)) :responses :commands)
|
||||
[command command-scope-bitmask]
|
||||
data-type]
|
||||
to (get-in contacts [chat-id :address])
|
||||
jail-params {:parameters params
|
||||
:context (generate-context current-account-id chat-id (models.message/group-message? message) to)}]
|
||||
{:db db
|
||||
:call-jail {:jail-id bot
|
||||
:path path
|
||||
:params jail-params
|
||||
:callback-event-creator (fn [jail-response]
|
||||
[::jail-command-data-response
|
||||
jail-response message opts])}})
|
||||
{:db (update-in db [:contacts/contacts bot :jail-loaded-events]
|
||||
conj [:request-command-message-data message opts])})))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
|
@ -60,7 +54,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:request-command-message-data
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)]
|
||||
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
|
||||
(fn [{:keys [db]} [message opts]]
|
||||
(request-command-message-data db message opts)))
|
||||
|
||||
|
|
|
@ -6,30 +6,29 @@
|
|||
[taoensso.timbre :as log]
|
||||
[status-im.i18n :as i18n]
|
||||
[goog.string :as gstring]
|
||||
goog.string.format))
|
||||
goog.string.format
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
;;;; Helper fns
|
||||
|
||||
(defn console-respond-command-messages
|
||||
[command random-id-seq]
|
||||
(let [{:keys [command handler-data]} command]
|
||||
(when command
|
||||
(let [{:keys [name]} command]
|
||||
(case name
|
||||
"js" (let [{:keys [err data messages]} handler-data
|
||||
content (or err data)
|
||||
message-events (mapv (fn [{:keys [message type]} id]
|
||||
(console-chat/console-message
|
||||
{:message-id id
|
||||
:content (str type ": " message)
|
||||
:content-type constants/text-content-type}))
|
||||
messages random-id-seq)]
|
||||
(conj message-events
|
||||
(console-chat/console-message
|
||||
{:message-id (first random-id-seq)
|
||||
:content (str content)
|
||||
:content-type constants/text-content-type})))
|
||||
(log/debug "ignoring command: " name))))))
|
||||
[{:keys [name] :as command} handler-data random-id-seq]
|
||||
(when command
|
||||
(case name
|
||||
"js" (let [{:keys [err data messages]} handler-data
|
||||
content (or err data)
|
||||
message-events (mapv (fn [{:keys [message type]} id]
|
||||
(console-chat/console-message
|
||||
{:message-id id
|
||||
:content (str type ": " message)
|
||||
:content-type constants/text-content-type}))
|
||||
messages random-id-seq)]
|
||||
(conj message-events
|
||||
(console-chat/console-message
|
||||
{:message-id (first random-id-seq)
|
||||
:content (str content)
|
||||
:content-type constants/text-content-type})))
|
||||
(log/debug "ignoring command: " name))))
|
||||
|
||||
(defn faucet-base-url->url [url]
|
||||
(str url "/donate/0x%s"))
|
||||
|
@ -42,7 +41,7 @@
|
|||
|
||||
(def console-commands->fx
|
||||
{"faucet"
|
||||
(fn [{:keys [db random-id]} {:keys [params]}]
|
||||
(fn [{:keys [db random-id] :as cofx} {:keys [params]}]
|
||||
(let [{:accounts/keys [accounts current-account-id]} db
|
||||
current-address (get-in accounts [current-account-id :address])
|
||||
faucet-url (faucet-base-url->url (:url params))]
|
||||
|
@ -58,19 +57,19 @@
|
|||
(i18n/label :t/faucet-error)))}}))
|
||||
|
||||
"debug"
|
||||
(fn [{:keys [db random-id now]} {:keys [params]}]
|
||||
(fn [{:keys [db random-id now] :as cofx} {:keys [params]}]
|
||||
(let [debug? (= "On" (:mode params))]
|
||||
(-> {:db db}
|
||||
(accounts-events/account-update {:debug? debug?
|
||||
:last-updated now})
|
||||
(assoc :dispatch-n (if debug?
|
||||
[[:initialize-debugging {:force-start? true}]
|
||||
[:chat-received-message/add
|
||||
(console-chat/console-message
|
||||
{:message-id random-id
|
||||
:content (i18n/label :t/debug-enabled)
|
||||
:content-type constants/text-content-type})]]
|
||||
[[:stop-debugging]])))))})
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch-n (if debug?
|
||||
[[:initialize-debugging {:force-start? true}]
|
||||
[:chat-received-message/add
|
||||
(console-chat/console-message
|
||||
{:message-id random-id
|
||||
:content (i18n/label :t/debug-enabled)
|
||||
:content-type constants/text-content-type})]]
|
||||
[[:stop-debugging]])}
|
||||
(accounts-events/account-update {:debug? debug?
|
||||
:last-updated now}))))})
|
||||
|
||||
(def commands-names (set (keys console-commands->fx)))
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
chat-text (if append?
|
||||
(str current-input new-input)
|
||||
new-input)]
|
||||
(cond-> db
|
||||
(cond-> (model/set-chat-ui-props db {:validation-messages nil})
|
||||
true
|
||||
(assoc-in [:chats current-chat-id :input-text] (input-model/text->emoji chat-text))
|
||||
|
||||
|
@ -146,12 +146,12 @@
|
|||
{:call-jail {:jail-id owner-id
|
||||
:path path
|
||||
:params params
|
||||
:callback-events-creator (fn [jail-response]
|
||||
[[:chat-received-message/bot-response
|
||||
{:chat-id current-chat-id
|
||||
:command command
|
||||
:parameter-index parameter-index}
|
||||
jail-response]])}}))))
|
||||
:callback-event-creator (fn [jail-response]
|
||||
[:chat-received-message/bot-response
|
||||
{:chat-id current-chat-id
|
||||
:command command
|
||||
:parameter-index parameter-index}
|
||||
jail-response])}}))))
|
||||
|
||||
(defn chat-input-focus
|
||||
"Returns fx for focusing on active chat input reference"
|
||||
|
@ -179,15 +179,13 @@
|
|||
|
||||
(defn select-chat-input-command
|
||||
"Selects command + (optional) arguments as input for active chat"
|
||||
[{:keys [current-chat-id chat-ui-props] :as db}
|
||||
{:keys [prefill prefill-bot-db sequential-params name owner-id] :as command} metadata prevent-auto-focus?]
|
||||
(let [db' (-> db
|
||||
[{:keys [prefill prefill-bot-db sequential-params name owner-id] :as command} metadata prevent-auto-focus? {:keys [db]}]
|
||||
(let [{:keys [current-chat-id chat-ui-props]} db
|
||||
db' (-> db
|
||||
(bots-events/clear-bot-db owner-id)
|
||||
clear-seq-arguments
|
||||
(model/set-chat-ui-props {:show-suggestions? false
|
||||
:result-box nil
|
||||
:validation-messages nil
|
||||
:prev-command name})
|
||||
:result-box nil})
|
||||
(set-chat-input-metadata metadata)
|
||||
(set-chat-input-text (str (commands-model/command-name command)
|
||||
constants/spacing-char
|
||||
|
@ -237,16 +235,16 @@
|
|||
|
||||
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
|
||||
(defn- command->message
|
||||
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
|
||||
(cond-> {:chat-id current-chat-id
|
||||
:jail-id (:owner-id command)
|
||||
:content {:command (:name command)
|
||||
:type (:type command)
|
||||
:scope-bitmask (:scope-bitmask command)
|
||||
:params (assoc (input-model/args->params command-params)
|
||||
:bot-db (get bot-db (:owner-id command)))}}
|
||||
(get-in chats [current-chat-id :group-chat])
|
||||
(assoc :group-id current-chat-id)))
|
||||
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
|
||||
(message-model/add-message-type
|
||||
{:chat-id current-chat-id
|
||||
:content {:bot (:owner-id command)
|
||||
:command (:name command)
|
||||
:type (:type command)
|
||||
:command-scope-bitmask (:scope-bitmask command)
|
||||
:params (assoc (input-model/args->params command-params)
|
||||
:bot-db (get bot-db (:owner-id command)))}}
|
||||
(get chats current-chat-id)))
|
||||
|
||||
(defn proceed-command
|
||||
"Proceed with command processing by creating command message + setting up and executing chain of events:
|
||||
|
@ -260,8 +258,7 @@
|
|||
:to-message (:to-message-id metadata)
|
||||
:created-at current-time
|
||||
:id message-id
|
||||
:chat-id current-chat-id
|
||||
:jail-id (:jail-id message)}
|
||||
:chat-id current-chat-id}
|
||||
event-chain {:data-type :validator
|
||||
:proceed-event-creator (fn [validation-response]
|
||||
[::proceed-validation
|
||||
|
@ -282,35 +279,21 @@
|
|||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-db
|
||||
:update-input-data
|
||||
(fn [db]
|
||||
(input-model/modified-db-after-change db)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-chat-input-text
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [text]]
|
||||
(-> (set-chat-input-text db text)
|
||||
(call-on-message-input-change))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:add-to-chat-input-text
|
||||
[re-frame/trim-v]
|
||||
(fn [db [text-to-add]]
|
||||
(set-chat-input-text db text-to-add :append? true)))
|
||||
(let [new-db (set-chat-input-text db text)
|
||||
fx (call-on-message-input-change new-db)]
|
||||
(if-let [{:keys [command]} (input-model/selected-chat-command new-db)]
|
||||
(merge fx (load-chat-parameter-box new-db command))
|
||||
fx))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:select-chat-input-command
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [command metadata prevent-auto-focus?]]
|
||||
(select-chat-input-command db command metadata prevent-auto-focus?)))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:set-chat-input-metadata
|
||||
[re-frame/trim-v]
|
||||
(fn [db [data]]
|
||||
(set-chat-input-metadata db data)))
|
||||
(fn [cofx [command metadata prevent-auto-focus?]]
|
||||
(select-chat-input-command command metadata prevent-auto-focus? cofx)))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:set-command-argument
|
||||
|
@ -331,39 +314,18 @@
|
|||
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
|
||||
{::blur-rn-component cmp-ref})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-chat-parameter-box
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [command]]
|
||||
(load-chat-parameter-box db command)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::proceed-validation
|
||||
[re-frame/trim-v]
|
||||
(fn [_ [{:keys [markup validationHandler parameters]} proceed-events]]
|
||||
(fn [_ [{:keys [markup parameters]} proceed-events]]
|
||||
(let [error-events-creator (fn [validator-result]
|
||||
[[:set-chat-ui-props {:validation-messages validator-result
|
||||
:sending-in-progress? false}]])
|
||||
events (cond
|
||||
markup
|
||||
events (if markup
|
||||
(error-events-creator markup)
|
||||
|
||||
validationHandler
|
||||
[[::execute-validation-handler
|
||||
validationHandler parameters error-events-creator proceed-events]]
|
||||
|
||||
:default
|
||||
proceed-events)]
|
||||
{:dispatch-n events})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::execute-validation-handler
|
||||
[re-frame/trim-v]
|
||||
(fn [_ [validation-handler-name params error-events-creator proceed-events]]
|
||||
(let [error-events (when-let [validator (input-model/validation-handler validation-handler-name)]
|
||||
(validator params error-events-creator))]
|
||||
{:dispatch-n (or error-events proceed-events)})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::send-command
|
||||
message-model/send-interceptors
|
||||
|
@ -432,12 +394,6 @@
|
|||
(command-not-complete-fx db input-text))
|
||||
(plain-text-message-fx db cofx input-text current-chat-id current-public-key))))))
|
||||
|
||||
;; TODO: remove this handler and leave only helper fn once all invocations are refactored
|
||||
(handlers/register-handler-db
|
||||
:clear-seq-arguments
|
||||
(fn [db]
|
||||
(clear-seq-arguments db)))
|
||||
|
||||
(handlers/register-handler-db
|
||||
::update-seq-arguments
|
||||
[re-frame/trim-v]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(ns status-im.chat.events.receive-message
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.data-store.messages :as messages-store]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.chat.events.commands :as commands-events]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.constants :as constants]
|
||||
|
@ -14,7 +13,7 @@
|
|||
::received-message
|
||||
message-model/receive-interceptors
|
||||
(fn [cofx [message]]
|
||||
(message-model/receive cofx message)))
|
||||
(message-model/receive message cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat-received-message/add
|
||||
|
@ -39,7 +38,7 @@
|
|||
{:short-preview short-preview
|
||||
:preview preview})])}])})
|
||||
;; regular non command message, we can add it right away
|
||||
(message-model/receive cofx message)))))
|
||||
(message-model/receive message cofx)))))
|
||||
|
||||
;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored
|
||||
(handlers/register-handler-fx
|
||||
|
|
|
@ -1,43 +1,26 @@
|
|||
(ns status-im.chat.events.requests
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.data-store.requests :as requests-store]))
|
||||
|
||||
;; Coeffects
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-stored-unanswered-requests
|
||||
(fn [cofx _]
|
||||
(assoc cofx :stored-unanswered-requests (requests-store/get-all-unanswered))))
|
||||
|
||||
;; Effects
|
||||
(re-frame/reg-fx
|
||||
::mark-as-answered
|
||||
(fn [{:keys [chat-id message-id]}]
|
||||
(requests-store/mark-as-answered chat-id message-id)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::save-request
|
||||
(fn [request]
|
||||
(requests-store/save request)))
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
;; Functions
|
||||
|
||||
(defn request-answered
|
||||
"Takes fx, chat-id and message, updates fx with necessary data for markin request as answered"
|
||||
[fx chat-id message-id]
|
||||
(-> fx
|
||||
(update-in [:db :chats chat-id :requests] dissoc message-id)
|
||||
(assoc ::mark-as-answered {:chat-id chat-id
|
||||
:message-id message-id})))
|
||||
"Takes chat-id, message-id and cofx, returns fx necessary data for marking request as answered"
|
||||
[chat-id message-id {:keys [db]}]
|
||||
(when message-id
|
||||
{:db (update-in db [:chats chat-id :requests] dissoc message-id)
|
||||
:data-store/mark-request-as-answered {:chat-id chat-id
|
||||
:message-id message-id}}))
|
||||
|
||||
(defn add-request
|
||||
"Takes fx, chat-id and message, updates fx with necessary data for adding new request"
|
||||
[fx chat-id {:keys [message-id content]}]
|
||||
(let [request {:chat-id chat-id
|
||||
:message-id message-id
|
||||
:response (:command content)
|
||||
:status "open"}]
|
||||
(-> fx
|
||||
(assoc-in [:db :chats chat-id :requests message-id] request)
|
||||
(assoc ::save-request request))))
|
||||
"Takes chat-id, message-id + cofx and returns fx with necessary data for adding new request"
|
||||
[chat-id message-id {:keys [db]}]
|
||||
(let [{:keys [content-type content]} (get-in db [:chats chat-id :messages message-id])]
|
||||
(when (= content-type constants/content-type-command-request)
|
||||
(let [request {:chat-id chat-id
|
||||
:message-id message-id
|
||||
:response (:request-command content)
|
||||
:status "open"}]
|
||||
{:db (assoc-in db [:chats chat-id :requests message-id] request)
|
||||
:data-store/save-request request}))))
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.types :as types]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
@ -15,21 +14,6 @@
|
|||
(log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json)
|
||||
(status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:send-group-message
|
||||
(fn [value]
|
||||
(protocol/send-group-message! value)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:send-public-group-message
|
||||
(fn [value]
|
||||
(protocol/send-public-group-message! value)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:send-message
|
||||
(fn [value]
|
||||
(protocol/send-message! value)))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
(ns status-im.chat.handlers
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as models]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.ui.components.styles :as components.styles]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.random :as random]
|
||||
status-im.chat.events
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
(handlers/register-handler
|
||||
:leave-group-chat
|
||||
;; todo order of operations tbd
|
||||
(re-frame/after (fn [_ _] (re-frame/dispatch [:navigation-replace :home])))
|
||||
(handlers/side-effect!
|
||||
(fn [{:keys [web3 current-chat-id chats current-public-key]} _]
|
||||
(let [{:keys [public-key private-key public?]} (chats current-chat-id)]
|
||||
(protocol/stop-watching-group!
|
||||
{:web3 web3
|
||||
:group-id current-chat-id})
|
||||
(when-not public?
|
||||
(protocol/leave-group-chat!
|
||||
{:web3 web3
|
||||
:group-id current-chat-id
|
||||
:keypair {:public public-key
|
||||
:private private-key}
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)}})))
|
||||
(re-frame/dispatch [:remove-chat current-chat-id]))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:leave-group-chat?
|
||||
(fn []
|
||||
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
|
||||
:content (i18n/label :t/leave-group-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/leave)
|
||||
:on-accept #(re-frame/dispatch [:leave-group-chat])}}))
|
||||
|
||||
(handlers/register-handler :update-group-message
|
||||
(handlers/side-effect!
|
||||
(fn [{:keys [current-public-key web3 chats]}
|
||||
[_ {:keys [from]
|
||||
{:keys [group-id keypair timestamp]} :payload}]]
|
||||
(let [{:keys [private public]} keypair
|
||||
{:keys [group-admin is-active] :as chat} (get chats group-id)]
|
||||
(when (and (= from group-admin
|
||||
(or (nil? chat)
|
||||
(models/new-update? chat timestamp))))
|
||||
(re-frame/dispatch [:update-chat! {:chat-id group-id
|
||||
:public-key public
|
||||
:private-key private
|
||||
:updated-at timestamp}])
|
||||
(when is-active
|
||||
(protocol/start-watching-group!
|
||||
{:web3 web3
|
||||
:group-id group-id
|
||||
:identity current-public-key
|
||||
:keypair keypair
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])})))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::start-watching-group
|
||||
(fn [{:keys [group-id web3 current-public-key keypair]}]
|
||||
(protocol/start-watching-group!
|
||||
{:web3 web3
|
||||
:group-id group-id
|
||||
:identity current-public-key
|
||||
:keypair keypair
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:create-new-public-chat
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [{:keys [db now]} [_ topic]]
|
||||
(let [exists? (boolean (get-in db [:chats topic]))
|
||||
chat {:chat-id topic
|
||||
:name topic
|
||||
:color components.styles/default-chat-color
|
||||
:group-chat true
|
||||
:public? true
|
||||
:is-active true
|
||||
:timestamp now}]
|
||||
(merge
|
||||
(when-not exists?
|
||||
{:db (assoc-in db [:chats (:chat-id chat)] chat)
|
||||
:save-chat chat
|
||||
::start-watching-group (merge {:group-id topic}
|
||||
(select-keys db [:web3 :current-public-key]))})
|
||||
{:dispatch-n [[:navigate-to-clean :home]
|
||||
[:navigate-to-chat topic]]}))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::start-listen-group
|
||||
(fn [{:keys [new-chat web3 current-public-key]}]
|
||||
(let [{:keys [chat-id public-key private-key contacts name]} new-chat
|
||||
identities (mapv :identity contacts)]
|
||||
(protocol/invite-to-group!
|
||||
{:web3 web3
|
||||
:group {:id chat-id
|
||||
:name name
|
||||
:contacts (conj identities current-public-key)
|
||||
:admin current-public-key
|
||||
:keypair {:public public-key
|
||||
:private private-key}}
|
||||
:identities identities
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)}})
|
||||
(protocol/start-watching-group!
|
||||
{:web3 web3
|
||||
:group-id chat-id
|
||||
:identity current-public-key
|
||||
:keypair {:public public-key
|
||||
:private private-key}
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])}))))
|
||||
|
||||
(defn group-name-from-contacts [contacts selected-contacts username]
|
||||
(->> (select-keys contacts selected-contacts)
|
||||
vals
|
||||
(map :name)
|
||||
(cons username)
|
||||
(string/join ", ")))
|
||||
|
||||
(defn prepare-group-chat
|
||||
[{:keys [current-public-key username]
|
||||
:group/keys [selected-contacts]
|
||||
:contacts/keys [contacts]} group-name timestamp]
|
||||
(let [selected-contacts' (mapv #(hash-map :identity %) selected-contacts)
|
||||
chat-name (if-not (string/blank? group-name)
|
||||
group-name
|
||||
(group-name-from-contacts contacts selected-contacts username))
|
||||
{:keys [public private]} (protocol/new-keypair!)]
|
||||
{:chat-id (random/id)
|
||||
:public-key public
|
||||
:private-key private
|
||||
:name chat-name
|
||||
:color components.styles/default-chat-color
|
||||
:group-chat true
|
||||
:group-admin current-public-key
|
||||
:is-active true
|
||||
:timestamp timestamp
|
||||
:contacts selected-contacts'}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:create-new-group-chat-and-open
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [{:keys [db now]} [_ group-name]]
|
||||
(let [new-chat (prepare-group-chat (select-keys db [:group/selected-contacts :current-public-key :username
|
||||
:contacts/contacts])
|
||||
group-name
|
||||
now)]
|
||||
{:db (-> db
|
||||
(assoc-in [:chats (:chat-id new-chat)] new-chat)
|
||||
(assoc :group/selected-contacts #{}))
|
||||
:save-chat new-chat
|
||||
::start-listen-group (merge {:new-chat new-chat}
|
||||
(select-keys db [:web3 :current-public-key]))
|
||||
:dispatch-n [[:navigate-to-clean :home]
|
||||
[:navigate-to-chat (:chat-id new-chat)]]})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:group-chat-invite-received
|
||||
(fn [{{:keys [current-public-key] :as db} :db}
|
||||
[_ {:keys [from]
|
||||
{:keys [group-id group-name contacts keypair timestamp]} :payload}]]
|
||||
(let [{:keys [private public]} keypair]
|
||||
(let [contacts' (keep (fn [ident]
|
||||
(when (not= ident current-public-key)
|
||||
{:identity ident})) contacts)
|
||||
chat (get-in db [:chats group-id])
|
||||
new-chat {:chat-id group-id
|
||||
:name group-name
|
||||
:group-chat true
|
||||
:group-admin from
|
||||
:public-key public
|
||||
:private-key private
|
||||
:contacts contacts'
|
||||
:added-to-at timestamp
|
||||
:timestamp timestamp
|
||||
:is-active true}]
|
||||
(when (or (nil? chat)
|
||||
(models/new-update? chat timestamp))
|
||||
{::start-watching-group (merge {:group-id group-id
|
||||
:keypair keypair}
|
||||
(select-keys db [:web3 :current-public-key]))
|
||||
:dispatch (if chat
|
||||
[:update-chat! new-chat]
|
||||
[:add-chat group-id new-chat])})))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-profile
|
||||
(fn [{db :db} [_ identity]]
|
||||
{:db (assoc db :contacts/identity identity)
|
||||
:dispatch [:navigate-forget :profile]}))
|
|
@ -1,6 +1,8 @@
|
|||
(ns status-im.chat.models
|
||||
(:require [status-im.ui.components.styles :as styles]
|
||||
[status-im.utils.gfycat.core :as gfycat]))
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.core :as transport]))
|
||||
|
||||
(defn set-chat-ui-props
|
||||
"Updates ui-props in active chat by merging provided kvs into them"
|
||||
|
@ -13,67 +15,105 @@
|
|||
(update-in db [:chat-ui-props current-chat-id ui-element] not))
|
||||
|
||||
(defn- create-new-chat
|
||||
[{:keys [db now]} chat-id]
|
||||
[chat-id {:keys [db now]}]
|
||||
(let [name (get-in db [:contacts/contacts chat-id :name])]
|
||||
{:chat-id chat-id
|
||||
:name (or name (gfycat/generate-gfy chat-id))
|
||||
:color styles/default-chat-color
|
||||
:group-chat false
|
||||
:is-active true
|
||||
:timestamp now
|
||||
:contacts [{:identity chat-id}]}))
|
||||
{:chat-id chat-id
|
||||
:name (or name (gfycat/generate-gfy chat-id))
|
||||
:color styles/default-chat-color
|
||||
:group-chat false
|
||||
:is-active true
|
||||
:timestamp now
|
||||
:contacts [{:identity chat-id}]
|
||||
:last-from-clock-value 0
|
||||
:last-to-clock-value 0}))
|
||||
|
||||
(defn add-chat
|
||||
"Adds new chat to db & realm, if the chat with same id already exists, justs restores it"
|
||||
([cofx chat-id]
|
||||
(add-chat cofx chat-id {}))
|
||||
([{:keys [db get-stored-chat] :as cofx} chat-id chat-props]
|
||||
([chat-id cofx]
|
||||
(add-chat chat-id {} cofx))
|
||||
([chat-id chat-props {:keys [db get-stored-chat] :as cofx}]
|
||||
(let [{:keys [deleted-chats]} db
|
||||
new-chat (merge (if (get deleted-chats chat-id)
|
||||
(assoc (get-stored-chat chat-id) :is-active true)
|
||||
(create-new-chat cofx chat-id))
|
||||
(create-new-chat chat-id cofx))
|
||||
chat-props)]
|
||||
{:db (-> db
|
||||
(update :chats assoc chat-id new-chat)
|
||||
(update :deleted-chats (fnil disj #{}) chat-id))
|
||||
:save-chat new-chat})))
|
||||
:data-store/save-chat new-chat})))
|
||||
|
||||
(defn add-public-chat
|
||||
"Adds new public group chat to db & realm"
|
||||
[topic {:keys [db now] :as cofx}]
|
||||
(let [chat {:chat-id topic
|
||||
:name topic
|
||||
:color styles/default-chat-color
|
||||
:group-chat true
|
||||
:public? true
|
||||
:is-active true
|
||||
:timestamp now
|
||||
:last-to-clock-value 0
|
||||
:last-from-clock-value 0}]
|
||||
{:db (assoc-in db [:chats topic] chat)
|
||||
:data-store/save-chat chat}))
|
||||
|
||||
(defn add-group-chat
|
||||
"Adds new private group chat to db & realm"
|
||||
[chat-id chat-name admin participants {:keys [db now] :as cofx}]
|
||||
(let [chat {:chat-id chat-id
|
||||
:name chat-name
|
||||
:color styles/default-chat-color
|
||||
:group-chat true
|
||||
:group-admin admin
|
||||
:is-active true
|
||||
:timestamp now
|
||||
:contacts (mapv (partial hash-map :identity) participants)
|
||||
:last-to-clock-value 0
|
||||
:last-from-clock-value 0}]
|
||||
{:db (assoc-in db [:chats chat-id] chat)
|
||||
:data-store/save-chat chat}))
|
||||
|
||||
;; TODO (yenda): there should be an option to update the timestamp
|
||||
;; this shouldn't need a specific function like `upsert-chat` which
|
||||
;; is wrongfuly named
|
||||
(defn update-chat
|
||||
"Updates chat properties when not deleted, if chat is not present in app-db, creates a default new one"
|
||||
[{:keys [db] :as cofx} {:keys [chat-id] :as chat-props}]
|
||||
[{:keys [chat-id] :as chat-props} {:keys [db] :as cofx}]
|
||||
(let [{:keys [chats deleted-chats]} db]
|
||||
(if (get deleted-chats chat-id) ;; when chat is deleted, don't change anything
|
||||
{:db db}
|
||||
(let [chat (merge (or (get chats chat-id)
|
||||
(create-new-chat cofx chat-id))
|
||||
(create-new-chat chat-id cofx))
|
||||
chat-props)]
|
||||
{:db (update-in db [:chats chat-id] merge chat)
|
||||
:save-chat chat}))))
|
||||
:data-store/save-chat chat}))))
|
||||
|
||||
;; TODO (yenda): an upsert is suppose to add the entry if it doesn't
|
||||
;; exist and update it if it does
|
||||
(defn upsert-chat
|
||||
"Just like `update-chat` only implicitely updates timestamp"
|
||||
[cofx chat]
|
||||
(update-chat cofx (assoc chat :timestamp (:now cofx))))
|
||||
[chat cofx]
|
||||
(update-chat (assoc chat :timestamp (:now cofx)) cofx))
|
||||
|
||||
(defn new-update? [{:keys [added-to-at removed-at removed-from-at]} timestamp]
|
||||
(and (> timestamp added-to-at)
|
||||
(> timestamp removed-at)
|
||||
(> timestamp removed-from-at)))
|
||||
|
||||
(defn remove-chat [{:keys [db]} chat-id]
|
||||
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])]
|
||||
(cond-> {:db (-> db
|
||||
(update :chats dissoc chat-id)
|
||||
(update :deleted-chats (fnil conj #{}) chat-id))
|
||||
:delete-pending-messages chat-id}
|
||||
(or group-chat debug?)
|
||||
(assoc :delete-messages chat-id)
|
||||
debug?
|
||||
(assoc :delete-chat chat-id)
|
||||
(not debug?)
|
||||
(assoc :deactivate-chat chat-id))))
|
||||
(defn remove-chat [chat-id {:keys [db] :as cofx}]
|
||||
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])
|
||||
fx (cond-> {:db (-> db
|
||||
(update :chats dissoc chat-id)
|
||||
(update :deleted-chats (fnil conj #{}) chat-id))}
|
||||
(or group-chat debug?)
|
||||
(assoc :data-store/delete-messages chat-id)
|
||||
debug?
|
||||
(assoc :data-store/delete-chat chat-id)
|
||||
(not debug?)
|
||||
(assoc :data-store/deactivate-chat chat-id))]
|
||||
(handlers/merge-fx cofx fx (transport/unsubscribe-from-chat chat-id))))
|
||||
|
||||
(defn bot-only-chat? [db chat-id]
|
||||
(let [{:keys [group-chat contacts]} (get-in db [:chats chat-id])]
|
||||
(and (not group-chat)
|
||||
(get-in db [:contacts/contacts (:identity (first contacts)) :dapp?]))))
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
(ns status-im.chat.models.input
|
||||
(:require [clojure.string :as str]
|
||||
[goog.object :as object]
|
||||
[status-im.ui.components.react :as rc]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.chat.constants :as const]
|
||||
[status-im.chat.models.commands :as commands-model]
|
||||
[status-im.chat.views.input.validation-messages :refer [validation-message]]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.js-dependencies :as dependencies]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
|
@ -203,28 +199,3 @@
|
|||
[(keyword (get-in params [i :name])) value]))
|
||||
(remove #(nil? (first %)))
|
||||
(into {}))))
|
||||
|
||||
(defn modified-db-after-change
|
||||
"Returns the new db object that should be used after any input change."
|
||||
[{:keys [current-chat-id] :as db}]
|
||||
(let [command (selected-chat-command db)
|
||||
prev-command (get-in db [:chat-ui-props current-chat-id :prev-command])]
|
||||
(if command
|
||||
(cond-> db
|
||||
;; clear the bot db
|
||||
(not= prev-command (-> command :command :name))
|
||||
(assoc-in [:bot-db (or (:bot command) current-chat-id)] nil)
|
||||
;; clear the chat's validation messages
|
||||
true
|
||||
(assoc-in [:chat-ui-props current-chat-id :validation-messages] nil))
|
||||
(-> db
|
||||
;; clear input metadata
|
||||
(assoc-in [:chats current-chat-id :input-metadata] nil)
|
||||
;; clear
|
||||
(update-in [:chat-ui-props current-chat-id]
|
||||
merge
|
||||
{:result-box nil
|
||||
:validation-messages nil
|
||||
:prev-command (-> command :command :name)})))))
|
||||
|
||||
(defmulti validation-handler (fn [name] (keyword name)))
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
[status-im.chat.events.requests :as requests-events]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.commands :as commands-model]
|
||||
[status-im.utils.clocks :as clocks-utils]))
|
||||
|
||||
(defn- get-current-account
|
||||
[{:accounts/keys [accounts current-account-id]}]
|
||||
(get accounts current-account-id))
|
||||
[status-im.utils.clocks :as clocks-utils]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as protocol]))
|
||||
|
||||
(def receive-interceptors
|
||||
[(re-frame/inject-cofx :get-stored-message) (re-frame/inject-cofx :get-stored-chat)
|
||||
[(re-frame/inject-cofx :data-store/get-message) (re-frame/inject-cofx :data-store/get-chat)
|
||||
(re-frame/inject-cofx :random-id) re-frame/trim-v])
|
||||
|
||||
(defn- lookup-response-ref
|
||||
|
@ -25,71 +25,133 @@
|
|||
contacts)]
|
||||
(:ref (get available-commands-responses response-name))))
|
||||
|
||||
(defn add-message-to-db
|
||||
[db chat-id {:keys [message-id clock-value] :as message} current-chat?]
|
||||
(let [prepared-message (cond-> (assoc message
|
||||
:chat-id chat-id
|
||||
:appearing? true)
|
||||
(defn- add-message
|
||||
[chat-id {:keys [message-id from-clock-value to-clock-value] :as message} current-chat? {:keys [db]}]
|
||||
(let [prepared-message (cond-> (assoc message :appearing? true)
|
||||
(not current-chat?)
|
||||
(assoc :appearing? false))]
|
||||
(cond-> (-> db
|
||||
(update-in [:chats chat-id :messages] assoc message-id prepared-message)
|
||||
(update-in [:chats chat-id :last-clock-value] (fnil max 0) clock-value))
|
||||
(not current-chat?)
|
||||
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))))
|
||||
{:db (cond-> (-> db
|
||||
(update-in [:chats chat-id :messages] dissoc from-clock-value)
|
||||
(update-in [:chats chat-id :messages] assoc message-id prepared-message)
|
||||
(update-in [:chats chat-id :last-from-clock-value] max from-clock-value)
|
||||
(update-in [:chats chat-id :last-to-clock-value] max to-clock-value))
|
||||
(not current-chat?)
|
||||
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
|
||||
:data-store/save-message prepared-message}))
|
||||
|
||||
;; We start with [0 0] ([from-clock-value to-clock-value]) for each participant of 1-1 chat (local perspective on each device).
|
||||
;; Now for sending, we always increment the to-clock-value and include it in message payload being sent (so only to-clock-value is present in network message).
|
||||
;; Locally, the sent message always replicates the latest-from-clock-value of the chat.
|
||||
;; Upon receiving message, receiver reads the to-clock-value of the received message and sets that to be the from-clock-value locally
|
||||
;; (this will be also the new latest-from-clock-value of the chat), to-clock-value for the message is the latest-to-clock-value of the 1-1 chat`.
|
||||
|
||||
;; All this ensures, that there will be no [from-clock-value to-clock-value] duplicate in chat message on each device + the local order will appear consistent,
|
||||
;; even if it’s possible it won’t be the same on both devices (for example user A sends 5 messages, during the sending,
|
||||
;; he receives the message from user B, so his local order will be A1, A2, B, A3, A4, A5, but his messages will take a long time to reach user B,
|
||||
;; for some reason, so user B will see it as B, A1, A2, A3, A4, A5).
|
||||
;; I don’t think that’s very problematic and I don’t think we can do much about it without single source of truth where order received messages are serialised
|
||||
;; and definite order is established (server), it is the case even in the current implementation.
|
||||
(defn- prepare-chat [chat-id {:keys [db] :as cofx}]
|
||||
(if (get-in db [:chats chat-id])
|
||||
(chat-model/upsert-chat {:chat-id chat-id} cofx)
|
||||
(chat-model/add-chat chat-id cofx)))
|
||||
|
||||
(defn- get-current-account [{:accounts/keys [accounts current-account-id]}]
|
||||
(get accounts current-account-id))
|
||||
|
||||
(defn- send-message-seen [chat-id message-id send-seen? cofx]
|
||||
(when send-seen?
|
||||
(transport/send (protocol/map->MessagesSeen {:message-ids #{message-id}}) chat-id cofx)))
|
||||
|
||||
(defn- placeholder-message [chat-id from timestamp temp-id to-clock]
|
||||
{:message-id temp-id
|
||||
:outgoing false
|
||||
:chat-id chat-id
|
||||
:from from
|
||||
:to "me"
|
||||
:content "Waiting for message to arrive..."
|
||||
:content-type constants/content-type-placeholder
|
||||
:show? true
|
||||
:from-clock-value temp-id
|
||||
:to-clock-value to-clock
|
||||
:timestamp timestamp})
|
||||
|
||||
(defn- add-placeholder-messages [chat-id from timestamp old-from-clock to-clock new-from-clock {:keys [db]}]
|
||||
(when (> (- new-from-clock old-from-clock) 1)
|
||||
{:db (reduce (fn [db temp-id]
|
||||
(assoc-in db [:chats chat-id :messages temp-id] (placeholder-message chat-id from timestamp temp-id to-clock)))
|
||||
db
|
||||
(range (inc old-from-clock) new-from-clock))}))
|
||||
|
||||
(defn- add-received-message
|
||||
[{:keys [from message-id chat-id content content-type timestamp to-clock-value] :as message}
|
||||
{:keys [db now] :as cofx}]
|
||||
(let [{:keys [current-chat-id
|
||||
view-id
|
||||
access-scope->commands-responses]
|
||||
:contacts/keys [contacts]} db
|
||||
{:keys [public-key] :as current-account} (get-current-account db)
|
||||
current-chat? (and (= :chat view-id) (= current-chat-id chat-id))
|
||||
{:keys [last-from-clock-value
|
||||
last-to-clock-value] :as chat} (get-in db [:chats chat-id])
|
||||
request-command (:request-command content)
|
||||
command-request? (and (= content-type constants/content-type-command-request)
|
||||
request-command)
|
||||
new-from-clock-value (or to-clock-value (inc last-from-clock-value))
|
||||
new-timestamp (or timestamp now)]
|
||||
(handlers/merge-fx cofx
|
||||
(add-message chat-id
|
||||
(cond-> (assoc message
|
||||
:timestamp new-timestamp
|
||||
:show? true
|
||||
:from-clock-value new-from-clock-value
|
||||
:to-clock-value last-to-clock-value)
|
||||
public-key
|
||||
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
|
||||
command-request?
|
||||
(assoc-in [:content :request-command-ref]
|
||||
(lookup-response-ref access-scope->commands-responses
|
||||
current-account chat contacts request-command)))
|
||||
current-chat?)
|
||||
(send-message-seen chat-id message-id (and public-key
|
||||
current-chat?
|
||||
(not (chat-model/bot-only-chat? db chat-id))
|
||||
(not (= constants/system from))))
|
||||
(add-placeholder-messages chat-id from new-timestamp last-from-clock-value last-to-clock-value new-from-clock-value))))
|
||||
|
||||
(defn receive
|
||||
[{:keys [db now] :as cofx}
|
||||
{:keys [from group-id chat-id content-type content message-id timestamp clock-value]
|
||||
:as message}]
|
||||
(let [{:keys [current-chat-id view-id
|
||||
access-scope->commands-responses] :contacts/keys [contacts]} db
|
||||
{:keys [public-key] :as current-account} (get-current-account db)
|
||||
chat-identifier (or group-id chat-id from)
|
||||
current-chat? (and (= :chat view-id)
|
||||
(= current-chat-id chat-identifier))
|
||||
fx (if (get-in db [:chats chat-identifier])
|
||||
(chat-model/upsert-chat cofx {:chat-id chat-identifier
|
||||
:group-chat (boolean group-id)})
|
||||
(chat-model/add-chat cofx chat-identifier))
|
||||
chat (get-in fx [:db :chats chat-identifier])
|
||||
command-request? (= content-type constants/content-type-command-request)
|
||||
command (:command content)
|
||||
enriched-message (cond-> (assoc message
|
||||
:chat-id chat-identifier
|
||||
:timestamp (or timestamp now)
|
||||
:show? true
|
||||
:clock-value (clocks-utils/receive
|
||||
clock-value
|
||||
(:last-clock-value chat)))
|
||||
public-key
|
||||
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
|
||||
(and command command-request?)
|
||||
(assoc-in [:content :content-command-ref]
|
||||
(lookup-response-ref access-scope->commands-responses
|
||||
current-account
|
||||
(get-in fx [:db :chats chat-identifier])
|
||||
contacts
|
||||
command)))]
|
||||
(cond-> (-> fx
|
||||
(update :db add-message-to-db chat-identifier enriched-message current-chat?)
|
||||
(assoc :save-message (dissoc enriched-message :new?)))
|
||||
command-request?
|
||||
(requests-events/add-request chat-identifier enriched-message))))
|
||||
[{:keys [chat-id message-id] :as message} cofx]
|
||||
(handlers/merge-fx cofx
|
||||
(prepare-chat chat-id)
|
||||
(add-received-message message)
|
||||
(requests-events/add-request chat-id message-id)))
|
||||
|
||||
(defn system-message [chat-id message-id timestamp content]
|
||||
{:message-id message-id
|
||||
:outgoing false
|
||||
:chat-id chat-id
|
||||
:from constants/system
|
||||
:username constants/system
|
||||
:timestamp timestamp
|
||||
:show? true
|
||||
:content content
|
||||
:content-type constants/text-content-type})
|
||||
|
||||
(defn group-message? [{:keys [message-type]}]
|
||||
(#{:group-user-message :public-group-user-message} message-type))
|
||||
|
||||
(defn add-to-chat?
|
||||
[{:keys [db get-stored-message]} {:keys [group-id chat-id from message-id]}]
|
||||
(let [chat-identifier (or group-id chat-id from)
|
||||
{:keys [chats deleted-chats current-public-key]} db
|
||||
{:keys [messages not-loaded-message-ids]} (get chats chat-identifier)]
|
||||
[{:keys [db get-stored-message]} {:keys [chat-id from message-id] :as message}]
|
||||
(let [{:keys [chats deleted-chats current-public-key]} db
|
||||
{:keys [messages not-loaded-message-ids]} (get chats chat-id)]
|
||||
(when (not= from current-public-key)
|
||||
(if group-id
|
||||
(not (or (get deleted-chats chat-identifier)
|
||||
(if (group-message? message)
|
||||
(not (or (get deleted-chats chat-id)
|
||||
(get messages message-id)
|
||||
(get not-loaded-message-ids message-id)))
|
||||
(not (or (get messages message-id)
|
||||
(get not-loaded-message-ids message-id)
|
||||
(and (get deleted-chats chat-identifier)
|
||||
(and (get deleted-chats chat-id)
|
||||
(get-stored-message message-id))))))))
|
||||
|
||||
(defn message-seen-by? [message user-pk]
|
||||
|
@ -99,7 +161,7 @@
|
|||
|
||||
(def send-interceptors
|
||||
[(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :random-id-seq)
|
||||
(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v])
|
||||
(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v])
|
||||
|
||||
(defn- handle-message-from-bot [{:keys [random-id] :as cofx} {:keys [message chat-id]}]
|
||||
(when-let [message (cond
|
||||
|
@ -120,15 +182,12 @@
|
|||
:chat-id chat-id
|
||||
:from chat-id
|
||||
:to "me"})]
|
||||
(receive cofx message)))
|
||||
(receive message cofx)))
|
||||
|
||||
(defn- send-dapp-message!
|
||||
[{{:accounts/keys [current-account-id] :as db} :db :as cofx}
|
||||
{{:keys [message-type]
|
||||
:as message} :message
|
||||
:keys [chat-id command] :as args}]
|
||||
(if command
|
||||
(when-let [text-message (get-in command [:content :handler-data :text-message])]
|
||||
[{{:accounts/keys [current-account-id] :as db} :db :as cofx} chat-id {:keys [content-type] :as message}]
|
||||
(if (= content-type constants/content-type-command)
|
||||
(when-let [text-message (get-in message [:content :handler-data :text-message])]
|
||||
(handle-message-from-bot cofx {:message text-message
|
||||
:chat-id chat-id}))
|
||||
(let [data (get-in db [:local-storage chat-id])]
|
||||
|
@ -138,146 +197,107 @@
|
|||
:context {:data data
|
||||
:from current-account-id}}})))
|
||||
|
||||
(defn- generate-message
|
||||
[{:keys [network-status]} chat-id message]
|
||||
(assoc (select-keys message [:from :message-id])
|
||||
:payload (cond-> (select-keys message [:content :content-type :clock-value :timestamp :show?])
|
||||
(= :offline network-status)
|
||||
(assoc :show? false))))
|
||||
|
||||
(defn send
|
||||
[{{:keys [web3 chats]
|
||||
:accounts/keys [accounts current-account-id]
|
||||
:contacts/keys [contacts] :as db} :db :as cofx}
|
||||
{:keys [chat-id command message] :as args}]
|
||||
(defn- send
|
||||
[chat-id send-record {{:contacts/keys [contacts]} :db :as cofx}]
|
||||
(let [{:keys [dapp? fcm-token]} (get contacts chat-id)]
|
||||
(if dapp?
|
||||
(send-dapp-message! cofx args)
|
||||
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id])
|
||||
options {:web3 web3
|
||||
:message (generate-message db chat-id (or command message))}]
|
||||
(cond
|
||||
(and group-chat (not public?))
|
||||
(let [{:keys [public-key private-key]} (get chats chat-id)]
|
||||
{:send-group-message (assoc options
|
||||
:group-id chat-id
|
||||
:keypair {:public public-key
|
||||
:private private-key})})
|
||||
(send-dapp-message! cofx chat-id send-record)
|
||||
(if fcm-token
|
||||
(handlers/merge-fx cofx
|
||||
{:send-notification {:message "message"
|
||||
:payload {:title "Status" :body "You have a new message"}
|
||||
:tokens [fcm-token]}}
|
||||
(transport/send send-record chat-id))
|
||||
(transport/send send-record chat-id cofx)))))
|
||||
|
||||
(and group-chat public?)
|
||||
{:send-public-group-message (assoc options :group-id chat-id
|
||||
:username (get-in accounts [current-account-id :name]))}
|
||||
(defn add-message-type [message {:keys [chat-id group-chat public?]}]
|
||||
(cond-> message
|
||||
(not group-chat)
|
||||
(assoc :message-type :user-message)
|
||||
(and group-chat public?)
|
||||
(assoc :message-type :public-group-user-message)
|
||||
(and group-chat (not public?))
|
||||
(assoc :message-type :group-user-message)))
|
||||
|
||||
:else
|
||||
(merge {:send-message (assoc-in options [:message :to] chat-id)}
|
||||
(when fcm-token {:send-notification {:message "message"
|
||||
:payload {:title "Status" :body "You have a new message"}
|
||||
:tokens [fcm-token]}})))))))
|
||||
(defn- prepare-plain-message [{:keys [identity message-text]}
|
||||
{:keys [chat-id last-to-clock-value last-from-clock-value] :as chat} now]
|
||||
(add-message-type {:chat-id chat-id
|
||||
:content message-text
|
||||
:from identity
|
||||
:content-type constants/text-content-type
|
||||
:outgoing true
|
||||
:timestamp now
|
||||
:to-clock-value (inc last-to-clock-value)
|
||||
:from-clock-value last-from-clock-value
|
||||
:show? true}
|
||||
chat))
|
||||
|
||||
(defn- prepare-message [params chat now random-id]
|
||||
(let [{:keys [chat-id identity message-text]} params
|
||||
{:keys [group-chat public? last-clock-value]} chat
|
||||
message {:message-id random-id
|
||||
:chat-id chat-id
|
||||
:content message-text
|
||||
:from identity
|
||||
:content-type constants/text-content-type
|
||||
:outgoing true
|
||||
:timestamp now
|
||||
:clock-value (clocks-utils/send last-clock-value)
|
||||
:show? true}]
|
||||
(cond-> message
|
||||
(not group-chat)
|
||||
(assoc :message-type :user-message
|
||||
:to chat-id)
|
||||
group-chat
|
||||
(assoc :group-id chat-id)
|
||||
(and group-chat public?)
|
||||
(assoc :message-type :public-group-user-message)
|
||||
(and group-chat (not public?))
|
||||
(assoc :message-type :group-user-message)
|
||||
(not group-chat)
|
||||
(assoc :to chat-id :message-type :user-message))))
|
||||
(def ^:private transport-keys [:content :content-type :message-type :to-clock-value :timestamp])
|
||||
|
||||
(defn- upsert-and-send [{:keys [chat-id] :as message} cofx]
|
||||
(let [send-record (protocol/map->Message (select-keys message transport-keys))
|
||||
message-with-id (assoc message :message-id (transport.utils/message-id send-record))]
|
||||
(handlers/merge-fx cofx
|
||||
(chat-model/upsert-chat {:chat-id chat-id})
|
||||
(add-message chat-id message-with-id true)
|
||||
(send chat-id send-record))))
|
||||
|
||||
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
|
||||
(let [chat (get-in db [:chats chat-id])
|
||||
message (prepare-message params chat now random-id)
|
||||
params' (assoc params :message message)
|
||||
fx (-> (chat-model/upsert-chat cofx {:chat-id chat-id})
|
||||
(update :db add-message-to-db chat-id message true)
|
||||
(assoc :save-message message))]
|
||||
(merge fx (send cofx params'))))
|
||||
(upsert-and-send (prepare-plain-message params (get-in db [:chats chat-id]) now) cofx))
|
||||
|
||||
(defn- prepare-command
|
||||
[identity chat-id now clock-value
|
||||
(defn- prepare-command-message
|
||||
[identity
|
||||
{:keys [last-to-clock-value last-from-clock-value chat-id] :as chat}
|
||||
now
|
||||
{request-params :params
|
||||
request-command :command
|
||||
:keys [prefill prefillBotDb]
|
||||
:as request}
|
||||
{:keys [id params command to-message handler-data content-type]}]
|
||||
{:keys [params command handler-data content-type]}]
|
||||
(let [content (if request
|
||||
{:command request-command
|
||||
:params (assoc request-params :bot-db (:bot-db params))
|
||||
:prefill prefill
|
||||
:prefill-bot-db prefillBotDb}
|
||||
{:command (:name command)
|
||||
:scope (:scope command)
|
||||
:params params})
|
||||
content' (assoc content :handler-data handler-data
|
||||
:type (name (:type command))
|
||||
:content-command (:name command)
|
||||
:content-command-scope-bitmask (:scope-bitmask command)
|
||||
:content-command-ref (:ref command)
|
||||
:preview (:preview command)
|
||||
:short-preview (:short-preview command)
|
||||
:bot (or (:bot command)
|
||||
(:owner-id command)))]
|
||||
{:message-id id
|
||||
:from identity
|
||||
:to chat-id
|
||||
:timestamp now
|
||||
:content content'
|
||||
:content-type (or content-type
|
||||
(if request
|
||||
constants/content-type-command-request
|
||||
constants/content-type-command))
|
||||
:outgoing true
|
||||
:to-message to-message
|
||||
:type (:type command)
|
||||
:has-handler (:has-handler command)
|
||||
:clock-value (clocks-utils/send clock-value)
|
||||
:show? true}))
|
||||
{:request-command request-command
|
||||
;; TODO janherich this is technically not correct, but works for now
|
||||
:request-command-ref (:ref command)
|
||||
:params (assoc request-params :bot-db (:bot-db params))
|
||||
:prefill prefill
|
||||
:prefill-bot-db prefillBotDb}
|
||||
{:params params})
|
||||
content' (assoc content
|
||||
:command (:name command)
|
||||
:handler-data handler-data
|
||||
:type (name (:type command))
|
||||
:command-scope-bitmask (:scope-bitmask command)
|
||||
:command-ref (:ref command)
|
||||
:preview (:preview command)
|
||||
:short-preview (:short-preview command)
|
||||
:bot (:owner-id command))]
|
||||
(add-message-type {:chat-id chat-id
|
||||
:from identity
|
||||
:timestamp now
|
||||
:content content'
|
||||
:content-type (or content-type
|
||||
(if request
|
||||
constants/content-type-command-request
|
||||
constants/content-type-command))
|
||||
:outgoing true
|
||||
:to-clock-value (inc last-to-clock-value)
|
||||
:from-clock-value last-from-clock-value
|
||||
:show? true}
|
||||
chat)))
|
||||
|
||||
(defn- add-console-responses
|
||||
[command handler-data {:keys [random-id-seq]}]
|
||||
{:dispatch-n (->> (console-events/console-respond-command-messages command handler-data random-id-seq)
|
||||
(mapv (partial vector :chat-received-message/add)))})
|
||||
|
||||
(defn send-command
|
||||
[{{:keys [current-public-key chats]} :db :keys [now random-id-seq] :as cofx} params]
|
||||
(let [{{:keys [handler-data
|
||||
command]
|
||||
:as content} :command
|
||||
chat-id :chat-id} params
|
||||
request (:request handler-data)
|
||||
last-clock-value (get-in chats [chat-id :last-clock-value])
|
||||
hidden-params (->> (:params command)
|
||||
(filter :hidden)
|
||||
(map :name))
|
||||
command' (prepare-command current-public-key chat-id now last-clock-value request content)
|
||||
params' (assoc params :command command')
|
||||
fx (-> (chat-model/upsert-chat cofx {:chat-id chat-id})
|
||||
(update :db add-message-to-db chat-id command' true)
|
||||
(assoc :save-message (-> command'
|
||||
(assoc :chat-id chat-id)
|
||||
(update-in [:content :params]
|
||||
#(apply dissoc % hidden-params))
|
||||
(dissoc :to-message :has-handler :raw-input))))]
|
||||
(cond-> (merge fx (send cofx params'))
|
||||
|
||||
(:to-message command')
|
||||
(requests-events/request-answered chat-id (:to-message command'))
|
||||
|
||||
(= constants/console-chat-id chat-id)
|
||||
(as-> fx'
|
||||
(let [messages (console-events/console-respond-command-messages params' random-id-seq)
|
||||
events (mapv #(vector :chat-received-message/add %) messages)]
|
||||
(update fx' :dispatch-n into events))))))
|
||||
[{{:keys [current-public-key chats] :as db} :db :keys [now] :as cofx} params]
|
||||
(let [{{:keys [handler-data to-message command] :as content} :command chat-id :chat-id} params
|
||||
request (:request handler-data)]
|
||||
(handlers/merge-fx cofx
|
||||
(upsert-and-send (prepare-command-message current-public-key (get chats chat-id) now request content))
|
||||
(add-console-responses command handler-data)
|
||||
(requests-events/request-answered chat-id to-message))))
|
||||
|
||||
(defn invoke-console-command-handler
|
||||
[{:keys [db] :as cofx} {:keys [command] :as command-params}]
|
||||
|
@ -311,9 +331,9 @@
|
|||
{:call-jail {:jail-id identity
|
||||
:path [handler-type [name scope-bitmask] :handler]
|
||||
:params jail-params
|
||||
:callback-events-creator (fn [jail-response]
|
||||
(when-not (:async-handler command)
|
||||
[[:command-handler! chat-id orig-params jail-response]]))}}))
|
||||
:callback-event-creator (fn [jail-response]
|
||||
(when-not (:async-handler command)
|
||||
[:command-handler! chat-id orig-params jail-response]))}}))
|
||||
|
||||
(defn process-command
|
||||
[cofx {:keys [command chat-id] :as params}]
|
||||
|
|
|
@ -35,13 +35,11 @@
|
|||
[react/view style/action
|
||||
[vector-icons/icon :icons/dots-horizontal]]])
|
||||
|
||||
(defview add-contact-bar []
|
||||
(letsubs [chat-id [:get-current-chat-id]
|
||||
pending-contact? [:current-contact :pending?]]
|
||||
(when (or (nil? pending-contact?) ; user not in contact list
|
||||
pending-contact?)
|
||||
(defview add-contact-bar [contact-identity]
|
||||
(letsubs [{:keys [pending?] :as contact} [:contact-by-identity contact-identity]]
|
||||
(when (or pending? (not contact)) ;; contact is pending or not in contact list at all
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:add-contact chat-id])
|
||||
{:on-press #(re-frame/dispatch [:add-contact contact-identity])
|
||||
:accessibility-label :add-to-contacts-button}
|
||||
[react/view style/add-contact
|
||||
[react/text {:style style/add-contact-text}
|
||||
|
@ -52,7 +50,7 @@
|
|||
:options (actions/actions group-chat? chat-id public?)}))
|
||||
|
||||
(defview chat-toolbar [public?]
|
||||
(letsubs [{:keys [group-chat name chat-id]} [:get-current-chat]]
|
||||
(letsubs [{:keys [group-chat name chat-id contacts]} [:get-current-chat]]
|
||||
[react/view
|
||||
[status-bar/status-bar]
|
||||
[toolbar/platform-agnostic-toolbar {}
|
||||
|
@ -62,7 +60,7 @@
|
|||
:icon-opts {:color :black
|
||||
:accessibility-label :chat-menu-button}
|
||||
:handler #(on-options chat-id name group-chat public?)}]]]
|
||||
(when-not (or public? group-chat) [add-contact-bar])]))
|
||||
(when-not (or public? group-chat) [add-contact-bar (-> contacts first :identity)])]))
|
||||
|
||||
(defmulti message-row (fn [{{:keys [type]} :row}] type))
|
||||
|
||||
|
|
|
@ -93,10 +93,11 @@
|
|||
|
||||
(defn message-datemark-groups
|
||||
"Transforms map of messages into sequence of `[datemark messages]` tuples, where
|
||||
messages with particular datemark are sorted according to their `:clock-value` and
|
||||
tuples themeselves are sorted according to the highest `:clock-value` in the messages."
|
||||
messages with particular datemark are sorted according to their `:clock-values` and
|
||||
tuples themeselves are sorted according to the highest `:clock-values` in the messages."
|
||||
[id->messages]
|
||||
(let [datemark->messages (transduce (comp (map second)
|
||||
(let [clock-sorter (juxt :from-clock-value :to-clock-value)
|
||||
datemark->messages (transduce (comp (map second)
|
||||
(filter :show?)
|
||||
(map (fn [{:keys [timestamp] :as msg}]
|
||||
(assoc msg :datemark (time/day-relative timestamp)))))
|
||||
|
@ -106,8 +107,9 @@
|
|||
id->messages)]
|
||||
(->> datemark->messages
|
||||
(map (fn [[datemark messages]]
|
||||
[datemark (sort-by :clock-value > messages)]))
|
||||
(sort-by (comp :clock-value first second) >))))
|
||||
[datemark (->> messages (sort-by clock-sorter) reverse)]))
|
||||
(sort-by (comp clock-sorter first second))
|
||||
reverse)))
|
||||
|
||||
(reg-sub
|
||||
:get-chat-message-datemark-groups
|
||||
|
@ -330,4 +332,4 @@
|
|||
:get-chats-unread-messages-number
|
||||
:<- [:get-active-chats]
|
||||
(fn [chats _]
|
||||
(apply + (map #(count (:unviewed-messages %)) (vals chats)))))
|
||||
(apply + (map #(count (:unviewed-messages %)) (vals chats)))))
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
(defn- delete-chat [chat-id group?]
|
||||
{:label (i18n/label :t/delete-chat)
|
||||
:action #(re-frame/dispatch [:delete-chat? chat-id group?])})
|
||||
:action #(re-frame/dispatch [:remove-chat-and-navigate-home? chat-id group?])})
|
||||
|
||||
(defn- leave-group-chat [chat-id public?]
|
||||
{:label (i18n/label (if public? :t/leave-public-chat :t/leave-group-chat))
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
[status-im.utils.utils :as utils]))
|
||||
|
||||
(defview basic-text-input [{:keys [set-layout-height-fn set-container-width-fn height single-line-input?]}]
|
||||
(letsubs [input-text [:chat :input-text]
|
||||
command [:selected-chat-command]
|
||||
(letsubs [input-text [:chat :input-text]
|
||||
input-focused? [:get-current-chat-ui-prop :input-focused?]
|
||||
input-ref (atom nil)]
|
||||
[react/text-input
|
||||
|
@ -49,10 +48,7 @@
|
|||
content-size)
|
||||
(set-layout-height-fn (.-height content-size)))
|
||||
(when (not= text input-text)
|
||||
(re-frame/dispatch [:set-chat-input-text text])
|
||||
(when command
|
||||
(re-frame/dispatch [:load-chat-parameter-box (:command command)]))
|
||||
(re-frame/dispatch [:update-input-data]))))
|
||||
(re-frame/dispatch [:set-chat-input-text text]))))
|
||||
:on-content-size-change (when (and (not input-focused?)
|
||||
(not single-line-input?))
|
||||
#(let [s (.-contentSize (.-nativeEvent %))
|
||||
|
@ -120,8 +116,7 @@
|
|||
[react/text-input (merge {:ref #(re-frame/dispatch [:set-chat-ui-props {:seq-input-ref %}])
|
||||
:style (style/seq-input-text command-width container-width)
|
||||
:default-value (or seq-arg-input-text "")
|
||||
:on-change-text #(do (re-frame/dispatch [:set-chat-seq-arg-input-text %])
|
||||
(re-frame/dispatch [:load-chat-parameter-box (:command command)])
|
||||
:on-change-text #(do (re-frame/dispatch [:set-chat-seq-arg-input-text %])
|
||||
(re-frame/dispatch [:set-chat-ui-props {:validation-messages nil}]))
|
||||
:placeholder placeholder
|
||||
:accessibility-label :chat-request-input
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
(defview message-content-command
|
||||
[{:keys [content params] :as message}]
|
||||
(letsubs [command [:get-command (:content-command-ref content)]]
|
||||
(letsubs [command [:get-command (:command-ref content)]]
|
||||
(let [preview (:preview content)
|
||||
{:keys [color] icon-path :icon} command]
|
||||
[react/view style/content-command-view
|
||||
|
@ -119,7 +119,7 @@
|
|||
(fn [text-seq]
|
||||
(map (fn [text] {:text text :url? false}) text-seq))))
|
||||
|
||||
(defn- autolink [string on-press]
|
||||
(defn- autolink [string event-on-press]
|
||||
(->> (parse-url string)
|
||||
(map-indexed (fn [idx {:keys [text url?]}]
|
||||
(if url?
|
||||
|
@ -127,7 +127,7 @@
|
|||
[react/text
|
||||
{:key idx
|
||||
:style {:color colors/blue}
|
||||
:on-press #(on-press url)}
|
||||
:on-press #(re-frame/dispatch [event-on-press url])}
|
||||
url])
|
||||
text)))
|
||||
vec))
|
||||
|
@ -142,7 +142,7 @@
|
|||
replacements))
|
||||
|
||||
;; todo rewrite this, naive implementation
|
||||
(defn- parse-text [string url-on-press]
|
||||
(defn- parse-text [string event-on-press]
|
||||
(parse-str-regx string
|
||||
regx-styled
|
||||
(fn [text-seq]
|
||||
|
@ -157,15 +157,22 @@
|
|||
(map-indexed (fn [idx string]
|
||||
(apply react/text
|
||||
{:key (str idx "_" string)}
|
||||
(autolink string url-on-press)))
|
||||
(autolink string event-on-press)))
|
||||
text-seq))))
|
||||
|
||||
(def cached-parse-text (memoize parse-text))
|
||||
|
||||
(defn text-message
|
||||
[{:keys [content] :as message}]
|
||||
[message-view message
|
||||
(let [parsed-text (parse-text content #(re-frame/dispatch [:browse-link-from-message %]))]
|
||||
(let [parsed-text (cached-parse-text content :browse-link-from-message)]
|
||||
[react/text {:style (style/text-message message)} parsed-text])])
|
||||
|
||||
(defn placeholder-message
|
||||
[{:keys [content] :as message}]
|
||||
[message-view message
|
||||
[react/text {:style (style/text-message message)} content]])
|
||||
|
||||
(defmulti message-content (fn [_ message _] (message :content-type)))
|
||||
|
||||
(defmethod message-content constants/content-type-command-request
|
||||
|
@ -190,6 +197,10 @@
|
|||
[wrapper message
|
||||
[message-view message [message-content-command message]]])
|
||||
|
||||
(defmethod message-content constants/content-type-placeholder
|
||||
[wrapper message]
|
||||
[wrapper message [placeholder-message message]])
|
||||
|
||||
(defmethod message-content :default
|
||||
[wrapper {:keys [content-type content] :as message}]
|
||||
[wrapper message
|
||||
|
@ -203,7 +214,7 @@
|
|||
:font :default}
|
||||
(i18n/message-status-label status)]])
|
||||
|
||||
(defview group-message-delivery-status [{:keys [message-id group-id current-public-key user-statuses] :as msg}]
|
||||
(defview group-message-delivery-status [{:keys [message-id current-public-key user-statuses] :as msg}]
|
||||
(letsubs [{participants :contacts} [:get-current-chat]
|
||||
contacts [:get-contacts]]
|
||||
(let [outgoing-status (or (get user-statuses current-public-key) :sending)
|
||||
|
@ -250,14 +261,14 @@
|
|||
(->> (string/replace photo-path #"contacts://" "")
|
||||
(keyword)
|
||||
(get resources/contacts))
|
||||
{:uri (if (string/blank? photo-path)
|
||||
(identicon/identicon from)
|
||||
photo-path)})
|
||||
{:uri photo-path})
|
||||
:style style/photo}]])
|
||||
|
||||
(defview member-photo [from]
|
||||
(letsubs [photo-path [:get-photo-path from]]
|
||||
(photo from photo-path)))
|
||||
(photo from (if (string/blank? photo-path)
|
||||
(identicon/identicon from)
|
||||
photo-path))))
|
||||
|
||||
(defview my-photo [from]
|
||||
(letsubs [{:keys [photo-path]} [:get-current-account]]
|
||||
|
@ -286,8 +297,7 @@
|
|||
content]]
|
||||
(when last-outgoing?
|
||||
[react/view style/delivery-status
|
||||
(if (or (= (keyword message-type) :group-user-message)
|
||||
group-chat)
|
||||
(if (= message-type :group-user-message)
|
||||
[group-message-delivery-status message]
|
||||
[message-delivery-status message])])])
|
||||
|
||||
|
@ -296,11 +306,11 @@
|
|||
(let [to-value @to-value]
|
||||
(when (pos? to-value)
|
||||
(animation/start
|
||||
(animation/timing val {:toValue to-value
|
||||
:duration 250})
|
||||
(fn [arg]
|
||||
(when (.-finished arg)
|
||||
(callback))))))))
|
||||
(animation/timing val {:toValue to-value
|
||||
:duration 250})
|
||||
(fn [arg]
|
||||
(when (.-finished arg)
|
||||
(callback))))))))
|
||||
|
||||
(defn message-container [message & children]
|
||||
(if (:appearing? message)
|
||||
|
@ -327,27 +337,14 @@
|
|||
children)])}))
|
||||
(into [react/view] children)))
|
||||
|
||||
(defn chat-message [{:keys [outgoing message-id chat-id from current-public-key] :as message}]
|
||||
(reagent/create-class
|
||||
{:display-name
|
||||
"chat-message"
|
||||
:component-did-mount
|
||||
;; send `:seen` signal when we have signed-in user, message not from us and we didn't sent it already
|
||||
#(when (and current-public-key message-id chat-id (not outgoing)
|
||||
(not (models.message/message-seen-by? message current-public-key)))
|
||||
(re-frame/dispatch [:send-seen! {:chat-id chat-id
|
||||
:from from
|
||||
:me current-public-key
|
||||
:message-id message-id}]))
|
||||
:reagent-render
|
||||
(fn [{:keys [outgoing group-chat content-type content] :as message}]
|
||||
[message-container message
|
||||
[react/touchable-highlight {:on-press #(when platform/ios?
|
||||
(react/dismiss-keyboard!))
|
||||
:on-long-press #(when (= content-type constants/text-content-type)
|
||||
(list-selection/share content (i18n/label :t/message)))}
|
||||
[react/view {:accessibility-label :chat-item}
|
||||
(let [incoming-group (and group-chat (not outgoing))]
|
||||
[message-content message-body (merge message
|
||||
{:current-public-key current-public-key
|
||||
:incoming-group incoming-group})])]]])}))
|
||||
(defn chat-message [{:keys [outgoing group-chat current-public-key content-type content] :as message}]
|
||||
[message-container message
|
||||
[react/touchable-highlight {:on-press #(when platform/ios?
|
||||
(react/dismiss-keyboard!))
|
||||
:on-long-press #(when (= content-type constants/text-content-type)
|
||||
(list-selection/share content (i18n/label :t/message)))}
|
||||
[react/view {:accessibility-label :chat-item}
|
||||
(let [incoming-group (and group-chat (not outgoing))]
|
||||
[message-content message-body (merge message
|
||||
{:current-public-key current-public-key
|
||||
:incoming-group incoming-group})])]]])
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
|
||||
(defview message-content-command-request
|
||||
[{:keys [message-id content] :as message}]
|
||||
(letsubs [command [:get-command (:content-command-ref content)]
|
||||
(letsubs [command [:get-command (:request-command-ref content)]
|
||||
answered? [:is-request-answered? message-id]
|
||||
status-initialized? [:get :status-module-initialized?]]
|
||||
(let [{:keys [prefill prefill-bot-db prefillBotDb params preview]
|
||||
|
|
|
@ -8,16 +8,9 @@
|
|||
[status-im.utils.utils :as utils]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.data-store.local-storage :as local-storage]
|
||||
[status-im.bots.events :as bots-events]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
;; COFX
|
||||
(re-frame/reg-cofx
|
||||
:get-local-storage-data
|
||||
(fn [cofx]
|
||||
(assoc cofx :get-local-storage-data local-storage/get-data)))
|
||||
|
||||
;; FX
|
||||
(re-frame/reg-fx
|
||||
::evaluate-jail-n
|
||||
|
@ -26,9 +19,9 @@
|
|||
(status/parse-jail
|
||||
jail-id jail-resource
|
||||
(fn [jail-response]
|
||||
(let [converted (types/json->clj jail-response)]
|
||||
(let [converted (types/json->clj jail-response)]
|
||||
(re-frame/dispatch [::proceed-loading jail-id (if config/jsc-enabled?
|
||||
(update converted :result types/json->clj)
|
||||
(update converted :result types/json->clj)
|
||||
converted)])))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
@ -79,7 +72,7 @@
|
|||
(reduce conj
|
||||
(disj scopes-set scope)
|
||||
(map (partial conj (s/difference scope exclusive-match))
|
||||
exclusive-match))
|
||||
exclusive-match))
|
||||
scopes-set)))
|
||||
scopes-set
|
||||
scopes-set))
|
||||
|
@ -138,7 +131,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
::evaluate-commands-in-jail
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)]
|
||||
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
|
||||
(fn [cofx [commands-resource whisper-identity]]
|
||||
(evaluate-commands-in-jail cofx commands-resource whisper-identity)))
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.commands.events.loading :as loading-events]
|
||||
[status-im.data-store.accounts :as accounts]
|
||||
[status-im.data-store.messages :as messages]
|
||||
[status-im.data-store.accounts :as accounts]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.types :as types]
|
||||
|
@ -69,14 +69,14 @@
|
|||
[{:keys [chats]} {:keys [whisper-identity]}]
|
||||
(if (get chats whisper-identity)
|
||||
(if (get-in chats [whisper-identity :debug?])
|
||||
(do (re-frame/dispatch [:remove-chat whisper-identity])
|
||||
(do (re-frame/dispatch [:remove-chat-and-navigate-home whisper-identity])
|
||||
(respond {:type :ok
|
||||
:text "The DApp or bot has been removed."}))
|
||||
(respond {:type :error
|
||||
:text "Your DApp or bot should be debuggable."}))
|
||||
(respond {:type :error
|
||||
:text "There is no such DApp or bot."}))
|
||||
(re-frame/dispatch [:remove-contact whisper-identity #(and (:dapp? %) (:debug? %))]))
|
||||
(re-frame/dispatch [:remove-contact whisper-identity]))
|
||||
|
||||
(defn contact-changed
|
||||
[{:keys [webview-bridge current-chat-id]
|
||||
|
@ -177,7 +177,6 @@
|
|||
;; TODO(janherich) once `contact-changed` fn is refactored, get rid of this unnecessary event
|
||||
(handlers/register-handler-fx
|
||||
::load-commands
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)]
|
||||
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
|
||||
(fn [cofx [contact]]
|
||||
(loading-events/load-commands cofx {} contact)))
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
[status-im.commands.utils :refer [reg-handler]]
|
||||
[status-im.constants :refer [console-chat-id]]
|
||||
[status-im.i18n :refer [get-contact-translated]]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.data-store.local-storage :as local-storage]))
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn command-handler!
|
||||
[_ [chat-id
|
||||
|
@ -47,7 +46,7 @@
|
|||
|
||||
(defn suggestions-events-handler!
|
||||
[{:keys [bot-db]} [bot-id [n & data] val]]
|
||||
(log/debug "Suggestion event: " n (first data) val)
|
||||
(log/debug "Suggestion event: " n (first data) val)
|
||||
(case (keyword n)
|
||||
:set-command-argument
|
||||
(let [[index value move-to-next?] (first data)]
|
||||
|
@ -102,8 +101,9 @@
|
|||
{:result result
|
||||
:chat-id chat-id}])))))
|
||||
|
||||
(reg-handler :set-local-storage
|
||||
(handlers/side-effect!
|
||||
(fn [_ [{:keys [data chat-id]}]]
|
||||
(local-storage/set-data {:chat-id chat-id
|
||||
:data data}))))
|
||||
(handlers/register-handler-fx
|
||||
:set-local-storage
|
||||
[trim-v]
|
||||
(fn [_ [{:keys [data chat-id]}]]
|
||||
{:data-store/set-local-storage-data {:chat-id chat-id
|
||||
:data data}}))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
(def content-type-command "command")
|
||||
(def content-type-command-request "command-request")
|
||||
(def content-type-status "status")
|
||||
(def content-type-placeholder "placeholder")
|
||||
|
||||
(def min-password-length 6)
|
||||
(def max-chat-name-length 20)
|
||||
|
@ -28,6 +29,8 @@
|
|||
|
||||
(def contract-address "0x0000000000000000000000000000000000000000")
|
||||
|
||||
(def system "system")
|
||||
|
||||
(def default-wallet-transactions
|
||||
{:filters
|
||||
{:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true}
|
||||
|
@ -82,6 +85,7 @@
|
|||
:DataDir "/ethereum/rinkeby_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://rinkeby.infura.io/z6GCTmjdP3FETEJmMBI4"}}}})
|
||||
|
||||
(def default-networks
|
||||
(transform-config
|
||||
(merge testnet-networks
|
||||
|
@ -102,6 +106,9 @@
|
|||
(def inbox-topic "0xaabb11ee")
|
||||
(def inbox-password "status-offline-inbox")
|
||||
|
||||
;; Used to generate topic for contact discoveries
|
||||
(def contact-discovery "contact-discovery")
|
||||
|
||||
(def ^:const send-transaction-no-error-code "0")
|
||||
(def ^:const send-transaction-default-error-code "1")
|
||||
(def ^:const send-transaction-password-error-code "2")
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
(ns status-im.data-store.accounts
|
||||
(:require [status-im.data-store.realm.accounts :as data-store]))
|
||||
|
||||
(defn get-all []
|
||||
(data-store/get-all-as-list))
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.accounts :as data-store]))
|
||||
|
||||
;; TODO janherich: define as cofx once debug handlers are refactored
|
||||
(defn get-by-address [address]
|
||||
(data-store/get-by-address address))
|
||||
|
||||
(defn save [account update?]
|
||||
(data-store/save account update?))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-all-accounts
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :all-accounts (data-store/get-all-as-list))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-account
|
||||
(fn [{:keys [after-update-event] :as account}]
|
||||
(let [account-to-save (dissoc account :after-update-event)]
|
||||
(async/go (async/>! core/realm-queue #(if after-update-event
|
||||
(do (data-store/save account-to-save true)
|
||||
(re-frame/dispatch after-update-event))
|
||||
(data-store/save account-to-save true)))))))
|
||||
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
(ns status-im.data-store.browser
|
||||
(:require [status-im.data-store.realm.browser :as data-store])
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.browser :as data-store])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(data-store/get-all))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/all-browsers
|
||||
(fn [cofx _]
|
||||
(assoc cofx :all-stored-browsers (data-store/get-all))))
|
||||
|
||||
(defn get-by-id
|
||||
[id]
|
||||
(data-store/get-by-id id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-browser
|
||||
(fn [{:keys [browser-id] :as browser}]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save browser (data-store/exists? browser-id))))))
|
||||
|
||||
(defn exists?
|
||||
[browser-id]
|
||||
(data-store/exists? browser-id))
|
||||
|
||||
(defn save
|
||||
[{:keys [browser-id] :as browser}]
|
||||
(data-store/save browser (exists? browser-id)))
|
||||
|
||||
(defn delete
|
||||
[browser-id]
|
||||
(data-store/delete browser-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/remove-browser
|
||||
(fn [browser-id]
|
||||
(async/go (async/>! core/realm-queue #(data-store/delete browser-id)))))
|
||||
|
|
|
@ -1,63 +1,51 @@
|
|||
(ns status-im.data-store.chats
|
||||
(:require [status-im.data-store.realm.chats :as data-store])
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.chats :as data-store])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(data-store/get-all-active))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/all-chats
|
||||
(fn [cofx _]
|
||||
(assoc cofx :all-stored-chats (data-store/get-all-active))))
|
||||
|
||||
(defn get-inactive-ids
|
||||
[]
|
||||
(data-store/get-inactive-ids))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/inactive-chat-ids
|
||||
(fn [cofx _]
|
||||
(assoc cofx :inactive-chat-ids (data-store/get-inactive-ids))))
|
||||
|
||||
(defn get-by-id
|
||||
[id]
|
||||
(data-store/get-by-id id))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-chat
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-chat data-store/get-by-id)))
|
||||
|
||||
(defn exists?
|
||||
[chat-id]
|
||||
(data-store/exists? chat-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-chat
|
||||
(fn [{:keys [chat-id] :as chat}]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save chat (data-store/exists? chat-id))))))
|
||||
|
||||
(defn save
|
||||
[{:keys [chat-id] :as chat}]
|
||||
(data-store/save chat (data-store/exists? chat-id)))
|
||||
(re-frame/reg-fx
|
||||
:data-store/delete-chat
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! core/realm-queue #(data-store/delete chat-id)))))
|
||||
|
||||
(defn delete
|
||||
[chat-id]
|
||||
(data-store/delete chat-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/deactivate-chat
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! core/realm-queue #(data-store/set-inactive chat-id)))))
|
||||
|
||||
(defn set-inactive
|
||||
[chat-id]
|
||||
(data-store/set-inactive chat-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/add-chat-contacts
|
||||
(fn [[chat-id contacts]]
|
||||
(async/go (async/>! core/realm-queue #(data-store/add-contacts chat-id contacts)))))
|
||||
|
||||
(defn get-contacts
|
||||
[chat-id]
|
||||
(data-store/get-contacts chat-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/remove-chat-contacts
|
||||
(fn [[chat-id contacts]]
|
||||
(async/go (async/>! core/realm-queue #(data-store/remove-contacts chat-id contacts)))))
|
||||
|
||||
(defn add-contacts
|
||||
[chat-id identities]
|
||||
(data-store/add-contacts chat-id identities))
|
||||
|
||||
(defn remove-contacts
|
||||
[chat-id identities]
|
||||
(data-store/remove-contacts chat-id identities))
|
||||
|
||||
(defn save-property
|
||||
[chat-id property-name value]
|
||||
(data-store/save-property chat-id property-name value))
|
||||
|
||||
(defn get-property
|
||||
[chat-id property-name]
|
||||
(data-store/get-property chat-id property-name))
|
||||
|
||||
(defn removed-at
|
||||
[chat-id]
|
||||
(get-property chat-id :removed-at))
|
||||
|
||||
(defn get-active-group-chats
|
||||
[]
|
||||
(data-store/get-active-group-chats))
|
||||
|
||||
(defn set-active
|
||||
[chat-id active?]
|
||||
(save-property chat-id :is-active active?))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-chat-property
|
||||
(fn [[chat-id prop value]]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save-property chat-id prop value)))))
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
(ns status-im.data-store.contact-groups
|
||||
(:require [status-im.data-store.realm.contact-groups :as data-store])
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.contact-groups :as data-store])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn- normalize-contacts
|
||||
[item]
|
||||
(update item :contacts vals))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(map normalize-contacts (data-store/get-all-as-list)))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-all-contact-groups
|
||||
(fn [cofx _]
|
||||
(assoc cofx :all-contact-groups (into {}
|
||||
(map (comp (juxt :group-id identity) normalize-contacts))
|
||||
(data-store/get-all-as-list)))))
|
||||
|
||||
(defn save
|
||||
[{:keys [group-id] :as group}]
|
||||
(data-store/save group (data-store/exists? group-id)))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-contact-group
|
||||
(fn [{:keys [group-id] :as group}]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save group (data-store/exists? group-id))))))
|
||||
|
||||
(defn save-all
|
||||
[groups]
|
||||
(mapv save groups))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-contact-groups
|
||||
(fn [groups]
|
||||
(doseq [{:keys [group-id] :as group} groups]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save group (data-store/exists? group-id)))))))
|
||||
|
||||
(defn save-property
|
||||
[group-id property-name value]
|
||||
(data-store/save-property group-id property-name value))
|
||||
|
||||
(defn delete
|
||||
[group-id]
|
||||
(data-store/delete group-id))
|
||||
|
||||
(defn add-contacts
|
||||
[group-id identities]
|
||||
(data-store/add-contacts group-id identities))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-contact-group-property
|
||||
(fn [[group-id property-name value]]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save-property group-id property-name value)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/add-contacts-to-contact-group
|
||||
(fn [[group-id contacts]]
|
||||
(async/go (async/>! core/realm-queue #(data-store/add-contacts group-id contacts)))))
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
(ns status-im.data-store.contacts
|
||||
(:require [status-im.data-store.realm.contacts :as data-store])
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.contacts :as data-store])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(data-store/get-all-as-list))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-all-contacts
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :all-contacts (data-store/get-all-as-list))))
|
||||
|
||||
(defn get-by-id
|
||||
(defn- get-by-id
|
||||
[whisper-identity]
|
||||
(data-store/get-by-id-cljs whisper-identity))
|
||||
|
||||
(defn save
|
||||
(defn- save
|
||||
[{:keys [whisper-identity pending?] :as contact}]
|
||||
(let [{pending-db? :pending?
|
||||
:as contact-db} (get-by-id whisper-identity)
|
||||
|
@ -21,13 +25,18 @@
|
|||
(dissoc :command :response :subscriptions :jail-loaded-events))]
|
||||
(data-store/save contact' (boolean contact-db))))
|
||||
|
||||
(defn save-all
|
||||
[contacts]
|
||||
(mapv save contacts))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-contact
|
||||
(fn [contact]
|
||||
(async/go (async/>! core/realm-queue #(save contact)))))
|
||||
|
||||
(defn delete [contact]
|
||||
(data-store/delete contact))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-contacts
|
||||
(fn [contacts]
|
||||
(doseq [contact contacts]
|
||||
(async/go (async/>! core/realm-queue #(save contact))))))
|
||||
|
||||
(defn exists?
|
||||
[whisper-identity]
|
||||
(data-store/exists? whisper-identity))
|
||||
(re-frame/reg-fx
|
||||
:data-store/delete-contact
|
||||
(fn [contact]
|
||||
(async/go (async/>! core/realm-queue #(data-store/delete contact)))))
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
(ns status-im.data-store.core
|
||||
(:require [status-im.data-store.realm.core :as data-source]
|
||||
(:require status-im.data-store.chats
|
||||
status-im.data-store.messages
|
||||
status-im.data-store.contacts
|
||||
status-im.data-store.transport
|
||||
status-im.data-store.browser
|
||||
status-im.data-store.accounts
|
||||
status-im.data-store.local-storage
|
||||
status-im.data-store.contact-groups
|
||||
status-im.data-store.requests
|
||||
[status-im.data-store.realm.core :as data-source]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
;; also deletes the oldest queries if the number of discovers stored is
|
||||
;; above maximum-number-of-discoveries
|
||||
(re-frame/reg-fx
|
||||
:data-store.discover/save-all
|
||||
:data-store/save-all-discoveries
|
||||
(fn [[discovers maximum-number-of-discoveries]]
|
||||
(data-store/save-all (mapv #(dissoc % :tags) discovers))
|
||||
(data-store/delete :created-at :asc maximum-number-of-discoveries)))
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
(ns status-im.data-store.local-storage
|
||||
(:require [status-im.data-store.realm.local-storage :as data-store]))
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.local-storage :as data-store]))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-local-storage-data
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-local-storage-data (comp :data data-store/get-by-chat-id))))
|
||||
|
||||
(defn get-data [chat-id]
|
||||
(:data (data-store/get-by-chat-id chat-id)))
|
||||
|
||||
(defn set-data [local-storage]
|
||||
(data-store/save local-storage))
|
||||
(re-frame/reg-fx
|
||||
:data-store/set-local-storage-data
|
||||
(fn [data]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save data)))))
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
(ns status-im.data-store.messages
|
||||
(:refer-clojure :exclude [exists?])
|
||||
(:require [cljs.reader :as reader]
|
||||
[cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.messages :as data-store]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.utils.core :as utils]
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
;; TODO janherich: define as cofx once debug handlers are refactored
|
||||
(defn get-log-messages
|
||||
[chat-id]
|
||||
(->> (data-store/get-by-chat-id chat-id 0 100)
|
||||
(filter #(= (:content-type %) constants/content-type-log-message))
|
||||
(map #(select-keys % [:content :timestamp]))))
|
||||
|
||||
(defn- command-type?
|
||||
[type]
|
||||
(contains?
|
||||
|
@ -17,9 +26,10 @@
|
|||
{:outgoing false
|
||||
:to nil})
|
||||
|
||||
(defn get-by-id
|
||||
[message-id]
|
||||
(data-store/get-by-id message-id))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-message
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-message data-store/get-by-id)))
|
||||
|
||||
(defn get-by-chat-id
|
||||
([chat-id]
|
||||
|
@ -31,22 +41,25 @@
|
|||
(update message :content reader/read-string)
|
||||
message))))))
|
||||
|
||||
(defn get-stored-message-ids
|
||||
[]
|
||||
(data-store/get-stored-message-ids))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-messages
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-messages get-by-chat-id)))
|
||||
|
||||
(defn get-log-messages
|
||||
[chat-id]
|
||||
(->> (data-store/get-by-chat-id chat-id 0 100)
|
||||
(filter #(= (:content-type %) constants/content-type-log-message))
|
||||
(map #(select-keys % [:content :timestamp]))))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/message-ids
|
||||
(fn [cofx _]
|
||||
(assoc cofx :stored-message-ids (data-store/get-stored-message-ids))))
|
||||
|
||||
(defn get-unviewed
|
||||
[current-public-key]
|
||||
(into {}
|
||||
(map (fn [[chat-id user-statuses]]
|
||||
[chat-id (into #{} (map :message-id) user-statuses)]))
|
||||
(group-by :chat-id (data-store/get-unviewed current-public-key))))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/unviewed-messages
|
||||
(fn [{:keys [db] :as cofx} _]
|
||||
(assoc cofx
|
||||
:stored-unviewed-messages
|
||||
(into {}
|
||||
(map (fn [[chat-id user-statuses]]
|
||||
[chat-id (into #{} (map :message-id) user-statuses)]))
|
||||
(group-by :chat-id (data-store/get-unviewed (:current-public-key db)))))))
|
||||
|
||||
(defn- prepare-content [content]
|
||||
(if (string? content)
|
||||
|
@ -79,10 +92,35 @@
|
|||
{:from (or from "anonymous")
|
||||
:timestamp (datetime/timestamp)})))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-message
|
||||
(fn [message]
|
||||
(async/go (async/>! core/realm-queue #(save message)))))
|
||||
|
||||
(defn update-message
|
||||
[{:keys [message-id] :as message}]
|
||||
(when-let [{:keys [chat-id]} (data-store/get-by-id message-id)]
|
||||
(data-store/save (prepare-message (assoc message :chat-id chat-id)))))
|
||||
|
||||
(defn delete-by-chat-id [chat-id]
|
||||
(data-store/delete-by-chat-id chat-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/update-message
|
||||
(fn [message]
|
||||
(async/go (async/>! core/realm-queue #(update-message message)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/update-messages
|
||||
(fn [messages]
|
||||
(doseq [message messages]
|
||||
(async/go (async/>! core/realm-queue #(update-message message))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/delete-messages
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! core/realm-queue #(data-store/delete-by-chat-id chat-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store/hide-messages
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! core/realm-queue #(doseq [message-id (data-store/get-message-ids-by-chat-id chat-id)]
|
||||
(data-store/save {:message-id message-id
|
||||
:show? false}))))))
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
(ns status-im.data-store.networks
|
||||
(:require [status-im.data-store.realm.networks :as data-store]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(data-store/get-all-as-list))
|
||||
|
||||
(defn save
|
||||
[{:keys [id] :as network}]
|
||||
(data-store/save network (data-store/exists? id)))
|
||||
|
||||
(defn save-all
|
||||
[networks]
|
||||
(mapv save networks))
|
||||
|
||||
(defn save-property
|
||||
[id property-name value]
|
||||
(data-store/save-property id property-name value))
|
||||
|
||||
(defn delete
|
||||
[id]
|
||||
(data-store/delete id))
|
|
@ -1,49 +0,0 @@
|
|||
(ns status-im.data-store.pending-messages
|
||||
(:require [status-im.data-store.realm.pending-messages :as data-store]
|
||||
[status-im.utils.hex :as i]))
|
||||
|
||||
(defn- get-id
|
||||
[message-id to]
|
||||
(let [to' (i/normalize-hex to)
|
||||
to'' (when to' (subs to' 0 7))
|
||||
id' (if to''
|
||||
(str message-id "-" (subs to'' 0 7))
|
||||
message-id)]
|
||||
id'))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(data-store/get-all-as-list))
|
||||
|
||||
(defn get-by-chat-id
|
||||
[chat-id]
|
||||
(data-store/get-by-chat-id chat-id))
|
||||
|
||||
(defn get-by-message-id
|
||||
[message-id]
|
||||
(data-store/get-by-message-id message-id))
|
||||
|
||||
(defn save
|
||||
[{:keys [id to group-id message] :as pending-message}]
|
||||
(let [{:keys [sig sym-key-password pubKey topic payload]} message
|
||||
id' (get-id id to)
|
||||
chat-id (or group-id to)
|
||||
message' (-> pending-message
|
||||
(assoc :id id'
|
||||
:sig sig
|
||||
:sym-key-password sym-key-password
|
||||
:pub-key pubKey
|
||||
:message-id id
|
||||
:chat-id chat-id
|
||||
:payload payload
|
||||
:topic topic)
|
||||
(dissoc :message))]
|
||||
(data-store/save message')))
|
||||
|
||||
(defn delete
|
||||
[message-id]
|
||||
(data-store/delete message-id))
|
||||
|
||||
(defn delete-all-by-chat-id
|
||||
[chat-id]
|
||||
(data-store/delete-all-by-chat-id chat-id))
|
|
@ -1,14 +0,0 @@
|
|||
(ns status-im.data-store.processed-messages
|
||||
(:require [status-im.data-store.realm.processed-messages :as data-store])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-filtered
|
||||
[condition]
|
||||
(data-store/get-filtered-as-list condition))
|
||||
|
||||
(defn save
|
||||
[processed-message]
|
||||
(data-store/save processed-message))
|
||||
|
||||
(defn delete [condition]
|
||||
(data-store/delete condition))
|
|
@ -7,10 +7,12 @@
|
|||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn- normalize-chat [{:keys [chat-id] :as chat}]
|
||||
(let [last-message (messages/get-last-message chat-id)]
|
||||
(let [last-to-clock-value (messages/get-last-clock-value chat-id :to-clock-value)
|
||||
last-from-clock-value (messages/get-last-clock-value chat-id :from-clock-value)]
|
||||
(-> chat
|
||||
(realm/fix-map->vec :contacts)
|
||||
(assoc :last-clock-value (or (:clock-value last-message) 0)))))
|
||||
(merge {:last-to-clock-value (or last-to-clock-value 0)
|
||||
:last-from-clock-value (or last-from-clock-value 0)}))))
|
||||
|
||||
(defn get-all-active
|
||||
[]
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[status-im.data-store.realm.schemas.base.core :as base]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.fs :as fs]
|
||||
[status-im.utils.async :as utils.async]
|
||||
[clojure.string :as str]
|
||||
[goog.string :as gstr]
|
||||
[cognitect.transit :as transit]
|
||||
|
@ -43,6 +44,8 @@
|
|||
|
||||
(def account-realm (atom (open-migrated-realm new-account-filename account/schemas)))
|
||||
|
||||
(def realm-queue (utils.async/task-queue 2000))
|
||||
|
||||
(defn close-account-realm []
|
||||
(close @account-realm)
|
||||
(reset! account-realm nil))
|
||||
|
|
|
@ -32,6 +32,12 @@
|
|||
realm/js-object->clj)]
|
||||
(mapv transform-message messages))))
|
||||
|
||||
(defn get-message-ids-by-chat-id
|
||||
[chat-id]
|
||||
(.map (realm/get-by-field @realm/account-realm :message :chat-id chat-id)
|
||||
(fn [msg _ _]
|
||||
(aget msg "message-id"))))
|
||||
|
||||
(defn get-stored-message-ids
|
||||
[]
|
||||
(let [chat-id->message-id (volatile! {})]
|
||||
|
@ -52,11 +58,12 @@
|
|||
(realm/page from (+ from number-of-messages))
|
||||
realm/js-object->clj))
|
||||
|
||||
(defn get-last-message
|
||||
[chat-id]
|
||||
(defn get-last-clock-value
|
||||
[chat-id clock-prop]
|
||||
(-> (realm/get-by-field @realm/account-realm :message :chat-id chat-id)
|
||||
(realm/sorted :clock-value :desc)
|
||||
(realm/single-clj)))
|
||||
(realm/sorted clock-prop :desc)
|
||||
(realm/single-clj)
|
||||
(get clock-prop)))
|
||||
|
||||
(defn get-unviewed
|
||||
[current-public-key]
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
(ns status-im.data-store.realm.networks
|
||||
(:require [status-im.data-store.realm.core :as realm])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(-> @realm/account-realm
|
||||
(realm/get-all :network)))
|
||||
|
||||
(defn get-all-as-list
|
||||
[]
|
||||
(realm/js-object->clj (get-all)))
|
||||
|
||||
(defn save
|
||||
[network update?]
|
||||
(realm/save @realm/account-realm :network network update?))
|
||||
|
||||
(defn save-property
|
||||
[id property-name value]
|
||||
(realm/write @realm/account-realm
|
||||
(fn []
|
||||
(-> @realm/account-realm
|
||||
(realm/get-one-by-field :network :id id)
|
||||
(aset (name property-name) value)))))
|
||||
|
||||
(defn exists?
|
||||
[id]
|
||||
(realm/exists? @realm/account-realm :network {:id id}))
|
||||
|
||||
(defn delete
|
||||
[id]
|
||||
(when-let [network (realm/get-one-by-field @realm/account-realm :network :id id)]
|
||||
(realm/delete @realm/account-realm network)))
|
|
@ -1,31 +0,0 @@
|
|||
(ns status-im.data-store.realm.pending-messages
|
||||
(:require [status-im.data-store.realm.core :as realm]
|
||||
[cljs.reader :refer [read-string]]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(realm/get-all @realm/account-realm :pending-message))
|
||||
|
||||
(defn get-all-as-list
|
||||
[]
|
||||
(realm/js-object->clj (get-all)))
|
||||
|
||||
(defn get-by-message-id
|
||||
[message-id]
|
||||
(realm/get-by-field @realm/account-realm :pending-message :message-id message-id))
|
||||
|
||||
(defn get-by-chat-id
|
||||
[chat-id]
|
||||
(realm/get-by-field @realm/account-realm :pending-message :chat-id chat-id))
|
||||
|
||||
(defn save
|
||||
[pending-message]
|
||||
(realm/save @realm/account-realm :pending-message pending-message true))
|
||||
|
||||
(defn delete
|
||||
[message-id]
|
||||
(realm/delete @realm/account-realm (get-by-message-id message-id)))
|
||||
|
||||
(defn delete-all-by-chat-id
|
||||
[chat-id]
|
||||
(realm/delete @realm/account-realm (get-by-chat-id chat-id)))
|
|
@ -1,25 +0,0 @@
|
|||
(ns status-im.data-store.realm.processed-messages
|
||||
(:require [status-im.data-store.realm.core :as realm])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(-> @realm/account-realm
|
||||
(realm/get-all :processed-message)
|
||||
(realm/sorted :ttl :asc)))
|
||||
|
||||
(defn get-filtered
|
||||
[condition]
|
||||
(realm/filtered (get-all) condition))
|
||||
|
||||
(defn get-filtered-as-list
|
||||
[condition]
|
||||
(realm/js-object->clj (get-filtered condition)))
|
||||
|
||||
(defn save
|
||||
[processed-message]
|
||||
(realm/save @realm/account-realm :processed-message processed-message))
|
||||
|
||||
(defn delete
|
||||
[condition]
|
||||
(realm/delete @realm/account-realm (get-filtered condition)))
|
|
@ -20,7 +20,8 @@
|
|||
[status-im.data-store.realm.schemas.account.v19.core :as v19]
|
||||
[status-im.data-store.realm.schemas.account.v20.core :as v20]
|
||||
[status-im.data-store.realm.schemas.account.v21.core :as v21]
|
||||
[status-im.data-store.realm.schemas.account.v22.core :as v22]))
|
||||
[status-im.data-store.realm.schemas.account.v22.core :as v22]
|
||||
[status-im.data-store.realm.schemas.account.v23.core :as v23]))
|
||||
|
||||
|
||||
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
|
||||
|
@ -91,5 +92,7 @@
|
|||
:migration v21/migration}
|
||||
{:schema v22/schema
|
||||
:schemaVersion 22
|
||||
:migration v22/migration}])
|
||||
|
||||
:migration v22/migration}
|
||||
{:schema v23/schema
|
||||
:schemaVersion 23
|
||||
:migration v23/migration}])
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v22.core
|
||||
(:require [status-im.data-store.realm.schemas.account.v22.chat :as chat]
|
||||
[status-im.data-store.realm.schemas.account.v22.transport :as transport]
|
||||
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
|
||||
[status-im.data-store.realm.schemas.account.v19.contact :as contact]
|
||||
[status-im.data-store.realm.schemas.account.v20.discover :as discover]
|
||||
|
@ -18,6 +19,7 @@
|
|||
|
||||
(def schema [chat/schema
|
||||
chat-contact/schema
|
||||
transport/schema
|
||||
contact/schema
|
||||
discover/schema
|
||||
message/schema
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v22.transport)
|
||||
|
||||
(def schema {:name :transport
|
||||
:primaryKey :chat-id
|
||||
:properties {:chat-id :string
|
||||
:ack :string
|
||||
:seen :string
|
||||
:pending-ack :string
|
||||
:pending-send :string
|
||||
:topic :string
|
||||
:sym-key-id {:type :string
|
||||
:optional true}
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
:sym-key {:type :string
|
||||
:optional true}}})
|
|
@ -0,0 +1,62 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v23.core
|
||||
(:require [status-im.data-store.realm.schemas.account.v22.chat :as chat]
|
||||
[status-im.data-store.realm.schemas.account.v22.transport :as transport]
|
||||
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
|
||||
[status-im.data-store.realm.schemas.account.v19.contact :as contact]
|
||||
[status-im.data-store.realm.schemas.account.v20.discover :as discover]
|
||||
[status-im.data-store.realm.schemas.account.v23.message :as message]
|
||||
[status-im.data-store.realm.schemas.account.v19.request :as request]
|
||||
[status-im.data-store.realm.schemas.account.v19.user-status :as user-status]
|
||||
[status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group]
|
||||
[status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact]
|
||||
[status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage]
|
||||
[status-im.data-store.realm.schemas.account.v21.browser :as browser]
|
||||
[goog.object :as object]
|
||||
[taoensso.timbre :as log]
|
||||
[cljs.reader :as reader]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(def schema [chat/schema
|
||||
chat-contact/schema
|
||||
transport/schema
|
||||
contact/schema
|
||||
discover/schema
|
||||
message/schema
|
||||
request/schema
|
||||
user-status/schema
|
||||
contact-group/schema
|
||||
group-contact/schema
|
||||
local-storage/schema
|
||||
browser/schema])
|
||||
|
||||
(defn update-new-message [new-realm message-id to-clock-value from-clock-value]
|
||||
(when-let [message (some-> new-realm
|
||||
(.objects "message")
|
||||
(.filtered (str "message-id = \"" message-id "\""))
|
||||
(aget 0))]
|
||||
(aset message "to-clock-value" to-clock-value)
|
||||
(aset message "from-clock-value" from-clock-value)))
|
||||
|
||||
(defn update-chat-messages [old-realm new-realm chat-id]
|
||||
(let [from-clock-value (atom 0)
|
||||
to-clock-value (atom 0)]
|
||||
(some-> old-realm
|
||||
(.objects "message")
|
||||
(.filtered (str "chat-id = \"" chat-id "\""))
|
||||
(.sorted "clock-value" false)
|
||||
(.map (fn [message _ _]
|
||||
(let [message-id (object/get message "message-id")
|
||||
outgoing? (boolean (object/get message "outgoing"))]
|
||||
(if outgoing?
|
||||
(update-new-message new-realm message-id (swap! to-clock-value inc) @from-clock-value)
|
||||
(update-new-message new-realm message-id @to-clock-value (swap! from-clock-value inc)))))))))
|
||||
|
||||
(defn update-chats [old-realm new-realm]
|
||||
(some-> new-realm
|
||||
(.objects "chat")
|
||||
(.map (fn [chat _ _]
|
||||
(update-chat-messages old-realm new-realm (object/get chat "chat-id"))))))
|
||||
|
||||
(defn migration [old-realm new-realm]
|
||||
(log/debug "migrating v23 account database: " old-realm new-realm)
|
||||
(update-chats old-realm new-realm))
|
|
@ -0,0 +1,30 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v23.message)
|
||||
|
||||
(def schema {:name :message
|
||||
:primaryKey :message-id
|
||||
:properties {:message-id :string
|
||||
:from :string
|
||||
:to {:type :string
|
||||
:optional true}
|
||||
:content :string ; TODO make it ArrayBuffer
|
||||
:content-type :string
|
||||
:username {:type :string
|
||||
:optional true}
|
||||
:timestamp :int
|
||||
:chat-id {:type :string
|
||||
:indexed true}
|
||||
:outgoing :bool
|
||||
:retry-count {:type :int
|
||||
:default 0}
|
||||
:message-type {:type :string
|
||||
:optional true}
|
||||
:message-status {:type :string
|
||||
:optional true}
|
||||
:user-statuses {:type :list
|
||||
:objectType :user-status}
|
||||
:from-clock-value {:type :int
|
||||
:default 0}
|
||||
:to-clock-value {:type :int
|
||||
:default 0}
|
||||
:show? {:type :bool
|
||||
:default true}}})
|
|
@ -0,0 +1,21 @@
|
|||
(ns status-im.data-store.realm.transport
|
||||
(:require [status-im.data-store.realm.core :as realm])
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defn get-all
|
||||
[]
|
||||
(-> (realm/get-all @realm/account-realm :transport)
|
||||
realm/js-object->clj))
|
||||
|
||||
(defn exists?
|
||||
[chat-id]
|
||||
(realm/exists? @realm/account-realm :transport {:chat-id chat-id}))
|
||||
|
||||
(defn save
|
||||
[{:keys [chat-id] :as chat}]
|
||||
(realm/save @realm/account-realm :transport chat (exists? chat-id)))
|
||||
|
||||
(defn delete
|
||||
[chat-id]
|
||||
(when-let [chat (realm/get-by-field @realm/account-realm :transport :chat-id chat-id)]
|
||||
(realm/delete @realm/account-realm chat)))
|
|
@ -1,14 +1,20 @@
|
|||
(ns status-im.data-store.requests
|
||||
(:require [status-im.data-store.realm.requests :as data-store]))
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.data-store.realm.requests :as data-store]))
|
||||
|
||||
(defn get-all-unanswered
|
||||
[]
|
||||
(data-store/get-all-unanswered))
|
||||
(re-frame/reg-cofx
|
||||
:data-store/get-unanswered-requests
|
||||
(fn [cofx _]
|
||||
(assoc cofx :stored-unanswered-requests (data-store/get-all-unanswered))))
|
||||
|
||||
(defn save
|
||||
[request]
|
||||
(data-store/save request))
|
||||
(re-frame/reg-fx
|
||||
:data-store/save-request
|
||||
(fn [request]
|
||||
(async/go (async/>! core/realm-queue #(data-store/save request)))))
|
||||
|
||||
(defn mark-as-answered
|
||||
[chat-id message-id]
|
||||
(data-store/mark-as-answered chat-id message-id))
|
||||
(re-frame/reg-fx
|
||||
:data-store/mark-request-as-answered
|
||||
(fn [{:keys [chat-id message-id]}]
|
||||
(async/go (async/>! core/realm-queue #(data-store/mark-as-answered chat-id message-id)))))
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
(ns status-im.data-store.transport
|
||||
(:require [cljs.tools.reader.edn :as edn]
|
||||
[cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.transport :as data-store]
|
||||
[status-im.data-store.realm.core :as core]))
|
||||
|
||||
|
||||
(defn deserialize-chat [serialized-chat]
|
||||
(-> serialized-chat
|
||||
(dissoc :chat-id)
|
||||
(update :ack edn/read-string)
|
||||
(update :seen edn/read-string)
|
||||
(update :pending-ack edn/read-string)
|
||||
(update :pending-send edn/read-string)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:data-store/transport
|
||||
(fn [cofx _]
|
||||
(assoc cofx
|
||||
:data-store/transport
|
||||
(reduce (fn [acc {:keys [chat-id] :as chat}]
|
||||
(assoc acc chat-id (deserialize-chat chat)))
|
||||
{}
|
||||
(data-store/get-all)))))
|
||||
|
||||
(defn save [chat-id chat]
|
||||
(let [serialized-chat (-> chat
|
||||
(assoc :chat-id chat-id)
|
||||
(update :ack pr-str)
|
||||
(update :seen pr-str)
|
||||
(update :pending-ack pr-str)
|
||||
(update :pending-send pr-str))]
|
||||
(data-store/save serialized-chat)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store.transport/save
|
||||
(fn [{:keys [chat-id chat]}]
|
||||
(async/go (async/>! core/realm-queue #(save chat-id chat)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:data-store.transport/delete
|
||||
(fn [chat-id]
|
||||
(async/go (async/>! core/realm-queue #(data-store/delete chat-id)))))
|
|
@ -1,24 +0,0 @@
|
|||
(ns status-im.protocol.ack
|
||||
(:require [status-im.protocol.web3.delivery :as d]
|
||||
[status-im.protocol.web3.filtering :as f]
|
||||
[status-im.utils.random :as random]))
|
||||
|
||||
(defn check-ack!
|
||||
[web3
|
||||
from
|
||||
{:keys [type requires-ack? message-id ack? group-id ack-of-message]}
|
||||
identity]
|
||||
(when (and requires-ack? (not ack?))
|
||||
(let [message {:from identity
|
||||
:to from
|
||||
:message-id (random/id)
|
||||
:topics [f/status-topic]
|
||||
:type type
|
||||
:ack? true
|
||||
:payload {:type type
|
||||
:ack? true
|
||||
:ack-of-message message-id
|
||||
:group-id group-id}}]
|
||||
(d/add-pending-message! web3 message)))
|
||||
(when ack?
|
||||
(d/remove-pending-message! web3 ack-of-message from)))
|
|
@ -1,43 +0,0 @@
|
|||
(ns status-im.protocol.chat
|
||||
(:require [cljs.spec.alpha :as s]
|
||||
[status-im.protocol.web3.filtering :as f]
|
||||
[status-im.protocol.web3.delivery :as d]
|
||||
[taoensso.timbre :refer-macros [debug] :as log]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]))
|
||||
|
||||
(def message-defaults
|
||||
{:topics [f/status-topic]})
|
||||
|
||||
(s/def ::timestamp int?)
|
||||
(s/def ::user-message
|
||||
(s/merge
|
||||
:protocol/message
|
||||
(s/keys :req-un [:message/to :chat-message/payload])))
|
||||
|
||||
(defn send!
|
||||
[{:keys [web3 message]}]
|
||||
{:pre [(valid? ::user-message message)]}
|
||||
(let [topics (f/get-topics (:to message))
|
||||
message' (assoc message
|
||||
:topics topics
|
||||
:type :message
|
||||
:requires-ack? true)]
|
||||
(debug :send-user-message message')
|
||||
(d/add-pending-message! web3 message')))
|
||||
|
||||
(s/def ::seen-message
|
||||
(s/merge :protocol/message (s/keys :req-un [:message/to])))
|
||||
|
||||
(defn send-seen!
|
||||
[{:keys [web3 message]}]
|
||||
{:pre [(valid? ::seen-message message)]}
|
||||
(debug :send-seen message)
|
||||
(d/add-pending-message!
|
||||
web3
|
||||
(merge message-defaults
|
||||
(-> message
|
||||
(assoc
|
||||
:type :seen
|
||||
:requires-ack? false)
|
||||
(assoc-in [:payload :group-id] (:group-id message))
|
||||
(dissoc :group-id)))))
|
|
@ -1,125 +0,0 @@
|
|||
(ns status-im.protocol.core
|
||||
(:require status-im.protocol.message
|
||||
[status-im.protocol.web3.utils :as u]
|
||||
[status-im.protocol.web3.filtering :as f]
|
||||
[status-im.protocol.web3.delivery :as d]
|
||||
[status-im.protocol.web3.inbox :as inbox]
|
||||
[taoensso.timbre :refer-macros [debug] :as log]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]
|
||||
[status-im.protocol.web3.keys :as shh-keys]
|
||||
[status-im.protocol.chat :as chat]
|
||||
[status-im.protocol.group :as group]
|
||||
[status-im.protocol.listeners :as l]
|
||||
[status-im.protocol.encryption :as e]
|
||||
[status-im.protocol.discoveries :as discoveries]
|
||||
[cljs.spec.alpha :as s]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.utils.random :as random]))
|
||||
|
||||
;; user
|
||||
(def send-message! chat/send!)
|
||||
(def send-seen! chat/send-seen!)
|
||||
(def reset-pending-messages! d/reset-pending-messages!)
|
||||
|
||||
;; group
|
||||
(def start-watching-group! group/start-watching-group!)
|
||||
(def stop-watching-group! group/stop-watching-group!)
|
||||
(def send-group-message! group/send!)
|
||||
(def send-public-group-message! group/send-to-public-group!)
|
||||
(def invite-to-group! group/invite!)
|
||||
(def update-group! group/update-group!)
|
||||
(def remove-from-group! group/remove-identity!)
|
||||
(def add-to-group! group/add-identity!)
|
||||
(def leave-group-chat! group/leave!)
|
||||
|
||||
;; encryption
|
||||
;; todo move somewhere, encryption functions shouldn't be there
|
||||
(def new-keypair! e/new-keypair!)
|
||||
|
||||
;; discoveries
|
||||
(def watch-user! discoveries/watch-user!)
|
||||
(def stop-watching-user! discoveries/stop-watching-user!)
|
||||
(def contact-request! discoveries/contact-request!)
|
||||
(def broadcast-profile! discoveries/broadcast-profile!)
|
||||
(def send-status! discoveries/send-status!)
|
||||
(def send-discoveries-request! discoveries/send-discoveries-request!)
|
||||
(def send-discoveries-response! discoveries/send-discoveries-response!)
|
||||
(def update-keys! discoveries/update-keys!)
|
||||
|
||||
(def message-pending? d/message-pending?)
|
||||
|
||||
;; initialization
|
||||
(s/def ::identity string?)
|
||||
(s/def :message/chat-id string?)
|
||||
(s/def ::public? (s/and boolean? true?))
|
||||
(s/def ::group-id :message/chat-id)
|
||||
(s/def ::group (s/or
|
||||
:group (s/keys :req-un [::group-id :message/keypair])
|
||||
:public-group (s/keys :req-un [::group-id ::public?])))
|
||||
(s/def ::groups (s/* ::group))
|
||||
(s/def ::callback fn?)
|
||||
(s/def ::contact (s/keys :req-un [::identity :message/keypair]))
|
||||
(s/def ::contacts (s/* ::contact))
|
||||
(s/def ::profile-keypair :message/keypair)
|
||||
(s/def ::options
|
||||
(s/merge
|
||||
(s/keys :req-un [::identity ::groups ::profile-keypair
|
||||
::callback :discoveries/hashtags ::contacts])
|
||||
::d/delivery-options))
|
||||
|
||||
(def stop-watching-all! f/remove-all-filters!)
|
||||
(def reset-all-pending-messages! d/reset-all-pending-messages!)
|
||||
(def reset-keys! shh-keys/reset-keys!)
|
||||
|
||||
(defn stop-whisper! []
|
||||
(stop-watching-all!)
|
||||
(reset-all-pending-messages!)
|
||||
(reset-keys!))
|
||||
|
||||
(defn init-whisper!
|
||||
[{:keys [identity groups callback web3
|
||||
contacts profile-keypair pending-messages]
|
||||
:as options}]
|
||||
{:pre [(valid? ::options options)]}
|
||||
(debug :init-whisper)
|
||||
(stop-whisper!)
|
||||
(let [listener-options {:web3 web3
|
||||
:identity identity
|
||||
:callback callback}]
|
||||
;; start listening to groups
|
||||
(doseq [group groups]
|
||||
(let [options (merge listener-options group)]
|
||||
(group/start-watching-group! options)))
|
||||
;; start listening to user's inbox
|
||||
(if config/offline-inbox-enabled?
|
||||
(do (log/info "offline inbox: flag enabled")
|
||||
(f/add-filter!
|
||||
web3
|
||||
{:key identity
|
||||
:allowP2P true
|
||||
:topics (f/get-topics identity)}
|
||||
(l/message-listener listener-options))
|
||||
(inbox/initialize! web3))
|
||||
(f/add-filter!
|
||||
web3
|
||||
{:key identity
|
||||
:topics (f/get-topics identity)}
|
||||
(l/message-listener listener-options)))
|
||||
|
||||
;; start listening to profiles
|
||||
(doseq [{:keys [identity keypair]} contacts]
|
||||
(watch-user! {:web3 web3
|
||||
:identity identity
|
||||
:keypair keypair
|
||||
:callback callback}))
|
||||
(d/set-pending-mesage-callback! callback)
|
||||
(let [online-message #(discoveries/send-online!
|
||||
{:web3 web3
|
||||
:message {:from identity
|
||||
:message-id (random/id)
|
||||
:keypair profile-keypair}})]
|
||||
(d/run-delivery-loop!
|
||||
web3
|
||||
(assoc options :online-message online-message)))
|
||||
(doseq [pending-message pending-messages]
|
||||
(d/add-prepared-pending-message! web3 pending-message))))
|
|
@ -1,182 +0,0 @@
|
|||
(ns status-im.protocol.discoveries
|
||||
(:require
|
||||
[taoensso.timbre :refer-macros [debug]]
|
||||
[status-im.protocol.web3.utils :as u]
|
||||
[status-im.protocol.web3.delivery :as d]
|
||||
[status-im.protocol.web3.filtering :as f]
|
||||
[status-im.protocol.listeners :as l]
|
||||
[cljs.spec.alpha :as s]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.protocol.web3.keys :as shh-keys]
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
(def discover-topic-prefix "status-discover-")
|
||||
(def discover-topic "0xbeefdead")
|
||||
|
||||
(defn- make-discover-topic [identity]
|
||||
(str discover-topic-prefix identity))
|
||||
|
||||
(s/def :send-online/message
|
||||
(s/merge :protocol/message
|
||||
(s/keys :req-un [:message/keypair])))
|
||||
(s/def :send-online/options
|
||||
(s/keys :req-un [:options/web3 :send-online/message]))
|
||||
|
||||
(def discovery-key-password "status-discovery")
|
||||
|
||||
(defn send-online!
|
||||
[{:keys [web3 message] :as options}]
|
||||
{:pre [(valid? :send-online/options options)]}
|
||||
(debug :send-online)
|
||||
(let [message' (merge
|
||||
message
|
||||
{:requires-ack? false
|
||||
:type :online
|
||||
:key-password discovery-key-password
|
||||
:payload {:content {:timestamp (datetime/timestamp)}}
|
||||
:topics [f/status-topic]})]
|
||||
(d/add-pending-message! web3 message')))
|
||||
|
||||
(s/def ::identity :message/from)
|
||||
(s/def :watch-user/options
|
||||
(s/keys :req-un [:options/web3 :message/keypair ::identity ::callback]))
|
||||
|
||||
(defn watch-user!
|
||||
[{:keys [web3 identity] :as options}]
|
||||
{:pre [(valid? :watch-user/options options)]}
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
discovery-key-password
|
||||
(fn [key-id]
|
||||
(f/add-filter!
|
||||
web3
|
||||
{:sig identity
|
||||
:topics [f/status-topic]
|
||||
:key key-id
|
||||
:type :sym}
|
||||
(l/message-listener (dissoc options :identity))))))
|
||||
|
||||
(defn stop-watching-user!
|
||||
[{:keys [web3 identity]}]
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
discovery-key-password
|
||||
(fn [key-id]
|
||||
(f/remove-filter!
|
||||
web3
|
||||
{:sig identity
|
||||
:topics [f/status-topic]
|
||||
:key key-id
|
||||
:type :sym}))))
|
||||
|
||||
(s/def :contact-request/contact map?)
|
||||
|
||||
(s/def :contact-request/payload
|
||||
(s/merge :message/payload
|
||||
(s/keys :req-un [:contact-request/contact :message/keypair])))
|
||||
|
||||
(s/def :contact-request/message
|
||||
(s/merge :protocol/message
|
||||
(s/keys :req-un [:message/to :contact-request/payload])))
|
||||
|
||||
(defn contact-request!
|
||||
[{:keys [web3 message]}]
|
||||
{:pre [(valid? :contact-request/message message)]}
|
||||
(debug :send-command-request!)
|
||||
(d/add-pending-message!
|
||||
web3
|
||||
(assoc message
|
||||
:type :contact-request
|
||||
:requires-ack? true
|
||||
:topics [f/status-topic])))
|
||||
|
||||
(s/def :discoveries/hashtags (s/every string? :kind-of set?))
|
||||
|
||||
(s/def ::callback fn?)
|
||||
(s/def :watch-hashtags/options
|
||||
(s/keys :req-un [:options/web3 :discoveries/hashtags ::callback]))
|
||||
|
||||
(s/def ::status (s/nilable string?))
|
||||
(s/def ::profile (s/keys :req-un [::status]))
|
||||
(s/def :profile/payload
|
||||
(s/merge :message/payload (s/keys :req-un [::profile])))
|
||||
(s/def :profile/message
|
||||
(s/merge :protocol/message (s/keys :req-un [:message/keypair
|
||||
:profile/payload])))
|
||||
(s/def :broadcast-profile/options
|
||||
(s/keys :req-un [:profile/message :options/web3]))
|
||||
|
||||
(defn broadcast-profile!
|
||||
[{:keys [web3 message] :as options}]
|
||||
{:pre [(valid? :broadcast-profile/options options)]}
|
||||
(debug :broadcasting-status)
|
||||
(d/add-pending-message!
|
||||
web3
|
||||
(-> message
|
||||
(assoc :type :profile
|
||||
:topics [f/status-topic]
|
||||
:key-password discovery-key-password)
|
||||
(assoc-in [:payload :timestamp] (datetime/timestamp))
|
||||
(assoc-in [:payload :content :profile]
|
||||
(get-in message [:payload :profile]))
|
||||
(update :payload dissoc :profile))))
|
||||
|
||||
(s/def ::public string?)
|
||||
(s/def ::private string?)
|
||||
(s/def ::keypair (s/keys :req-un [::public ::private]))
|
||||
(s/def :update-keys/payload
|
||||
(s/keys :req-un [::keypair]))
|
||||
(s/def :update-keys/message
|
||||
(s/merge :protocol/message (s/keys :req-un [:update-keys/payload])))
|
||||
(s/def :update-keys/options
|
||||
(s/keys :req-un [:update-keys/message :options/web3]))
|
||||
|
||||
(defn update-keys!
|
||||
[{:keys [web3 message] :as options}]
|
||||
{:pre [(valid? :update-keys/options options)]}
|
||||
(let [message (-> message
|
||||
(assoc :type :update-keys
|
||||
:requires-ack? false
|
||||
:key-password discovery-key-password
|
||||
:topics [f/status-topic])
|
||||
(assoc-in [:payload :timestamp] (datetime/timestamp)))]
|
||||
(d/add-pending-message! web3 message)))
|
||||
|
||||
(s/def :status/payload
|
||||
(s/merge :message/payload (s/keys :req-un [::status])))
|
||||
(s/def :status/message
|
||||
(s/merge :protocol/message (s/keys :req-un [:status/payload])))
|
||||
(s/def :broadcast-hasthags/options
|
||||
(s/keys :req-un [:discoveries/hashtags :status/message :options/web3]))
|
||||
|
||||
(defn send-status!
|
||||
[{:keys [web3 message]}]
|
||||
(debug :broadcasting-status)
|
||||
(let [message (assoc message :type :discover
|
||||
:key-password discovery-key-password
|
||||
:topics [f/status-topic])]
|
||||
(d/add-pending-message! web3 message)))
|
||||
|
||||
(defn send-discoveries-request!
|
||||
[{:keys [web3 message]}]
|
||||
(debug :sending-discoveries-request)
|
||||
(d/add-pending-message!
|
||||
web3
|
||||
(assoc message :type :discoveries-request
|
||||
:key-password discovery-key-password
|
||||
:topics [f/status-topic])))
|
||||
|
||||
(defn send-discoveries-response!
|
||||
[{:keys [web3 discoveries message]}]
|
||||
(debug :sending-discoveries-response)
|
||||
(doseq [portion (->> discoveries
|
||||
(take 100)
|
||||
(partition 10 10 nil))]
|
||||
(d/add-pending-message!
|
||||
web3
|
||||
(assoc message :type :discoveries-response
|
||||
:key-password discovery-key-password
|
||||
:topics [f/status-topic]
|
||||
:message-id (random/id)
|
||||
:payload {:data (into [] portion)}))))
|
|
@ -1,21 +0,0 @@
|
|||
(ns status-im.protocol.encryption
|
||||
(:require [status-im.js-dependencies :as dependencies]))
|
||||
|
||||
(def default-curve 384)
|
||||
|
||||
(defn new-keypair!
|
||||
"Returns {:private \"private key\" :public \"public key\""
|
||||
[]
|
||||
(let [{:keys [enc dec]}
|
||||
(-> dependencies/eccjs
|
||||
(.generate (.-ENC_DEC dependencies/eccjs) default-curve)
|
||||
(js->clj :keywordize-keys true))]
|
||||
{:private dec
|
||||
:public enc}))
|
||||
|
||||
(defn encrypt [public-key content]
|
||||
(.encrypt dependencies/eccjs public-key content))
|
||||
|
||||
(defn decrypt [private-key content]
|
||||
(.decrypt dependencies/eccjs private-key content))
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
(ns status-im.protocol.group
|
||||
(:require
|
||||
[status-im.protocol.web3.delivery :as d]
|
||||
[status-im.protocol.web3.utils :as u]
|
||||
[status-im.utils.config :as config]
|
||||
[cljs.spec.alpha :as s]
|
||||
[taoensso.timbre :refer-macros [debug]]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]
|
||||
[status-im.protocol.web3.filtering :as f]
|
||||
[status-im.protocol.listeners :as l]
|
||||
[clojure.string :as str]
|
||||
[status-im.protocol.web3.keys :as shh-keys]
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
(defn prepare-mesage
|
||||
[{:keys [message group-id keypair new-keypair type username requires-ack?]}]
|
||||
(let [message' (-> message
|
||||
(update :payload assoc
|
||||
:username username
|
||||
:group-id group-id
|
||||
:type type
|
||||
:timestamp (datetime/timestamp))
|
||||
(assoc :topics [f/status-topic]
|
||||
:key-password group-id
|
||||
:requires-ack? (or (nil? requires-ack?) requires-ack?)
|
||||
:type type))]
|
||||
(cond-> message'
|
||||
keypair (assoc :keypair keypair)
|
||||
new-keypair (assoc :new-keypair keypair))))
|
||||
|
||||
(defn- send-group-message!
|
||||
[{:keys [web3 group-id] :as opts} type]
|
||||
(let [message (-> opts
|
||||
(assoc :type type
|
||||
:key-password group-id)
|
||||
(prepare-mesage))]
|
||||
(debug :send-group-message message)
|
||||
(d/add-pending-message! web3 message)))
|
||||
|
||||
(s/def ::message
|
||||
(s/merge :protocol/message (s/keys :req-un [:chat-message/payload])))
|
||||
|
||||
(s/def :public-group/username (s/and string? (complement str/blank?)))
|
||||
(s/def :public-group/message
|
||||
(s/merge ::message (s/keys :username :public-group/username)))
|
||||
|
||||
(defn send!
|
||||
[{:keys [keypair message] :as options}]
|
||||
{:pre [(valid? :message/keypair keypair)
|
||||
(valid? ::message message)]}
|
||||
(send-group-message! options :group-message))
|
||||
|
||||
(defn send-to-public-group!
|
||||
[{:keys [message] :as options}]
|
||||
{:pre [(valid? :public-group/message message)]}
|
||||
(send-group-message! (assoc options :requires-ack? false)
|
||||
:public-group-message))
|
||||
|
||||
(defn leave!
|
||||
[options]
|
||||
(send-group-message! options :leave-group))
|
||||
|
||||
(defn add-identity!
|
||||
[{:keys [identity] :as options}]
|
||||
{:pre [(valid? :message/to identity)]}
|
||||
(let [options' (assoc-in options
|
||||
[:message :payload :identity]
|
||||
identity)]
|
||||
(send-group-message! options' :add-group-identity)))
|
||||
|
||||
(defn remove-identity!
|
||||
[{:keys [identity] :as options}]
|
||||
{:pre [(valid? :message/to identity)]}
|
||||
(let [options' (assoc-in options
|
||||
[:message :payload :identity]
|
||||
identity)]
|
||||
(send-group-message! options' :remove-group-identity)))
|
||||
|
||||
(s/def ::identities (s/* string?))
|
||||
|
||||
(s/def ::name string?)
|
||||
(s/def ::id string?)
|
||||
(s/def ::admin string?)
|
||||
(s/def ::contacts (s/* string?))
|
||||
(s/def ::group
|
||||
(s/keys :req-un
|
||||
[::name ::id ::contacts :message/keypair ::admin]))
|
||||
(s/def :invite/options
|
||||
(s/keys :req-un [:options/web3 :protocol/message ::group ::identities]))
|
||||
|
||||
(defn- notify-about-group!
|
||||
[type {:keys [web3 message identities group]
|
||||
:as options}]
|
||||
{:pre [(valid? :invite/options options)]}
|
||||
(let [{:keys [id admin name keypair contacts]} group
|
||||
message' (-> message
|
||||
(assoc :topics [f/status-topic]
|
||||
:requires-ack? true
|
||||
:type type)
|
||||
(update :payload assoc
|
||||
:timestamp (datetime/timestamp)
|
||||
:group-id id
|
||||
:group-admin admin
|
||||
:group-name name
|
||||
:keypair keypair
|
||||
:contacts contacts
|
||||
:type type))]
|
||||
(doseq [identity identities]
|
||||
(d/add-pending-message! web3 (assoc message' :to identity)))))
|
||||
|
||||
(defn invite!
|
||||
[options]
|
||||
(notify-about-group! :group-invitation options))
|
||||
|
||||
;; todo notify users about keypair change when someone leaves group (from admin)
|
||||
(defn update-group!
|
||||
[options]
|
||||
(notify-about-group! :update-group options))
|
||||
|
||||
(defn stop-watching-group!
|
||||
[{:keys [web3 group-id]}]
|
||||
{:pre [(valid? :message/chat-id group-id)]}
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
group-id
|
||||
(fn [key-id]
|
||||
(f/remove-filter!
|
||||
web3
|
||||
{:topics [f/status-topic]
|
||||
:key key-id
|
||||
:type :sym}))))
|
||||
|
||||
(defn start-watching-group!
|
||||
[{:keys [web3 group-id keypair callback identity]}]
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
group-id
|
||||
(fn [key-id]
|
||||
(f/add-filter!
|
||||
web3
|
||||
(if (and config/offline-inbox-enabled?
|
||||
config/offline-inbox-many-enabled?)
|
||||
{:topics [f/status-topic]
|
||||
:key key-id
|
||||
:allowP2P true
|
||||
:type :sym}
|
||||
{:topics [f/status-topic]
|
||||
:key key-id
|
||||
:type :sym})
|
||||
(l/message-listener {:web3 web3
|
||||
:identity identity
|
||||
:callback callback
|
||||
:keypair keypair})))))
|
|
@ -1,93 +1,21 @@
|
|||
(ns status-im.protocol.handlers
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[cljs.core.async :as async]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.data-store.contacts :as contacts]
|
||||
[status-im.data-store.messages :as messages]
|
||||
[status-im.data-store.pending-messages :as pending-messages]
|
||||
[status-im.data-store.processed-messages :as processed-messages]
|
||||
[status-im.data-store.chats :as chats]
|
||||
[status-im.protocol.core :as protocol]
|
||||
(:require [cljs.core.async :as async]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.utils.async :as async-utils]
|
||||
[status-im.protocol.message-cache :as cache]
|
||||
[status-im.protocol.listeners :as listeners]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.protocol.web3.inbox :as inbox]
|
||||
[status-im.protocol.web3.keys :as web3.keys]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.native-module.core :as status]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.web3-provider :as web3-provider]
|
||||
[status-im.transport.message-cache :as message-cache]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.utils.ethereum.core :as utils]
|
||||
[status-im.utils.config :as config]))
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.web3-provider :as web3-provider]
|
||||
[status-im.transport.core :as transport]))
|
||||
|
||||
;;;; COFX
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::get-web3
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :web3 (web3-provider/make-web3))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::get-chat-groups
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :groups (chats/get-active-group-chats))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::get-pending-messages
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :pending-messages (pending-messages/get-all))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::message-get-by-id
|
||||
(fn [coeffects _]
|
||||
(let [[{{:keys [message-id]} :payload}] (:event coeffects)]
|
||||
(assoc coeffects :message-by-id (messages/get-by-id message-id)))))
|
||||
|
||||
|
||||
;;;; FX
|
||||
|
||||
(def ^:private protocol-realm-queue (async-utils/task-queue 2000))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:stop-whisper
|
||||
(fn [] (protocol/stop-whisper!)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::init-whisper
|
||||
(fn [{:keys [web3 public-key groups updates-public-key updates-private-key status contacts pending-messages]}]
|
||||
(protocol/init-whisper!
|
||||
{:web3 web3
|
||||
:identity public-key
|
||||
:groups groups
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])
|
||||
:ack-not-received-s-interval 125
|
||||
:default-ttl 120
|
||||
:send-online-s-interval 180
|
||||
:ttl-config {:public-group-message 2400}
|
||||
:max-attempts-number 3
|
||||
:delivery-loop-ms-interval 500
|
||||
:profile-keypair {:public updates-public-key
|
||||
:private updates-private-key}
|
||||
:hashtags (mapv name (handlers/get-hashtags status))
|
||||
:pending-messages pending-messages
|
||||
:contacts (keep (fn [{:keys [whisper-identity
|
||||
public-key
|
||||
private-key]}]
|
||||
(when (and public-key private-key)
|
||||
{:identity whisper-identity
|
||||
:keypair {:public public-key
|
||||
:private private-key}}))
|
||||
contacts)
|
||||
:post-error-callback #(re-frame/dispatch [::post-error %])
|
||||
:pow-target config/pow-target
|
||||
:pow-time config/pow-time})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::web3-get-syncing
|
||||
(fn [web3]
|
||||
|
@ -97,314 +25,24 @@
|
|||
(fn [error sync]
|
||||
(re-frame/dispatch [:update-sync-state error sync]))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::save-processed-messages
|
||||
(fn [processed-message]
|
||||
(async/go (async/>! protocol-realm-queue #(processed-messages/save processed-message)))))
|
||||
|
||||
(defn system-message [message-id timestamp content]
|
||||
{:from "system"
|
||||
:message-id message-id
|
||||
:timestamp timestamp
|
||||
:content content
|
||||
:content-type constants/text-content-type})
|
||||
|
||||
(re-frame/reg-fx
|
||||
::participant-removed-from-group-message
|
||||
(fn [{:keys [identity from message-id timestamp group-id contacts]}]
|
||||
(let [remover-name (get-in contacts [from :name])
|
||||
removed-name (get-in contacts [identity :name])
|
||||
message (->> [(or remover-name from) (i18n/label :t/removed) (or removed-name identity)]
|
||||
(string/join " ")
|
||||
(system-message message-id timestamp))
|
||||
message' (assoc message :group-id group-id)]
|
||||
(re-frame/dispatch [:chat-received-message/add message']))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::chats-add-contact
|
||||
(fn [[group-id identity]]
|
||||
(chats/add-contacts group-id [identity])))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::chats-remove-contact
|
||||
(fn [[group-id identity]]
|
||||
(chats/remove-contacts group-id [identity])))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::you-removed-from-group-message
|
||||
(fn [{:keys [from message-id timestamp group-id contacts]}]
|
||||
(let [remover-name (get-in contacts [from :name])
|
||||
message (->> [(or remover-name from) (i18n/label :t/removed-from-chat)]
|
||||
(string/join " ")
|
||||
(system-message message-id timestamp))
|
||||
message' (assoc message :group-id group-id)]
|
||||
(re-frame/dispatch [:chat-received-message/add message']))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::stop-watching-group!
|
||||
(fn [params]
|
||||
(protocol/stop-watching-group! params)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::participant-left-group-message
|
||||
(fn [{:keys [chat-id from message-id timestamp contacts]}]
|
||||
(let [left-name (get-in contacts [from :name])
|
||||
message-text (str (or left-name from) " " (i18n/label :t/left))]
|
||||
(-> (system-message message-id timestamp message-text)
|
||||
(assoc :chat-id chat-id)
|
||||
(messages/save)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::participant-invited-to-group-message
|
||||
(fn [{:keys [group-id current-identity identity from message-id timestamp contacts]}]
|
||||
(let [inviter-name (get-in contacts [from :name])
|
||||
invitee-name (if (= identity current-identity)
|
||||
(i18n/label :t/You)
|
||||
(get-in contacts [identity :name]))]
|
||||
(re-frame/dispatch
|
||||
[:chat-received-message/add
|
||||
{:from "system"
|
||||
:group-id group-id
|
||||
:timestamp timestamp
|
||||
:message-id message-id
|
||||
:content (str (or inviter-name from) " " (i18n/label :t/invited) " " (or invitee-name identity))
|
||||
:content-type constants/text-content-type}]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::pending-messages-delete
|
||||
(fn [message-id]
|
||||
(async/go (async/>! protocol-realm-queue #(pending-messages/delete message-id)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::pending-messages-save
|
||||
(fn [pending-message]
|
||||
(async/go (async/>! protocol-realm-queue #(pending-messages/save pending-message)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::status-init-jail
|
||||
(fn []
|
||||
(status/init-jail)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::load-processed-messages!
|
||||
(fn []
|
||||
(let [now (datetime/timestamp)
|
||||
messages (processed-messages/get-filtered (str "ttl > " now))]
|
||||
(cache/init! messages)
|
||||
(processed-messages/delete (str "ttl <=" now)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::add-peer
|
||||
(fn [{:keys [wnode web3]}]
|
||||
(inbox/add-peer wnode
|
||||
#(re-frame/dispatch [::add-peer-success web3 %])
|
||||
#(re-frame/dispatch [::add-peer-error %]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::fetch-peers
|
||||
(fn [{:keys [wnode web3 retries]}]
|
||||
;; Run immediately on first run, add delay before retry
|
||||
(let [delay (cond
|
||||
(zero? retries) 0
|
||||
(< retries 3) 300
|
||||
(< retries 10) 1000
|
||||
:else 5000)]
|
||||
(if (> retries 100)
|
||||
(log/error "Number of retries for fetching peers exceed" wnode)
|
||||
(js/setTimeout
|
||||
(fn [] (inbox/fetch-peers #(re-frame/dispatch [::fetch-peers-success web3 % retries])
|
||||
#(re-frame/dispatch [::fetch-peers-error %])))
|
||||
delay)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::mark-trusted-peer
|
||||
(fn [{:keys [wnode web3 peers]}]
|
||||
(inbox/mark-trusted-peer web3
|
||||
wnode
|
||||
peers
|
||||
#(re-frame/dispatch [::mark-trusted-peer-success web3 %])
|
||||
#(re-frame/dispatch [::mark-trusted-peer-error %]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::get-sym-key
|
||||
(fn [{:keys [web3 password]}]
|
||||
(web3.keys/get-sym-key web3
|
||||
password
|
||||
#(re-frame/dispatch [::get-sym-key-success web3 %])
|
||||
#(re-frame/dispatch [::get-sym-key-error %]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::request-messages
|
||||
(fn [{:keys [wnode topic sym-key-id web3]}]
|
||||
(inbox/request-messages web3
|
||||
wnode
|
||||
topic
|
||||
sym-key-id
|
||||
#(re-frame/dispatch [::request-messages-success %])
|
||||
#(re-frame/dispatch [::request-messages-error %]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::handle-whisper-message
|
||||
listeners/handle-whisper-message)
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
|
||||
(defn get-wnode [db]
|
||||
(let [wnode-id (get db :inbox/wnode)]
|
||||
(get-in db [:inbox/wnodes wnode-id :address])))
|
||||
|
||||
(defn connectivity-check [peers {:keys [db] :as cofx}]
|
||||
(let [wnode (get-wnode db)
|
||||
peers-count (count peers)
|
||||
mailserver-connected? (inbox/registered-peer? peers wnode)]
|
||||
{:db (cond-> db
|
||||
mailserver-connected? (dissoc :mailserver-status)
|
||||
(not mailserver-connected?) (assoc :mailserver-status :disconnected)
|
||||
:always (assoc :peers-count peers-count))}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:connectivity/fetch-peers
|
||||
(fn [{:keys [wnode web3 retries]}]
|
||||
(inbox/fetch-peers #(re-frame/dispatch [:connectivity-check-success %])
|
||||
#(log/error :connectivity/fetch-peers %))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:connectivity-check
|
||||
(fn [{:keys [db]} _]
|
||||
(let [web3 (:web3 db)
|
||||
wnode (get-wnode db)]
|
||||
{:connectivity/fetch-peers {:wnode wnode
|
||||
:web3 web3}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:connectivity-check-success
|
||||
(fn [{:keys [db] :as cofx} [_ peers]]
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch-later [{:ms 30000 :dispatch [:connectivity-check]}]}
|
||||
(connectivity-check peers))))
|
||||
|
||||
;; NOTE(dmitryn): events chain
|
||||
;; add-peer -> fetch-peers -> mark-trusted-peer -> get-sym-key -> request-messages
|
||||
(handlers/register-handler-fx
|
||||
:initialize-offline-inbox
|
||||
(fn [{:keys [db]} [_ web3]]
|
||||
(log/info "offline inbox: initialize")
|
||||
(let [wnode (get-wnode db)]
|
||||
{::add-peer {:wnode wnode
|
||||
:web3 web3}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::add-peer-success
|
||||
(fn [{:keys [db]} [_ web3 response]]
|
||||
(let [wnode (get-wnode db)]
|
||||
(log/info "offline inbox: add-peer response" wnode response)
|
||||
{::fetch-peers {:wnode wnode
|
||||
:web3 web3
|
||||
:retries 0}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::fetch-peers-success
|
||||
(fn [{:keys [db] :as cofx} [_ web3 peers retries]]
|
||||
(let [wnode (get-wnode db)]
|
||||
(log/info "offline inbox: fetch-peers response" peers)
|
||||
(if (inbox/registered-peer? peers wnode)
|
||||
(handlers/merge-fx cofx
|
||||
{::mark-trusted-peer {:wnode wnode
|
||||
:web3 web3
|
||||
:peers peers}
|
||||
:dispatch-later [{:ms 30000 :dispatch [:connectivity-check]}]}
|
||||
(connectivity-check peers))
|
||||
(do
|
||||
(log/info "Peer" wnode "is not registered. Retrying fetch peers.")
|
||||
(handlers/merge-fx cofx
|
||||
{::fetch-peers {:wnode wnode
|
||||
:web3 web3
|
||||
:retries (inc retries)}}
|
||||
(connectivity-check peers)))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::mark-trusted-peer-success
|
||||
(fn [{:keys [db]} [_ web3 response]]
|
||||
(let [wnode (get-wnode db)
|
||||
password (:inbox/password db)]
|
||||
(log/info "offline inbox: mark-trusted-peer response" wnode response)
|
||||
{::get-sym-key {:password password
|
||||
:web3 web3}})))
|
||||
|
||||
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::get-sym-key-success
|
||||
(fn [{:keys [db]} [_ web3 sym-key-id]]
|
||||
(log/info "offline inbox: get-sym-key response" sym-key-id)
|
||||
(let [wnode (get-wnode db)
|
||||
topic (:inbox/topic db)]
|
||||
{::request-messages {:wnode wnode
|
||||
:topic topic
|
||||
:sym-key-id sym-key-id
|
||||
:web3 web3}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::request-messages-success
|
||||
(fn [_ [_ response]]
|
||||
(log/info "offline inbox: request-messages response" response)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::add-peer-error
|
||||
(fn [_ [_ error]]
|
||||
(log/error "offline inbox: add-peer error" error)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::fetch-peers-error
|
||||
(fn [_ [_ error]]
|
||||
(log/error "offline inbox: fetch-peers error" error)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::mark-trusted-peer-error
|
||||
(fn [_ [_ error]]
|
||||
(log/error "offline inbox: mark-trusted-peer error" error)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::get-sym-key-error
|
||||
(fn [_ [_ error]]
|
||||
(log/error "offline inbox: get-sym-key error" error)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::request-messages-error
|
||||
(fn [_ [_ error]]
|
||||
(log/error "offline inbox: request-messages error" error)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:handle-whisper-message
|
||||
(fn [_ [_ error msg options]]
|
||||
{::handle-whisper-message {:error error
|
||||
:msg msg
|
||||
:options options}}))
|
||||
|
||||
;;; INITIALIZE PROTOCOL
|
||||
(handlers/register-handler-fx
|
||||
:initialize-protocol
|
||||
[re-frame/trim-v
|
||||
(re-frame/inject-cofx ::get-web3)
|
||||
(re-frame/inject-cofx ::get-chat-groups)
|
||||
(re-frame/inject-cofx ::get-pending-messages)
|
||||
(re-frame/inject-cofx :get-all-contacts)]
|
||||
(fn [{:keys [db web3 groups all-contacts pending-messages]} [current-account-id ethereum-rpc-url]]
|
||||
(let [{:keys [public-key status updates-public-key
|
||||
updates-private-key]}
|
||||
(get-in db [:accounts/accounts current-account-id])]
|
||||
(when public-key
|
||||
{::init-whisper {:web3 web3 :public-key public-key :groups groups :pending-messages pending-messages
|
||||
:updates-public-key updates-public-key :updates-private-key updates-private-key
|
||||
:status status :contacts all-contacts}
|
||||
:db (assoc db :web3 web3
|
||||
:rpc-url (or ethereum-rpc-url constants/ethereum-rpc-url))}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-processed-messages
|
||||
(fn [_ _]
|
||||
{::load-processed-messages! nil}))
|
||||
(re-frame/inject-cofx :data-store/transport)]
|
||||
(fn [{:data-store/keys [transport] :keys [db web3] :as cofx} [current-account-id ethereum-rpc-url]]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc db
|
||||
:web3 web3
|
||||
:rpc-url (or ethereum-rpc-url constants/ethereum-rpc-url)
|
||||
:transport/chats transport)}
|
||||
(transport/init-whisper current-account-id))))
|
||||
|
||||
;;; NODE SYNC STATE
|
||||
|
||||
|
@ -433,216 +71,12 @@
|
|||
:check-sync
|
||||
(fn [{{:keys [web3]} :db} _]
|
||||
{::web3-get-syncing web3
|
||||
:dispatch-later [{:ms 10000 :dispatch [:check-sync]}]}))
|
||||
:dispatch-later [{:ms 10000 :dispatch [:check-sync]}]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:initialize-sync-listener
|
||||
(fn [{{:keys [sync-listening-started network networks/networks] :as db} :db} _]
|
||||
(when (and (not sync-listening-started)
|
||||
(not (utils/network-with-upstream-rpc? networks network)))
|
||||
{:db (assoc db :sync-listening-started true)
|
||||
{:db (assoc db :sync-listening-started true)
|
||||
:dispatch [:check-sync]})))
|
||||
|
||||
;;; MESSAGES
|
||||
|
||||
(defn- transform-protocol-message [{:keys [from to payload]}]
|
||||
(merge payload {:from from
|
||||
:to to
|
||||
:chat-id (or (:group-id payload) from)}))
|
||||
|
||||
(defn- message-from-self [{:keys [current-public-key]} {:keys [id to group-id]}]
|
||||
{:from to
|
||||
:sent-from current-public-key
|
||||
:payload {:message-id id
|
||||
:group-id group-id}})
|
||||
|
||||
(defn- get-message-id [{:keys [message-id ack-of-message]}]
|
||||
(or ack-of-message message-id))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:incoming-message
|
||||
(fn [{:keys [db]} [_ type {:keys [payload ttl id] :as message}]]
|
||||
(let [message-id (or id (:message-id payload))]
|
||||
(when-not (cache/exists? message-id type)
|
||||
(let [ttl-s (* 1000 (or ttl 120))
|
||||
processed-message {:id (random/id)
|
||||
:message-id message-id
|
||||
:type type
|
||||
:ttl (+ (datetime/timestamp) ttl-s)}
|
||||
chat-message (#{:message :group-message} (:type payload))
|
||||
route-fx (case type
|
||||
(:message
|
||||
:group-message
|
||||
:public-group-message) {:dispatch [:pre-received-message (transform-protocol-message message)]}
|
||||
:pending (cond-> {::pending-messages-save message}
|
||||
chat-message
|
||||
(assoc :dispatch
|
||||
[:update-message-status (message-from-self db message) :pending]))
|
||||
:sent {:dispatch [:update-message-status (message-from-self db message) :sent]}
|
||||
:ack (cond-> {::pending-messages-delete (get-message-id payload)}
|
||||
chat-message
|
||||
(assoc :dispatch [:update-message-status message :delivered]))
|
||||
:seen {:dispatch [:update-message-status message :seen]}
|
||||
:group-invitation {:dispatch [:group-chat-invite-received message]}
|
||||
:update-group {:dispatch [:update-group-message message]}
|
||||
:add-group-identity {:dispatch [:participant-invited-to-group message]}
|
||||
:remove-group-identity {:dispatch [:participant-removed-from-group message]}
|
||||
:leave-group {:dispatch [:participant-left-group message]}
|
||||
:contact-request {:dispatch [:contact-request-received message]}
|
||||
:discover {:dispatch [:status-received message]}
|
||||
:discoveries-request {:dispatch [:discoveries-request-received message]}
|
||||
:discoveries-response {:dispatch [:discoveries-response-received message]}
|
||||
:profile {:dispatch [:contact-update-received message]}
|
||||
:update-keys {:dispatch [:update-keys-received message]}
|
||||
:online {:dispatch [:contact-online-received message]}
|
||||
nil)]
|
||||
(when (nil? route-fx) (log/debug "Unknown message type" type))
|
||||
(cache/add! processed-message)
|
||||
(merge
|
||||
{::save-processed-messages processed-message}
|
||||
route-fx))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:update-message-status
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-stored-message)]
|
||||
(fn [{:keys [db get-stored-message]} [{:keys [from sent-from payload]} status]]
|
||||
(let [message-identifier (get-message-id payload)
|
||||
chat-identifier (or (:group-id payload) from)
|
||||
message-db-path [:chats chat-identifier :messages message-identifier]
|
||||
from-id (or sent-from from)
|
||||
message (or (get-in db message-db-path)
|
||||
(and (get (:not-loaded-message-ids db) message-identifier)
|
||||
(get-stored-message message-identifier)))]
|
||||
;; proceed with updating status if chat is in db, status is not the same and message was not already seen
|
||||
(when (and message
|
||||
(get-in db [:chats chat-identifier])
|
||||
(not= status (get-in message [:user-statuses from-id]))
|
||||
(not (models.message/message-seen-by? message from-id)))
|
||||
(let [statuses (assoc (:user-statuses message) from-id status)]
|
||||
(cond-> {:update-message {:message-id message-identifier
|
||||
:user-statuses statuses}}
|
||||
(get-in db message-db-path)
|
||||
(assoc :db (assoc-in db (conj message-db-path :user-statuses) statuses))))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:contact-request-received
|
||||
(fn [{{:contacts/keys [contacts]} :db}
|
||||
[_ {:keys [from payload timestamp]}]]
|
||||
(when from
|
||||
(let [{{:keys [name profile-image address status fcm-token]} :contact
|
||||
{:keys [public private]} :keypair} payload
|
||||
existing-contact (get contacts from)
|
||||
contact {:whisper-identity from
|
||||
:public-key public
|
||||
:private-key private
|
||||
:address address
|
||||
:status status
|
||||
:photo-path profile-image
|
||||
:name name
|
||||
:fcm-token fcm-token}
|
||||
chat {:name name
|
||||
:chat-id from
|
||||
:contact-info (prn-str contact)}
|
||||
prev-last-updated (get-in contacts [from :last-updated] 0)
|
||||
;; NOTE(dmitryn) Workaround for old messages not having "payload.timestamp" attribute.
|
||||
;; Get timestamp from message root level.
|
||||
;; Root level "timestamp" is a unix ts in seconds.
|
||||
timestamp' (or (:payload timestamp)
|
||||
(* 1000 timestamp))]
|
||||
(if-not existing-contact
|
||||
(let [contact (assoc contact :pending? true)]
|
||||
{:dispatch-n [[:add-contacts [contact]]
|
||||
[:add-chat from chat]]})
|
||||
(when-not (:pending? existing-contact)
|
||||
(cond-> {:dispatch-n [[:update-chat! chat]
|
||||
[:watch-contact contact]]}
|
||||
(<= prev-last-updated timestamp') (update :dispatch-n concat [[:update-contact! contact]]))))))))
|
||||
|
||||
;;GROUP
|
||||
|
||||
(defn- has-contact? [{:keys [contacts]} identity]
|
||||
(let [identities (set (map :identity contacts))]
|
||||
(contains? identities identity)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:participant-invited-to-group
|
||||
[re-frame/trim-v]
|
||||
(fn [{{:keys [current-public-key chats contacts/contacts] :as db} :db}
|
||||
[{:keys [from]
|
||||
{:keys [group-id identity message-id timestamp]} :payload}]]
|
||||
(let [chat (get-in db [:chats group-id])
|
||||
admin (:group-admin chats)]
|
||||
(when (= from admin)
|
||||
(merge {::participant-invited-to-group-message {:group-id group-id :current-public-key current-public-key
|
||||
:identity identity :from from :message-id message-id
|
||||
:timestamp timestamp :contacts contacts}}
|
||||
(when-not (and (= current-public-key identity) (has-contact? chat identity))
|
||||
{:db (update-in db [:chats group-id :contacts] conj {:identity identity})
|
||||
::chats-add-contact [group-id identity]}))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::you-removed-from-group
|
||||
[re-frame/trim-v]
|
||||
(fn [{{:keys [web3 contacts/contacts] :as db} :db}
|
||||
[{:keys [from]
|
||||
{:keys [group-id timestamp message-id]} :payload}]]
|
||||
(let [chat (get-in db [:chats group-id])
|
||||
new-update? (chat/new-update? chat timestamp)]
|
||||
(when new-update?
|
||||
{::you-removed-from-group-message {:from from
|
||||
:message-id message-id
|
||||
:timestamp timestamp
|
||||
:group-id group-id
|
||||
:contacts contacts}
|
||||
::stop-watching-group! {:web3 web3
|
||||
:group-id group-id}
|
||||
:dispatch [:update-chat! {:chat-id group-id
|
||||
:removed-from-at timestamp
|
||||
:is-active false}]}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:participant-removed-from-group
|
||||
[re-frame/trim-v
|
||||
(re-frame/inject-cofx ::message-get-by-id)]
|
||||
(fn [{{:keys [current-public-key chats contacts/contacts]} :db message-by-id :message-by-id}
|
||||
[{:keys [from]
|
||||
{:keys [group-id identity message-id timestamp]} :payload
|
||||
:as message}]]
|
||||
(when-not message-by-id
|
||||
(let [admin (get-in chats [group-id :group-admin])]
|
||||
(when (= admin from)
|
||||
(if (= current-public-key identity)
|
||||
{:dispatch [::you-removed-from-group message]}
|
||||
{::participant-removed-from-group-message {:identity identity :from from :message-id message-id
|
||||
:timestamp timestamp :group-id group-id :contacts contacts}
|
||||
::chats-remove-contact [group-id identity]}))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:participant-left-group
|
||||
[re-frame/trim-v]
|
||||
(fn [{{:keys [current-public-key contacts/contacts] :as db} :db}
|
||||
[{:keys [from]
|
||||
{:keys [group-id timestamp message-id]} :payload}]]
|
||||
(let [chat (get-in db [:chats group-id])
|
||||
{chat-is-active :is-active chat-timestamp :timestamp} chat]
|
||||
(when (and (not= current-public-key from)
|
||||
chat-is-active
|
||||
(> timestamp chat-timestamp))
|
||||
{::participant-left-group-message {:chat-id group-id
|
||||
:from from
|
||||
:message-id message-id
|
||||
:timestamp timestamp
|
||||
:contacts contacts}
|
||||
::chats-remove-contact [group-id from]
|
||||
:db (update-in db [:chats group-id :contacts]
|
||||
#(remove (fn [{:keys [identity]}]
|
||||
(= identity from)) %))}))))
|
||||
|
||||
;;ERROR
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::post-error
|
||||
(fn [_ [_ error]]
|
||||
(let [android-error? (re-find (re-pattern "Failed to connect") (.-message error))]
|
||||
(when android-error?
|
||||
{::status-init-jail nil}))))
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
(ns status-im.protocol.listeners
|
||||
(:require [cljs.reader :as r]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.protocol.ack :as ack]
|
||||
[status-im.protocol.web3.utils :as u]
|
||||
[status-im.protocol.encryption :as e]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.hex :as i]))
|
||||
|
||||
(defn empty-public-key? [public-key]
|
||||
(or (= "0x0" public-key)
|
||||
(= "" public-key)
|
||||
(nil? public-key)))
|
||||
|
||||
(defn create-error [step description]
|
||||
(when (not= description :silent)
|
||||
(log/debug step description))
|
||||
{:error description})
|
||||
|
||||
(defn init-scope [js-error js-message options]
|
||||
(if js-error
|
||||
(create-error :init-scope-error (-> js-error js->clj str))
|
||||
{:message (js->clj js-message :keywordize-keys true)
|
||||
:options options}))
|
||||
|
||||
(defn parse-payload [{:keys [message error options] :as scope}]
|
||||
(log/debug :parse-payload)
|
||||
(if error
|
||||
scope
|
||||
(try
|
||||
;; todo figure why we sometimes have to call to-utf8 twice and sometimes only once
|
||||
(let [payload (:payload message)
|
||||
payload' (u/to-utf8 payload)
|
||||
payload'' (r/read-string payload')
|
||||
payload''' (if (map? payload'')
|
||||
payload''
|
||||
(r/read-string (u/to-utf8 payload')))]
|
||||
(if (map? payload''')
|
||||
{:message (assoc message :payload payload''')
|
||||
:options options}
|
||||
(create-error :parse-payload-error (str "Invalid payload type " (type payload''')))))
|
||||
(catch :default err
|
||||
(create-error :parse-payload-error err)))))
|
||||
|
||||
(defn filter-messages-from-same-user [{:keys [message error options] :as scope}]
|
||||
(if error
|
||||
scope
|
||||
(if (or (not= (i/normalize-hex (:identity options))
|
||||
(i/normalize-hex (:sig message)))
|
||||
;; allow user to receive his own discoveries
|
||||
(= type :discover))
|
||||
scope
|
||||
(create-error :filter-messages-error :silent))))
|
||||
|
||||
(defn parse-content [{:keys [message error options] :as scope}]
|
||||
(if error
|
||||
scope
|
||||
(try
|
||||
(let [to (:recipientPublicKey message)
|
||||
from (:sig message)
|
||||
key (get-in options [:keypair :private])
|
||||
raw-content (get-in message [:payload :content])
|
||||
encrypted? (and (empty-public-key? to) key raw-content)
|
||||
content (if encrypted?
|
||||
(r/read-string (e/decrypt key raw-content))
|
||||
raw-content)]
|
||||
(log/debug :parse-content
|
||||
"Key exists:" (not (nil? key))
|
||||
"Content exists:" (not (nil? raw-content)))
|
||||
{:message (-> message
|
||||
(assoc-in [:payload :content] content)
|
||||
(assoc :to to
|
||||
:from from))
|
||||
:options options})
|
||||
(catch :default err
|
||||
(create-error :parse-content-error err)))))
|
||||
|
||||
(defn handle-message [{:keys [message error options] :as scope}]
|
||||
(if error
|
||||
scope
|
||||
(let [{:keys [web3 identity callback]} options
|
||||
{:keys [payload sig]} message
|
||||
ack? (get-in message [:payload :ack?])]
|
||||
(log/debug :handle-message message)
|
||||
(callback (if ack? :ack (:type payload)) message)
|
||||
(ack/check-ack! web3 sig payload identity))))
|
||||
|
||||
(defn- handle-whisper-message [{:keys [error msg options]}]
|
||||
(-> (init-scope error msg options)
|
||||
parse-payload
|
||||
filter-messages-from-same-user
|
||||
parse-content
|
||||
handle-message))
|
||||
|
||||
(defn message-listener
|
||||
"Valid options are: web3, identity, callback, keypair"
|
||||
[options]
|
||||
(fn [js-error js-message]
|
||||
(re-frame/dispatch [:handle-whisper-message js-error js-message options])))
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
(ns status-im.protocol.message
|
||||
(:require [cljs.spec.alpha :as s]))
|
||||
|
||||
(s/def :message/ttl (s/and int? pos?))
|
||||
(s/def :message/from string?)
|
||||
(s/def :message/sig :message/from)
|
||||
(s/def :message/privateKeyID (s/nilable string?))
|
||||
(s/def :message/pub-key (s/nilable string?))
|
||||
(s/def :message/sym-key-password (s/nilable string?))
|
||||
(s/def :message/topic string?)
|
||||
(s/def :message/to (s/nilable string?))
|
||||
(s/def :message/message-id string?)
|
||||
(s/def :message/requires-ack? boolean?)
|
||||
(s/def :keypair/private string?)
|
||||
(s/def :keypair/public string?)
|
||||
(s/def :message/keypair (s/keys :req-un [:keypair/private
|
||||
:keypair/public]))
|
||||
(s/def :message/topics (s/* string?))
|
||||
|
||||
(s/def :payload/content (s/or :string-message string?
|
||||
:command map?))
|
||||
(s/def :payload/content-type string?)
|
||||
(s/def :payload/timestamp (s/and int? pos?))
|
||||
(s/def :payload/new-keypair :message/keypair)
|
||||
|
||||
(s/def :group-message/type
|
||||
#{:public-group-message :group-message :group-invitation :add-group-identity
|
||||
:remove-group-identity :leave-group :update-group})
|
||||
|
||||
(s/def :discover-message/type #{:online :status :discover :contact-request :update-keys})
|
||||
|
||||
(s/def :message/type
|
||||
(s/or :group :group-message/type
|
||||
:discover :discover-message/type
|
||||
:user #{:message}))
|
||||
|
||||
(s/def :message/payload
|
||||
(s/keys :opt-un [:message/type
|
||||
:payload/content
|
||||
:payload/content-type
|
||||
:payload/new-keypair
|
||||
:payload/timestamp]))
|
||||
|
||||
(s/def :protocol/message
|
||||
(s/keys :req-un [:message/from :message/message-id]
|
||||
:opt-un [:message/to :message/topics :message/requires-ack?
|
||||
:message/keypair :message/ttl :message/payload]))
|
||||
|
||||
(s/def :chat-message/payload
|
||||
(s/keys :req-un [:payload/content :payload/content-type :payload/timestamp]))
|
||||
|
||||
(s/def :options/web3 #(not (nil? %)))
|
|
@ -1,22 +0,0 @@
|
|||
(ns status-im.protocol.message-cache
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defonce messages-set (atom #{}))
|
||||
(defonce messages-map (atom {}))
|
||||
|
||||
(defn init!
|
||||
[messages]
|
||||
(reset! messages-set (set messages))
|
||||
(reset! messages-map (->> messages
|
||||
(map (fn [{:keys [message-id type] :as message}]
|
||||
[[message-id type] message]))
|
||||
(into {}))))
|
||||
|
||||
(defn add!
|
||||
[{:keys [message-id type] :as message}]
|
||||
(swap! messages-set conj message)
|
||||
(swap! messages-map conj [[message-id type] message]))
|
||||
|
||||
(defn exists?
|
||||
[message-id type]
|
||||
(get @messages-map [message-id type]))
|
|
@ -1,12 +0,0 @@
|
|||
(ns status-im.protocol.validation)
|
||||
|
||||
(defn- fline [and-form] (:line (meta and-form)))
|
||||
|
||||
(defmacro valid? [spec x]
|
||||
`(let [v?# (cljs.spec.alpha/valid? ~spec ~x)]
|
||||
(when-not v?#
|
||||
(let [explanation# (cljs.spec.alpha/explain-str ~spec ~x)]
|
||||
(taoensso.timbre/log! :error :p
|
||||
[explanation#]
|
||||
~{:?line (fline &form)})))
|
||||
v?#))
|
|
@ -1,2 +0,0 @@
|
|||
(ns status-im.protocol.validation
|
||||
(:require-macros [status-im.protocol.validation :as macros]))
|
|
@ -1,271 +0,0 @@
|
|||
(ns status-im.protocol.web3.delivery
|
||||
(:require [cljs.core.async :as async]
|
||||
[status-im.protocol.web3.transport :as t]
|
||||
[status-im.protocol.web3.utils :as u]
|
||||
[status-im.protocol.encryption :as e]
|
||||
[cljs.spec.alpha :as s]
|
||||
[taoensso.timbre :refer-macros [debug] :as log]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]
|
||||
[clojure.set :as set]
|
||||
[status-im.protocol.web3.keys :as shh-keys]
|
||||
[status-im.utils.async :refer [timeout]]
|
||||
[status-im.utils.datetime :as datetime]))
|
||||
|
||||
(defonce loop-state (atom nil))
|
||||
(defonce messages (atom {}))
|
||||
|
||||
(defn prepare-message
|
||||
[web3 {:keys [payload keypair to from topics ttl key-password]
|
||||
:as message}
|
||||
callback]
|
||||
(let [{:keys [public]} keypair
|
||||
|
||||
content (:content payload)
|
||||
content' (if (and (not to) public content)
|
||||
(e/encrypt public (prn-str content))
|
||||
content)
|
||||
|
||||
payload' (-> message
|
||||
(select-keys [:message-id :requires-ack? :type :clock-value])
|
||||
(merge payload)
|
||||
(assoc :content content')
|
||||
prn-str
|
||||
u/from-utf8)
|
||||
sym-key-password (or key-password shh-keys/status-key-password)]
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
sym-key-password
|
||||
(fn [status-key-id]
|
||||
(callback
|
||||
(merge
|
||||
(select-keys message [:ttl])
|
||||
(let [type (if to :asym :sym)]
|
||||
(cond-> {:sig from
|
||||
:topic (first topics)
|
||||
:payload payload'}
|
||||
to (assoc :pubKey to)
|
||||
(not to) (assoc :symKeyID status-key-id
|
||||
:sym-key-password sym-key-password)))))))))
|
||||
|
||||
(s/def :shh/pending-message
|
||||
(s/keys :req-un [:message/sig :shh/payload :message/topic]
|
||||
:opt-un [:message/ttl :message/pubKey :message/symKeyID]))
|
||||
|
||||
(defonce pending-message-callback (atom nil))
|
||||
(defonce recipient->pending-message (atom {}))
|
||||
|
||||
;; Buffer needs to be big enough to not block even with many outbound messages
|
||||
(def ^:private pending-message-queue (async/chan 2000))
|
||||
|
||||
(async/go-loop [[web3 {:keys [type message-id requires-ack? to ack?] :as message}]
|
||||
(async/<! pending-message-queue)]
|
||||
(when message
|
||||
(prepare-message
|
||||
web3 message
|
||||
(fn [message']
|
||||
(when (valid? :shh/pending-message message')
|
||||
(let [group-id (get-in message [:payload :group-id])
|
||||
pending-message {:id message-id
|
||||
:ack? (boolean ack?)
|
||||
:message message'
|
||||
:to to
|
||||
:type type
|
||||
:group-id group-id
|
||||
:requires-ack? (boolean requires-ack?)
|
||||
:attempts 0
|
||||
:was-sent? false}]
|
||||
(when (and @pending-message-callback requires-ack?)
|
||||
(@pending-message-callback :pending pending-message))
|
||||
(swap! messages assoc-in [web3 message-id to] pending-message)
|
||||
(when to
|
||||
(swap! recipient->pending-message
|
||||
update to set/union #{[web3 message-id to]}))))))
|
||||
(recur (async/<! pending-message-queue))))
|
||||
|
||||
(defn set-pending-mesage-callback!
|
||||
[callback]
|
||||
(reset! pending-message-callback callback))
|
||||
|
||||
(defn add-pending-message!
|
||||
[web3 message]
|
||||
{:pre [(valid? :protocol/message message)]}
|
||||
;; encryption can take some time, better to run asynchronously
|
||||
(async/go (async/>! pending-message-queue [web3 message])))
|
||||
|
||||
(s/def :delivery/pending-message
|
||||
(s/keys :req-un [:message/sig :message/to :shh/payload :payload/ack? ::id
|
||||
:message/requires-ack? :message/topic ::attempts ::was-sent?]
|
||||
:opt-un [:message/pub-key :message/sym-key-password]))
|
||||
|
||||
(defn- do-add-pending-message!
|
||||
[web3 {:keys [message-id to pub-key sym-key-id] :as pending-message}]
|
||||
(let [message (select-keys pending-message [:sig :topic :payload])
|
||||
message' (if sym-key-id
|
||||
(assoc message :symKeyId sym-key-id)
|
||||
(assoc message :pubKey pub-key))
|
||||
pending-message' (assoc pending-message :message message'
|
||||
:id message-id)]
|
||||
(swap! messages assoc-in [web3 message-id to] pending-message')
|
||||
(when to
|
||||
(swap! recipient->pending-message
|
||||
update to set/union #{[web3 message-id to]}))))
|
||||
|
||||
(defn add-prepared-pending-message!
|
||||
[web3 {:keys [sym-key-password] :as pending-message}]
|
||||
{:pre [(valid? :delivery/pending-message pending-message)]}
|
||||
(debug :add-prepared-pending-message!)
|
||||
(if sym-key-password
|
||||
(shh-keys/get-sym-key
|
||||
web3
|
||||
sym-key-password
|
||||
(fn [sym-key-id]
|
||||
(do-add-pending-message! web3 (assoc pending-message :sym-key-id sym-key-id))))
|
||||
(do-add-pending-message! web3 pending-message)))
|
||||
|
||||
(defn remove-pending-message! [web3 id to]
|
||||
(swap! messages update web3
|
||||
(fn [messages]
|
||||
(when messages
|
||||
(let [message (messages id)
|
||||
;; Message that is send without specified "from" option
|
||||
;; is stored in pending "messages" map as
|
||||
;; {message-id {nil message}}.
|
||||
;; When we receive the first ack for such message it is
|
||||
;; removed from pending messages adding of the nil key
|
||||
;; to the next dissoc form
|
||||
;; todo rewrite handling of ack message in more clear way
|
||||
message' (dissoc message to nil)]
|
||||
(if (seq message')
|
||||
(assoc messages id message')
|
||||
(dissoc messages id))))))
|
||||
(when to
|
||||
(swap! recipient->pending-message
|
||||
update to set/difference #{[web3 id to]})))
|
||||
|
||||
(defn message-was-sent! [web3 id to]
|
||||
(let [messages' (swap! messages update web3
|
||||
(fn [messages]
|
||||
(let [message (get-in messages [id to])
|
||||
message' (when message
|
||||
(assoc message :was-sent? true
|
||||
:attempts 1))]
|
||||
(if message'
|
||||
(assoc-in messages [id to] message')
|
||||
messages))))]
|
||||
(when @pending-message-callback
|
||||
(let [message (get-in messages' [web3 id to])]
|
||||
(when message
|
||||
(@pending-message-callback :sent message))))))
|
||||
|
||||
(defn attempt-was-made! [web3 id to]
|
||||
(debug :attempt-was-made id)
|
||||
(swap! messages update-in [web3 id to]
|
||||
(fn [{:keys [attempts] :as data}]
|
||||
(assoc data :attempts (inc attempts)
|
||||
:last-attempt (datetime/timestamp)))))
|
||||
|
||||
(defn delivery-callback
|
||||
[web3 post-error-callback {:keys [id requires-ack? to]} message]
|
||||
(fn [error _]
|
||||
(when error
|
||||
(log/warn :shh-post-error error message)
|
||||
(when post-error-callback
|
||||
(post-error-callback error)))
|
||||
(when-not error
|
||||
(debug :delivery-callback)
|
||||
(message-was-sent! web3 id to)
|
||||
(when-not requires-ack?
|
||||
(remove-pending-message! web3 id to)))))
|
||||
|
||||
(s/def ::pos-int (s/and pos? int?))
|
||||
(s/def ::delivery-loop-ms-interval ::pos-int)
|
||||
(s/def ::ack-not-received-s-interval ::pos-int)
|
||||
(s/def ::max-attempts-number ::pos-int)
|
||||
(s/def ::default-ttl ::pos-int)
|
||||
(s/def ::send-online-s-interval ::pos-int)
|
||||
(s/def ::online-message fn?)
|
||||
(s/def ::post-error-callback fn?)
|
||||
|
||||
(s/def ::delivery-options
|
||||
(s/keys :req-un [::delivery-loop-ms-interval ::ack-not-received-s-interval
|
||||
::max-attempts-number ::default-ttl ::send-online-s-interval
|
||||
::post-error-callback]
|
||||
:opt-un [::online-message]))
|
||||
|
||||
(defn should-be-retransmitted?
|
||||
"Checks if messages should be transmitted again."
|
||||
[{:keys [ack-not-received-s-interval max-attempts-number]}
|
||||
{:keys [was-sent? attempts last-attempt]}]
|
||||
(if-not was-sent?
|
||||
;; message was not sent succesfully via web3.shh, but maybe
|
||||
;; better to do this only when we receive error from shh.post
|
||||
;; todo add some notification about network issues
|
||||
(<= attempts (* 5 max-attempts-number))
|
||||
(and
|
||||
;; if message was not send less then max-attempts-number times
|
||||
;; continue attempts
|
||||
(<= attempts max-attempts-number)
|
||||
;; check retransmission interval
|
||||
(<= (+ last-attempt (* 1000 ack-not-received-s-interval)) (datetime/timestamp)))))
|
||||
|
||||
(defn- check-ttl
|
||||
[message message-type ttl-config default-ttl]
|
||||
(update message :ttl #(or % ((keyword message-type) ttl-config) default-ttl)))
|
||||
|
||||
(defn message-pending?
|
||||
[web3 required-type required-to]
|
||||
(some (fn [[_ messages]]
|
||||
(some (fn [[_ {:keys [type to]}]]
|
||||
(and (= type required-type)
|
||||
(= to required-to)))
|
||||
messages))
|
||||
(@messages web3)))
|
||||
|
||||
(defn run-delivery-loop!
|
||||
[web3 {:keys [delivery-loop-ms-interval default-ttl ttl-config
|
||||
send-online-s-interval online-message post-error-callback
|
||||
pow-target pow-time]
|
||||
:as options}]
|
||||
{:pre [(valid? ::delivery-options options)]}
|
||||
(debug :run-delivery-loop!)
|
||||
(let [previous-stop-flag @loop-state
|
||||
stop? (atom false)]
|
||||
;; stop previous delivery loop if it exists
|
||||
(when previous-stop-flag
|
||||
(reset! previous-stop-flag true))
|
||||
;; reset stop flag for a new loop
|
||||
(reset! loop-state stop?)
|
||||
;; go go!!!
|
||||
(debug :init-loop)
|
||||
(async/go-loop [_ nil]
|
||||
(doseq [[_ messages] (@messages web3)]
|
||||
(doseq [[_ {:keys [id message to type] :as data}] messages]
|
||||
;; check each message asynchronously
|
||||
(when (should-be-retransmitted? options data)
|
||||
(try
|
||||
(let [message' (-> message
|
||||
(check-ttl type ttl-config default-ttl)
|
||||
(assoc :powTarget pow-target
|
||||
:powTime pow-time))
|
||||
callback (delivery-callback web3 post-error-callback data message')]
|
||||
(t/post-message! web3 message' callback))
|
||||
(catch :default err
|
||||
(log/error :post-message-error err))
|
||||
(finally
|
||||
(attempt-was-made! web3 id to))))))
|
||||
(when-not @stop?
|
||||
(recur (async/<! (timeout delivery-loop-ms-interval)))))
|
||||
(async/go-loop [_ nil]
|
||||
(when-not @stop?
|
||||
(online-message)
|
||||
(recur (async/<! (timeout (* 1000 send-online-s-interval))))))))
|
||||
|
||||
(defn reset-pending-messages! [to]
|
||||
(doseq [key (@recipient->pending-message to)]
|
||||
(when (get-in @messages key)
|
||||
(swap! messages #(update-in % key assoc
|
||||
:last-attempt 0
|
||||
:attempts 0)))))
|
||||
|
||||
(defn reset-all-pending-messages! []
|
||||
(reset! messages {}))
|
|
@ -1,61 +0,0 @@
|
|||
(ns status-im.protocol.web3.filtering
|
||||
(:require [status-im.protocol.web3.utils :as u]
|
||||
[status-im.utils.config :as config]
|
||||
[cljs.spec.alpha :as s]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
;; XXX(oskarth): Perf issue to have one topic
|
||||
;; See https://github.com/status-im/ideas/issues/55#issuecomment-355511183
|
||||
(def status-topic "0xaabb11ee")
|
||||
|
||||
(defonce filters (atom {}))
|
||||
|
||||
;; NOTE(oskarth): This has concerns for upgradability and chatting cross
|
||||
;; versions. How can we do this breaking change gradually?
|
||||
|
||||
;; NOTE(oskarth): Due to perf we don't want a single topic for all messages,
|
||||
;; instead we want many. We need a way for user A and B to agree on which topics
|
||||
;; to use. By using first 10 characters of the pub-key, we construct a topic
|
||||
;; that requires no coordination for 1-1 chats.
|
||||
(defn identity->topic [identity]
|
||||
(apply str (take 10 identity)))
|
||||
|
||||
(defn get-topics [& [identity]]
|
||||
(if config/many-whisper-topics-enabled?
|
||||
(do (log/info "FLAG: many-whisper-topics-enabled ON")
|
||||
[(identity->topic identity)])
|
||||
(do (log/info "FLAG: many-whisper-topics-enabled OFF")
|
||||
[status-topic])))
|
||||
|
||||
(s/def ::options (s/keys :opt-un [:message/to :message/topics]))
|
||||
|
||||
(defn remove-filter! [web3 options]
|
||||
(when-let [filter (get-in @filters [web3 options])]
|
||||
(.stopWatching filter
|
||||
(fn [error _]
|
||||
(when error
|
||||
(log/warn :remove-filter-error options error))))
|
||||
(log/debug :stop-watching options)
|
||||
(swap! filters update web3 dissoc options)))
|
||||
|
||||
(defn add-shh-filter!
|
||||
[web3 {:keys [key type] :as options} callback]
|
||||
(let [type (or type :asym)
|
||||
options' (cond-> (dissoc options :key)
|
||||
(= type :asym) (assoc :privateKeyID key)
|
||||
(= type :sym) (assoc :symKeyID key))
|
||||
filter (.newMessageFilter (u/shh web3) (clj->js options')
|
||||
callback
|
||||
#(log/warn :add-filter-error (.stringify js/JSON (clj->js options')) %))]
|
||||
(swap! filters assoc-in [web3 options] filter)))
|
||||
|
||||
(defn add-filter!
|
||||
[web3 {:keys [topics to] :as options} callback]
|
||||
(remove-filter! web3 options)
|
||||
(log/debug :add-filter options)
|
||||
(add-shh-filter! web3 options callback))
|
||||
|
||||
(defn remove-all-filters! []
|
||||
(doseq [[web3 filters] @filters]
|
||||
(doseq [options (keys filters)]
|
||||
(remove-filter! web3 options))))
|
|
@ -1,81 +0,0 @@
|
|||
(ns status-im.protocol.web3.inbox
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.protocol.web3.utils :as web3.utils]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(def peers (atom #{}))
|
||||
(def trusted-peers (atom #{}))
|
||||
|
||||
;; NOTE(dmitryn) Expects JSON response like:
|
||||
;; {"error": "msg"} or {"result": true}
|
||||
(defn- parse-json [s]
|
||||
(try
|
||||
(let [res (-> s
|
||||
js/JSON.parse
|
||||
(js->clj :keywordize-keys true))]
|
||||
;; NOTE(dmitryn): AddPeer() may return {"error": ""}
|
||||
;; assuming empty error is a success response
|
||||
;; by transforming {"error": ""} to {:result true}
|
||||
(if (and (:error res)
|
||||
(= (:error res) ""))
|
||||
{:result true}
|
||||
res))
|
||||
(catch :default e
|
||||
{:error (.-message e)})))
|
||||
|
||||
(defn- response-handler [error-fn success-fn]
|
||||
(fn handle-response
|
||||
([response]
|
||||
(let [{:keys [error result]} (parse-json response)]
|
||||
(handle-response error result)))
|
||||
([error result]
|
||||
(if error
|
||||
(error-fn error)
|
||||
(success-fn result)))))
|
||||
|
||||
(defn add-peer [enode success-fn error-fn]
|
||||
(if (@peers enode)
|
||||
(success-fn true)
|
||||
(status/add-peer enode
|
||||
(response-handler error-fn (fn [result]
|
||||
(swap! peers conj enode)
|
||||
(success-fn result))))))
|
||||
|
||||
(defn registered-peer? [peers enode]
|
||||
(let [peer-ids (set (map :id peers))
|
||||
enode-id (web3.utils/extract-enode-id enode)]
|
||||
(contains? peer-ids enode-id)))
|
||||
|
||||
(defn mark-trusted-peer [web3 enode peers success-fn error-fn]
|
||||
(if (@trusted-peers enode)
|
||||
(success-fn true)
|
||||
(.markTrustedPeer (web3.utils/shh web3)
|
||||
enode
|
||||
(response-handler error-fn (fn [result]
|
||||
(swap! trusted-peers conj enode)
|
||||
(success-fn result))))))
|
||||
|
||||
;; TODO(dmitryn): use web3 instead of rpc call
|
||||
(defn fetch-peers [success-fn error-fn]
|
||||
(let [args {:jsonrpc "2.0"
|
||||
:id 2
|
||||
:method "admin_peers"
|
||||
:params []}
|
||||
payload (.stringify js/JSON (clj->js args))]
|
||||
(status/call-web3 payload
|
||||
(response-handler error-fn success-fn))))
|
||||
|
||||
(defn request-messages [web3 wnode topic sym-key-id success-fn error-fn]
|
||||
(log/info "offline inbox: sym-key-id" sym-key-id)
|
||||
(let [opts {:mailServerPeer wnode
|
||||
:topic topic
|
||||
:symKeyID sym-key-id}]
|
||||
(log/info "offline inbox: request-messages request")
|
||||
(log/info "offline inbox: request-messages args" (pr-str opts))
|
||||
(.requestMessages (web3.utils/shh web3)
|
||||
(clj->js opts)
|
||||
(response-handler error-fn success-fn))))
|
||||
|
||||
(defn initialize! [web3]
|
||||
(re-frame/dispatch [:initialize-offline-inbox web3]))
|
|
@ -1,33 +0,0 @@
|
|||
(ns status-im.protocol.web3.keys
|
||||
(:require [taoensso.timbre :as log]))
|
||||
|
||||
(def status-key-password "status-key-password")
|
||||
(def status-group-key-password "status-public-group-key-password")
|
||||
|
||||
(defonce password->keys (atom {}))
|
||||
|
||||
(defn- add-sym-key-from-password
|
||||
[web3 password callback]
|
||||
(.. web3
|
||||
-shh
|
||||
(generateSymKeyFromPassword password callback)))
|
||||
|
||||
(defn get-sym-key
|
||||
"Memoizes expensive calls by password."
|
||||
([web3 password success-fn]
|
||||
;; TODO:(dmitryn) add proper error handling
|
||||
;; to other usages of get-sym-key fn
|
||||
(get-sym-key web3 password success-fn #(log/error %)))
|
||||
([web3 password success-fn error-fn]
|
||||
(if-let [key-id (get @password->keys password)]
|
||||
(success-fn key-id)
|
||||
(add-sym-key-from-password
|
||||
web3 password
|
||||
(fn [err res]
|
||||
(if err
|
||||
(error-fn err)
|
||||
(do (swap! password->keys assoc password res)
|
||||
(success-fn res))))))))
|
||||
|
||||
(defn reset-keys! []
|
||||
(reset! password->keys {}))
|
|
@ -1,19 +0,0 @@
|
|||
(ns status-im.protocol.web3.transport
|
||||
(:require [status-im.protocol.web3.utils :as u]
|
||||
[cljs.spec.alpha :as s]
|
||||
[status-im.protocol.validation :refer-macros [valid?]]
|
||||
[taoensso.timbre :refer-macros [debug]]))
|
||||
|
||||
(s/def :shh/payload string?)
|
||||
(s/def :shh/powTarget number?)
|
||||
(s/def :shh/powTime number?)
|
||||
(s/def :shh/message
|
||||
(s/keys
|
||||
:req-un [:shh/payload :message/ttl :message/sig :message/topic
|
||||
:shh/powTarget :shh/powTime]))
|
||||
|
||||
(defn post-message!
|
||||
[web3 message callback]
|
||||
{:pre [(valid? :shh/message message)]}
|
||||
(let [shh (u/shh web3)]
|
||||
(.post shh (clj->js message) callback)))
|
|
@ -0,0 +1,80 @@
|
|||
(ns ^{:doc "API to init and stop whisper messaging"}
|
||||
status-im.transport.core
|
||||
(:require [cljs.spec.alpha :as spec]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.filters :as filters]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.transport.inbox :as inbox]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.db :as transport.db]))
|
||||
|
||||
(defn init-whisper
|
||||
"Initialises whisper protocol by:
|
||||
- adding fixed shh discovery filter
|
||||
- restoring existing symetric keys along with their unique filters
|
||||
- (optionally) initializing offline inboxing"
|
||||
[current-account-id {:keys [db web3] :as cofx}]
|
||||
(log/debug :init-whisper)
|
||||
(when-let [public-key (get-in db [:accounts/accounts current-account-id :public-key])]
|
||||
(let [sym-key-added-callback (fn [chat-id sym-key sym-key-id]
|
||||
(re-frame/dispatch [::sym-key-added {:chat-id chat-id
|
||||
:sym-key sym-key
|
||||
:sym-key-id sym-key-id}]))
|
||||
topic (transport.utils/get-topic constants/contact-discovery)]
|
||||
(handlers/merge-fx cofx
|
||||
{:shh/add-discovery-filter {:web3 web3
|
||||
:private-key-id public-key
|
||||
:topic topic}
|
||||
:shh/restore-sym-keys {:web3 web3
|
||||
:transport (:transport/chats db)
|
||||
:on-success sym-key-added-callback}}
|
||||
(inbox/initialize-offline-inbox)))))
|
||||
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
;;Since symkeys are not persisted, we restore them via add sym-keys,
|
||||
;;this is the callback that is called when a key has been restored for a particular chat.
|
||||
;;it saves the sym-key-id in app-db to send messages later
|
||||
;;and starts a filter to receive messages
|
||||
(handlers/register-handler-fx
|
||||
::sym-key-added
|
||||
(fn [{:keys [db]} [_ {:keys [chat-id sym-key sym-key-id]}]]
|
||||
(let [web3 (:web3 db)
|
||||
{:keys [topic] :as chat} (get-in db [:transport/chats chat-id])]
|
||||
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat (assoc chat :sym-key-id sym-key-id)}
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic topic
|
||||
:chat-id chat-id}})))
|
||||
|
||||
;;TODO (yenda) uncomment and rework once go implements persistence
|
||||
#_(doseq [[chat-id {:keys [sym-key-id topic] :as chat}] transport]
|
||||
(when sym-key-id
|
||||
(filters/add-filter! web3
|
||||
{:symKeyID sym-key-id
|
||||
:topics [topic]}
|
||||
(fn [js-error js-message]
|
||||
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message chat-id])))))
|
||||
|
||||
(defn unsubscribe-from-chat
|
||||
"Unsubscribe from chat on transport layer"
|
||||
[chat-id {:keys [db]}]
|
||||
(let [filter (get-in db [:transport/chats chat-id :filter])]
|
||||
{:db (update db :transport/chats dissoc chat-id)
|
||||
:data-store.transport/delete chat-id
|
||||
:shh/remove-filter filter}))
|
||||
|
||||
(defn stop-whisper
|
||||
"Stops whisper protocol by removing all existing shh filters
|
||||
It is necessary to remove the filters because status-go there isn't currently a logout feature in status-go
|
||||
to clean-up after logout. When logging out of account A and logging in account B, account B would receive
|
||||
account A messages without this."
|
||||
[{:keys [db]}]
|
||||
(let [{:transport/keys [chats discovery-filter]} db
|
||||
chat-filters (mapv :filter (vals chats))
|
||||
all-filters (conj chat-filters discovery-filter)]
|
||||
{:shh/remove-filters all-filters}))
|
|
@ -0,0 +1,34 @@
|
|||
(ns ^{:doc "DB spec and utils for the transport layer"}
|
||||
status-im.transport.db
|
||||
(:require-macros [status-im.utils.db :refer [allowed-keys]])
|
||||
(:require [cljs.spec.alpha :as spec]))
|
||||
|
||||
;; required
|
||||
(spec/def ::ack (spec/coll-of string? :kind vector?))
|
||||
(spec/def ::seen (spec/coll-of string? :kind vector?))
|
||||
(spec/def ::pending-ack (spec/coll-of string? :kind vector?))
|
||||
(spec/def ::pending-send (spec/coll-of string? :kind vector?))
|
||||
(spec/def ::topic string?)
|
||||
|
||||
;; optional
|
||||
(spec/def ::sym-key-id string?)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(spec/def ::sym-key string?)
|
||||
(spec/def ::filter any?)
|
||||
|
||||
(spec/def :transport/chat (allowed-keys :req-un [::ack ::seen ::pending-ack ::pending-send ::topic]
|
||||
:opt-un [::sym-key-id ::sym-key ::filter]))
|
||||
|
||||
(spec/def :transport/chats (spec/map-of :global/not-empty-string :transport/chat))
|
||||
(spec/def :transport/discovery-filter (spec/nilable any?))
|
||||
|
||||
|
||||
(defn create-chat
|
||||
"Initialize datastructure for chat representation at the transport level
|
||||
Currently only :topic is actually used"
|
||||
[topic]
|
||||
{:ack []
|
||||
:seen []
|
||||
:pending-ack []
|
||||
:pending-send []
|
||||
:topic topic})
|
|
@ -0,0 +1,71 @@
|
|||
(ns ^{:doc "API for whisper filters"}
|
||||
status-im.transport.filters
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.utils :as utils]
|
||||
[status-im.utils.config :as config]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn remove-filter! [filter]
|
||||
(.stopWatching filter
|
||||
(fn [error _]
|
||||
(when error
|
||||
(log/warn :remove-filter-error filter error))))
|
||||
(log/debug :stop-watching filter))
|
||||
|
||||
(defn add-shh-filter!
|
||||
[web3 options callback]
|
||||
(.newMessageFilter (utils/shh web3) (clj->js options)
|
||||
callback
|
||||
#(log/warn :add-filter-error (.stringify js/JSON (clj->js options)) %)))
|
||||
|
||||
(defn add-filter!
|
||||
[web3 {:keys [topics to] :as options} callback]
|
||||
(let [options (if config/offline-inbox-enabled?
|
||||
(assoc options :allowP2P true)
|
||||
options)]
|
||||
(log/debug :add-filter options)
|
||||
(add-shh-filter! web3 options callback)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/add-filter
|
||||
(fn [{:keys [web3 sym-key-id topic chat-id]}]
|
||||
(when-let [filter (add-filter! web3
|
||||
{:topics [topic]
|
||||
:symKeyID sym-key-id}
|
||||
(fn [js-error js-message]
|
||||
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message chat-id])))]
|
||||
(re-frame/dispatch [::filter-added chat-id filter]))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
::filter-added
|
||||
[re-frame/trim-v]
|
||||
(fn [db [chat-id filter]]
|
||||
(assoc-in db [:transport/chats chat-id :filter] filter)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/add-discovery-filter
|
||||
(fn [{:keys [web3 private-key-id topic]}]
|
||||
(when-let [filter (add-filter! web3
|
||||
{:topics [topic]
|
||||
:privateKeyID private-key-id}
|
||||
(fn [js-error js-message]
|
||||
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message])))]
|
||||
(re-frame/dispatch [::discovery-filter-added filter]))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
::discovery-filter-added
|
||||
[re-frame/trim-v]
|
||||
(fn [db [filter]]
|
||||
(assoc db :transport/discovery-filter filter)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/remove-filter
|
||||
(fn [filter]
|
||||
(when filter (remove-filter! filter))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/remove-filters
|
||||
(fn [filters]
|
||||
(doseq [filter filters]
|
||||
(remove-filter! filter))))
|
|
@ -0,0 +1,37 @@
|
|||
(ns ^{:doc "Events for message handling"}
|
||||
status-im.transport.handlers
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[cljs.reader :as reader]
|
||||
[status-im.transport.message.transit :as transit]
|
||||
[status-im.transport.shh :as shh]
|
||||
[status-im.transport.filters :as filters]))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:protocol/receive-whisper-message
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [js-error js-message chat-id]]
|
||||
(let [{:keys [payload sig]} (js->clj js-message :keywordize-keys true)
|
||||
status-message (-> payload
|
||||
transport.utils/to-utf8
|
||||
transit/deserialize)]
|
||||
(when (and sig status-message)
|
||||
(message/receive status-message (or chat-id sig) sig cofx)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:protocol/send-status-message-success
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db] :as cofx} [_ resp]]
|
||||
(log/debug :send-status-message-success resp)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:protocol/send-status-message-error
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db] :as cofx} [err]]
|
||||
(log/error :send-status-message-error err)))
|
|
@ -0,0 +1,191 @@
|
|||
(ns ^{:doc "Offline inboxing events and API"}
|
||||
status-im.transport.inbox
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.utils :as web3.utils]
|
||||
[status-im.utils.config :as config]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn- parse-json
|
||||
;; NOTE(dmitryn) Expects JSON response like:
|
||||
;; {"error": "msg"} or {"result": true}
|
||||
[s]
|
||||
(try
|
||||
(let [res (-> s
|
||||
js/JSON.parse
|
||||
(js->clj :keywordize-keys true))]
|
||||
;; NOTE(dmitryn): AddPeer() may return {"error": ""}
|
||||
;; assuming empty error is a success response
|
||||
;; by transforming {"error": ""} to {:result true}
|
||||
(if (and (:error res)
|
||||
(= (:error res) ""))
|
||||
{:result true}
|
||||
res))
|
||||
(catch :default e
|
||||
{:error (.-message e)})))
|
||||
|
||||
(defn- response-handler [error-fn success-fn]
|
||||
(fn handle-response
|
||||
([response]
|
||||
(let [{:keys [error result]} (parse-json response)]
|
||||
(handle-response error result)))
|
||||
([error result]
|
||||
(if error
|
||||
(error-fn error)
|
||||
(success-fn result)))))
|
||||
|
||||
(defn initialize-offline-inbox
|
||||
"Initialises offline inbox by producing `::add-peer` effect if inboxing enabled in config,
|
||||
then the event chan is:
|
||||
add-peer -> fetch-peers -> check-peer-added -> mark-trusted-peer -> get-sym-key -> request-messages"
|
||||
[{:keys [db]}]
|
||||
(when config/offline-inbox-enabled?
|
||||
(let [wnode-id (get db :inbox/wnode)
|
||||
wnode (get-in db [:inbox/wnodes wnode-id :address])]
|
||||
(log/info "offline inbox: initialize")
|
||||
{::add-peer {:wnode wnode}})))
|
||||
|
||||
(defn add-peer [enode success-fn error-fn]
|
||||
(status/add-peer enode (response-handler error-fn success-fn)))
|
||||
|
||||
(defn fetch-peers
|
||||
;; https://github.com/ethereum/go-ethereum/wiki/Management-APIs#admin_peers
|
||||
;; retrieves all the information known about the connected remote nodes
|
||||
;; TODO(dmitryn): use web3 instead of rpc call
|
||||
[success-fn error-fn]
|
||||
(let [args {:jsonrpc "2.0"
|
||||
:id 2
|
||||
:method "admin_peers"
|
||||
:params []}
|
||||
payload (.stringify js/JSON (clj->js args))]
|
||||
(status/call-web3 payload (response-handler error-fn success-fn))))
|
||||
|
||||
(defn registered-peer? [peers enode]
|
||||
(let [peer-ids (into #{} (map :id) peers)
|
||||
enode-id (web3.utils/extract-enode-id enode)]
|
||||
(contains? peer-ids enode-id)))
|
||||
|
||||
(defn mark-trusted-peer [web3 enode peers success-fn error-fn]
|
||||
(.markTrustedPeer (web3.utils/shh web3)
|
||||
enode
|
||||
(fn [err resp]
|
||||
(if-not err
|
||||
(success-fn resp)
|
||||
(error-fn err)))))
|
||||
|
||||
(defn request-messages [web3 wnode topics to from sym-key-id success-fn error-fn]
|
||||
(log/info "offline inbox: sym-key-id" sym-key-id)
|
||||
(let [opts (merge {:mailServerPeer wnode
|
||||
:symKeyID sym-key-id}
|
||||
(when from {:from from})
|
||||
(when to {:to to}))]
|
||||
(log/info "offline inbox: request-messages request for topics " topics)
|
||||
(doseq [topic topics]
|
||||
(let [opts (assoc opts :topic topic)]
|
||||
(log/info "offline inbox: request-messages args" (pr-str opts))
|
||||
(.requestMessages (web3.utils/shh web3)
|
||||
(clj->js opts)
|
||||
(fn [err resp]
|
||||
(if-not err
|
||||
(success-fn resp)
|
||||
(error-fn err topic))))))))
|
||||
|
||||
(defn get-wnode [db]
|
||||
(let [wnode-id (get db :inbox/wnode)]
|
||||
(get-in db [:inbox/wnodes wnode-id :address])))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::add-peer
|
||||
(fn [{:keys [wnode]}]
|
||||
(add-peer wnode
|
||||
#(re-frame/dispatch [::fetch-peers %])
|
||||
#(log/error "offline inbox: add-peer error" %))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::fetch-peers
|
||||
(fn [retries]
|
||||
(fetch-peers #(re-frame/dispatch [::check-peer-added % retries])
|
||||
#(log/error "offline inbox: fetch-peers error" %))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::mark-trusted-peer
|
||||
(fn [{:keys [wnode web3 peers]}]
|
||||
(mark-trusted-peer web3
|
||||
wnode
|
||||
peers
|
||||
#(re-frame/dispatch [::get-sym-key %])
|
||||
#(log/error "offline inbox: mark-trusted-peer error" % wnode))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::request-messages
|
||||
(fn [{:keys [wnode topics to from sym-key-id web3]}]
|
||||
(request-messages web3
|
||||
wnode
|
||||
topics
|
||||
to
|
||||
from
|
||||
sym-key-id
|
||||
#(log/info "offline inbox: request-messages response" %)
|
||||
#(log/error "offline inbox: request-messages error" %1 %2 to from))))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::fetch-peers
|
||||
;; This event fetches the list of peers
|
||||
;; We want it to check if the node has been added
|
||||
(fn [_ [_ retries]]
|
||||
{::fetch-peers (or retries 0)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::check-peer-added
|
||||
;; We check if the wnode is part of the peers list
|
||||
;; if not we dispatch a new fetch-peer event for later
|
||||
(fn [{:keys [db]} [_ peers retries]]
|
||||
(let [web3 (:web3 db)
|
||||
wnode (get-wnode db)]
|
||||
(log/info "offline inbox: fetch-peers response" peers)
|
||||
(if (registered-peer? peers wnode)
|
||||
{::mark-trusted-peer {:web3 web3
|
||||
:wnode wnode
|
||||
:peers peers}}
|
||||
(do
|
||||
(log/info "Peer" wnode "is not registered. Retrying fetch peers.")
|
||||
(let [delay (if (< retries 3) 300 5000)]
|
||||
(if (> retries 10)
|
||||
(log/error "Could not connect to mailserver")
|
||||
{:dispatch-later [{:ms delay :dispatch [::fetch-peers (inc retries)]}]})))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::get-sym-key
|
||||
;; TODO(yenda): using core async flow this event can be done in parallel
|
||||
;; with add-peer
|
||||
(fn [{:keys [db]} [_ response]]
|
||||
(let [web3 (:web3 db)
|
||||
wnode (get-wnode db)
|
||||
password (:inbox/password db)]
|
||||
(log/info "offline inbox: mark-trusted-peer response" wnode response)
|
||||
{:shh/generate-sym-key-from-password {:password password
|
||||
:web3 web3
|
||||
:on-success (fn [_ sym-key-id]
|
||||
(re-frame/dispatch [::request-messages sym-key-id]))
|
||||
:on-error #(log/error "offline inbox: get-sym-key error" %)}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::request-messages
|
||||
;; TODO(yenda): we want to request-message once per topic and for specific timespan so
|
||||
;; we want a plural version of this function that does the right thing
|
||||
(fn [{:keys [db]} [_ sym-key-id]]
|
||||
(log/info "offline inbox: get-sym-key response") sym-key-id
|
||||
(let [web3 (:web3 db)
|
||||
wnode (get-wnode db)
|
||||
topics (map #(:topic %) (vals (:transport/chats db)))
|
||||
to nil
|
||||
from nil]
|
||||
{::request-messages {:wnode wnode
|
||||
:topics topics
|
||||
:to to
|
||||
:from from
|
||||
:sym-key-id sym-key-id
|
||||
:web3 web3}})))
|
|
@ -0,0 +1,7 @@
|
|||
(ns ^{:doc "Definition of the StatusMessage protocol"}
|
||||
status-im.transport.message.core)
|
||||
|
||||
(defprotocol StatusMessage
|
||||
"Protocol for the messages that are sent through the transport layer"
|
||||
(send [this chat-id cofx] "Method producing all effects necessary for sending the message record")
|
||||
(receive [this chat-id signature cofx] "Method producing all effects necessary for receiving the message record"))
|
|
@ -0,0 +1,123 @@
|
|||
(ns ^{:doc "Transit custom readers and writers, required when adding a new record implementing StatusMessage protocol"}
|
||||
status-im.transport.message.transit
|
||||
(:require [status-im.transport.message.v1.contact :as v1.contact]
|
||||
[status-im.transport.message.v1.protocol :as v1.protocol]
|
||||
[status-im.transport.message.v1.group-chat :as v1.group-chat]
|
||||
[cognitect.transit :as transit]))
|
||||
|
||||
;; When adding a new reccord implenting the StatusMessage protocol it is required to implement:
|
||||
;; - a handler that will turn the clojure record into a javascript datastructure.
|
||||
;; - a reader that will turn the javascript datastructure back into a clojure record.
|
||||
|
||||
;; Use the existing types as exemples of how this is done
|
||||
|
||||
;;
|
||||
;; Writer handlers
|
||||
;;
|
||||
|
||||
;; Each writer defines a tag and a representation
|
||||
;; The tag will determine which reader is used to recreate the clojure record
|
||||
;; When migrating a particular record, it is important to use a different type and still handle the previous
|
||||
;; gracefully for compatibility
|
||||
(deftype NewContactKeyHandler []
|
||||
Object
|
||||
(tag [this v] "c1")
|
||||
(rep [this {:keys [sym-key topic message]}]
|
||||
#js [sym-key topic message]))
|
||||
|
||||
(deftype ContactRequestHandler []
|
||||
Object
|
||||
(tag [this v] "c2")
|
||||
(rep [this {:keys [name profile-image address fcm-token]}]
|
||||
#js [name profile-image address fcm-token]))
|
||||
|
||||
(deftype ContactRequestConfirmedHandler []
|
||||
Object
|
||||
(tag [this v] "c3")
|
||||
(rep [this {:keys [name profile-image address fcm-token]}]
|
||||
#js [name profile-image address fcm-token]))
|
||||
|
||||
(deftype ContactUpdateHandler []
|
||||
Object
|
||||
(tag [this v] "c6")
|
||||
(rep [this {:keys [name profile-image]}]
|
||||
#js [name profile-image]))
|
||||
|
||||
(deftype MessageHandler []
|
||||
Object
|
||||
(tag [this v] "c4")
|
||||
(rep [this {:keys [content content-type message-type to-clock-value timestamp]}]
|
||||
#js [content content-type message-type to-clock-value timestamp]))
|
||||
|
||||
(deftype MessagesSeenHandler []
|
||||
Object
|
||||
(tag [this v] "c5")
|
||||
(rep [this {:keys [message-ids]}]
|
||||
(clj->js message-ids)))
|
||||
|
||||
(deftype NewGroupKeyHandler []
|
||||
Object
|
||||
(tag [this v] "g1")
|
||||
(rep [this {:keys [chat-id sym-key message]}]
|
||||
#js [chat-id sym-key message]))
|
||||
|
||||
(deftype GroupAdminUpdateHandler []
|
||||
Object
|
||||
(tag [this v] "g2")
|
||||
(rep [this {:keys [chat-name participants]}]
|
||||
#js [chat-name participants]))
|
||||
|
||||
(deftype GroupLeaveHandler []
|
||||
Object
|
||||
(tag [this v] "g3")
|
||||
(rep [this _]
|
||||
(clj->js nil)))
|
||||
|
||||
(def writer (transit/writer :json
|
||||
{:handlers
|
||||
{v1.contact/NewContactKey (NewContactKeyHandler.)
|
||||
v1.contact/ContactRequest (ContactRequestHandler.)
|
||||
v1.contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.)
|
||||
v1.contact/ContactUpdate (ContactUpdateHandler.)
|
||||
v1.protocol/Message (MessageHandler.)
|
||||
v1.protocol/MessagesSeen (MessagesSeenHandler.)
|
||||
v1.group-chat/NewGroupKey (NewGroupKeyHandler.)
|
||||
v1.group-chat/GroupAdminUpdate (GroupAdminUpdateHandler.)
|
||||
v1.group-chat/GroupLeave (GroupLeaveHandler.)}}))
|
||||
|
||||
;;
|
||||
;; Reader handlers
|
||||
;;
|
||||
|
||||
;; Here we only need to call the record with the arguments parsed from the clojure datastructures
|
||||
(def reader (transit/reader :json
|
||||
{:handlers
|
||||
{"c1" (fn [[sym-key topic message]]
|
||||
(v1.contact/NewContactKey. sym-key topic message))
|
||||
"c2" (fn [[name profile-image address fcm-token]]
|
||||
(v1.contact/ContactRequest. name profile-image address fcm-token))
|
||||
"c3" (fn [[name profile-image address fcm-token]]
|
||||
(v1.contact/ContactRequestConfirmed. name profile-image address fcm-token))
|
||||
"c4" (fn [[content content-type message-type to-clock-value timestamp]]
|
||||
(v1.protocol/Message. content content-type message-type to-clock-value timestamp))
|
||||
"c5" (fn [message-ids]
|
||||
(v1.protocol/MessagesSeen. message-ids))
|
||||
"c6" (fn [[name profile-image]]
|
||||
(v1.contact/ContactUpdate. name profile-image))
|
||||
"g1" (fn [[chat-id sym-key message]]
|
||||
(v1.group-chat/NewGroupKey. chat-id sym-key message))
|
||||
"g2" (fn [[chat-name participants]]
|
||||
(v1.group-chat/GroupAdminUpdate. chat-name participants))
|
||||
"g3" (fn [_]
|
||||
(v1.group-chat/GroupLeave.))}}))
|
||||
|
||||
|
||||
(defn serialize
|
||||
"Serializes a record implementing the StatusMessage protocol using the custom writers"
|
||||
[o]
|
||||
(transit/write writer o))
|
||||
|
||||
(defn deserialize
|
||||
"Deserializes a record implementing the StatusMessage protocol using the custom readers"
|
||||
[o]
|
||||
(try (transit/read reader o) (catch :default e nil)))
|
|
@ -0,0 +1,134 @@
|
|||
(ns ^{:doc "Contact request and update API"}
|
||||
status-im.transport.message.v1.contact
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.ui.screens.contacts.core :as contacts]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
(defrecord NewContactKey [sym-key topic message]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(protocol/send-with-pubkey {:chat-id chat-id
|
||||
:payload this}
|
||||
cofx))
|
||||
(receive [this chat-id signature cofx]
|
||||
(let [on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [::add-new-sym-key {:sym-key-id sym-key-id
|
||||
:sym-key sym-key
|
||||
:chat-id chat-id
|
||||
:topic topic
|
||||
:message message}]))]
|
||||
(handlers/merge-fx cofx
|
||||
{:shh/add-new-sym-key {:web3 (get-in cofx [:db :web3])
|
||||
:sym-key sym-key
|
||||
:on-success on-success}}
|
||||
(protocol/init-chat chat-id topic)))))
|
||||
|
||||
(defrecord ContactRequest [name profile-image address fcm-token]
|
||||
message/StatusMessage
|
||||
(send [this chat-id {:keys [db random-id] :as cofx}]
|
||||
(let [message-id (transport.utils/message-id this)
|
||||
topic (transport.utils/get-topic random-id)
|
||||
on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [::send-new-sym-key {:sym-key-id sym-key-id
|
||||
:sym-key sym-key
|
||||
:chat-id chat-id
|
||||
:topic topic
|
||||
:message this}]))]
|
||||
(handlers/merge-fx cofx
|
||||
{:shh/get-new-sym-key {:web3 (:web3 db)
|
||||
:on-success on-success}}
|
||||
(protocol/init-chat chat-id topic)
|
||||
#_(protocol/requires-ack message-id chat-id))))
|
||||
(receive [this chat-id signature {:keys [db] :as cofx}]
|
||||
(let [message-id (transport.utils/message-id this)]
|
||||
(when (protocol/is-new? message-id)
|
||||
(handlers/merge-fx cofx
|
||||
#_(protocol/ack message-id chat-id)
|
||||
(contacts/receive-contact-request signature
|
||||
this))))))
|
||||
|
||||
(defrecord ContactRequestConfirmed [name profile-image address fcm-token]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(let [message-id (transport.utils/message-id this)]
|
||||
(handlers/merge-fx cofx
|
||||
#_(protocol/requires-ack message-id chat-id)
|
||||
(protocol/send {:chat-id chat-id
|
||||
:payload this}))))
|
||||
(receive [this chat-id signature cofx]
|
||||
(let [message-id (transport.utils/message-id this)]
|
||||
(when (protocol/is-new? message-id)
|
||||
(handlers/merge-fx cofx
|
||||
#_(protocol/ack message-id chat-id)
|
||||
(contacts/receive-contact-request-confirmation signature
|
||||
this))))))
|
||||
|
||||
(defrecord ContactUpdate [name profile-image]
|
||||
message/StatusMessage
|
||||
(send [this _ {:keys [db] :as cofx}]
|
||||
(let [message-id (transport.utils/message-id this)
|
||||
public-keys (remove nil? (map :public-key (vals (:contacts/contacts db))))]
|
||||
(handlers/merge-fx cofx
|
||||
(protocol/multi-send-with-pubkey {:public-keys public-keys
|
||||
:payload this}))))
|
||||
(receive [this chat-id signature cofx]
|
||||
(let [message-id (transport.utils/message-id this)]
|
||||
(when (protocol/is-new? message-id)
|
||||
(handlers/merge-fx cofx
|
||||
(contacts/receive-contact-update chat-id
|
||||
signature
|
||||
this))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::send-new-sym-key
|
||||
(fn [{:keys [db random-id] :as cofx} [_ {:keys [chat-id topic message sym-key sym-key-id]}]]
|
||||
(let [{:keys [web3 current-public-key]} db
|
||||
chat-transport-info (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(assoc :sym-key sym-key))]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic topic
|
||||
:chat-id chat-id}
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat chat-transport-info}}
|
||||
(message/send (NewContactKey. sym-key topic message)
|
||||
chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::add-new-sym-key
|
||||
(fn [{:keys [db] :as cofx} [_ {:keys [sym-key-id sym-key chat-id topic message]}]]
|
||||
(let [{:keys [web3 current-public-key]} db
|
||||
chat-transport-info (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(assoc :sym-key sym-key))]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic topic
|
||||
:chat-id chat-id}
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat chat-transport-info}}
|
||||
(message/receive message chat-id chat-id)))))
|
||||
|
||||
#_(handlers/register-handler-fx
|
||||
:send-test-message
|
||||
(fn [cofx [this timer chat-id n]]
|
||||
(if (zero? n)
|
||||
(println "Time: " (str (- (inst-ms (js/Date.)) @timer)))
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch [this timer chat-id (dec n)]}
|
||||
(message/send (protocol/map->Message {:content (str n)
|
||||
:content-type "text/plain"
|
||||
:message-type :user-message
|
||||
:clock-value n
|
||||
:timestamp (str (inst-ms (js/Date.)))})
|
||||
chat-id)))))
|
|
@ -0,0 +1,179 @@
|
|||
(ns ^{:doc "Group chat API"}
|
||||
status-im.transport.message.v1.group-chat
|
||||
(:require [clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.screens.group.core :as group]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.utils :as transport.utils]))
|
||||
|
||||
|
||||
;; NOTE: We ignore the chat-id from the send and receive method.
|
||||
;; The chat-id is usually deduced from the filter the message comes from but not in that case because it is sent
|
||||
;; individually to each participant of the group.
|
||||
;; In order to be able to determine which group the message belongs to the chat-id is therefore
|
||||
;; passed in the message itself
|
||||
(defrecord NewGroupKey [chat-id sym-key message]
|
||||
message/StatusMessage
|
||||
(send [this _ cofx]
|
||||
(let [public-keys (map :identity (get-in cofx [:db :chats chat-id :contacts]))]
|
||||
(protocol/multi-send-with-pubkey {:public-keys public-keys
|
||||
:chat-id chat-id
|
||||
:payload this}
|
||||
cofx)))
|
||||
(receive [this _ signature {:keys [db] :as cofx}]
|
||||
(handlers/merge-fx cofx
|
||||
{:shh/add-new-sym-key {:web3 (:web3 db)
|
||||
:sym-key sym-key
|
||||
:on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [::add-new-sym-key {:chat-id chat-id
|
||||
:sym-key sym-key
|
||||
:sym-key-id sym-key-id
|
||||
:message message}]))}}
|
||||
(protocol/init-chat chat-id))))
|
||||
|
||||
(defn- user-is-group-admin? [chat-id cofx]
|
||||
(= (get-in cofx [:db :chats chat-id :group-admin])
|
||||
(get-in cofx [:db :current-public-key])))
|
||||
|
||||
(defn- send-new-group-key [message chat-id cofx]
|
||||
(when (user-is-group-admin? chat-id cofx)
|
||||
{:shh/get-new-sym-key {:web3 (get-in cofx [:db :web3])
|
||||
:on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [::send-new-sym-key {:chat-id chat-id
|
||||
:sym-key sym-key
|
||||
:sym-key-id sym-key-id
|
||||
:message message}]))}}))
|
||||
|
||||
(defn- prepare-system-message [admin-name added-participants removed-participants contacts]
|
||||
(let [added-participants-names (map #(get-in contacts [% :name] %) added-participants)
|
||||
removed-participants-names (map #(get-in contacts [% :name] %) removed-participants)]
|
||||
(cond
|
||||
(and added-participants removed-participants)
|
||||
(str admin-name " "
|
||||
(i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names))
|
||||
" and "
|
||||
(i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names)))
|
||||
|
||||
added-participants
|
||||
(str admin-name " " (i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names)))
|
||||
|
||||
removed-participants
|
||||
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
|
||||
|
||||
(defn- init-chat-if-new [chat-id cofx]
|
||||
(if (nil? (get-in cofx [:db :transport/chats chat-id]))
|
||||
(protocol/init-chat chat-id cofx)))
|
||||
|
||||
(defn- participants-diff [existing-participants-set new-participants-set]
|
||||
{:removed (set/difference existing-participants-set new-participants-set)
|
||||
:added (set/difference new-participants-set existing-participants-set)})
|
||||
|
||||
(defrecord GroupAdminUpdate [chat-name participants]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(handlers/merge-fx cofx
|
||||
(init-chat-if-new chat-id)
|
||||
(send-new-group-key this chat-id)))
|
||||
(receive [this chat-id signature {:keys [now db] :as cofx}]
|
||||
(let [me (:current-public-key db)]
|
||||
;; we have to check if we already have a chat, or it's a new one
|
||||
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
|
||||
;; update for existing group chat
|
||||
(when (= signature group-admin) ;; make sure that admin is the one making changes
|
||||
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
|
||||
admin-name (or (get-in cofx [db :contacts/contacts group-admin :name])
|
||||
group-admin)
|
||||
message-id (transport.utils/message-id this)]
|
||||
(if (removed me) ;; we were removed
|
||||
(handlers/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id message-id now
|
||||
(str admin-name " " (i18n/label :t/removed-from-chat))))
|
||||
(models.chat/update-chat {:chat-id chat-id
|
||||
:removed-from-at now
|
||||
:is-active false})
|
||||
(transport/unsubscribe-from-chat chat-id))
|
||||
(handlers/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id message-id now
|
||||
(prepare-system-message admin-name
|
||||
added
|
||||
removed
|
||||
(:contacts/contacts db))))
|
||||
(group/participants-added chat-id added)
|
||||
(group/participants-removed chat-id removed)))))
|
||||
;; first time we hear about chat -> create it if we are among participants
|
||||
(when (get (set participants) me)
|
||||
(models.chat/add-group-chat chat-id chat-name signature participants cofx))))))
|
||||
|
||||
(defrecord GroupLeave []
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(protocol/send {:chat-id chat-id
|
||||
:payload this
|
||||
:success-event [::unsubscribe-from-chat chat-id]}
|
||||
cofx))
|
||||
(receive [this chat-id signature {:keys [db now] :as cofx}]
|
||||
(let [message-id (transport.utils/message-id this)
|
||||
me (:current-public-key db)
|
||||
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
|
||||
signature)]
|
||||
(when-not (= me signature)
|
||||
(handlers/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id message-id now
|
||||
(str participant-leaving-name " " (i18n/label :t/left))))
|
||||
(group/participants-removed chat-id #{signature})
|
||||
(send-new-group-key nil chat-id))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::unsubscribe-from-chat
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id]]
|
||||
(transport/unsubscribe-from-chat chat-id cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::send-new-sym-key
|
||||
[re-frame/trim-v]
|
||||
;; this is the event that is called when we want to send a message that required first
|
||||
;; some async operations
|
||||
(fn [{:keys [db] :as cofx} [{:keys [chat-id message sym-key sym-key-id]}]]
|
||||
(let [{:keys [web3]} db]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic (transport.utils/get-topic chat-id)
|
||||
:chat-id chat-id}
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(assoc :sym-key sym-key))}}
|
||||
(message/send (NewGroupKey. chat-id sym-key message) chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::add-new-sym-key
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db] :as cofx} [{:keys [sym-key-id sym-key chat-id message]}]]
|
||||
(let [{:keys [web3 current-public-key]} db
|
||||
fx {:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic (transport.utils/get-topic chat-id)
|
||||
:chat-id chat-id}
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(assoc :sym-key sym-key))}}]
|
||||
;; if new sym-key is wrapping some message, call receive on it as well, if not just update the transport layer
|
||||
(if message
|
||||
(handlers/merge-fx cofx fx (message/receive message chat-id chat-id))
|
||||
fx))))
|
|
@ -0,0 +1,116 @@
|
|||
(ns ^{:doc "Protocol API and protocol utils"}
|
||||
status-im.transport.message.v1.protocol
|
||||
(:require [status-im.utils.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.chat.core :as chat]
|
||||
[status-im.transport.message-cache :as message-cache]
|
||||
[status-im.transport.db :as transport.db]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.utils :as transport.utils]))
|
||||
|
||||
(def ^:private whisper-opts
|
||||
{:ttl 10 ;; ttl of 10 sec
|
||||
:powTarget config/pow-target
|
||||
:powTime config/pow-time})
|
||||
|
||||
(defn init-chat
|
||||
"Initialises chat on protocol layer.
|
||||
2 arities are provided, 2arg arity initialises the chat with topic derived from first argument (`chat-id`),
|
||||
using the 3arg arity, you can specify the topic as well (second argument)"
|
||||
([chat-id cofx]
|
||||
(init-chat chat-id (transport.utils/get-topic chat-id) cofx))
|
||||
([chat-id topic {:keys [db] :as cofx}]
|
||||
{:db (assoc-in db [:transport/chats chat-id] (transport.db/create-chat topic))}))
|
||||
|
||||
(defn is-new?
|
||||
"Determines if given message-id already exists in in-memory message cache"
|
||||
[message-id]
|
||||
(when-not (message-cache/exists? message-id)
|
||||
(message-cache/add! message-id)))
|
||||
|
||||
#_(defn requires-ack [message-id chat-id {:keys [db] :as cofx}]
|
||||
{:db (update-in db [:transport/chats chat-id :pending-ack] conj message-id)})
|
||||
|
||||
#_(defn ack [message-id chat-id {:keys [db] :as cofx}]
|
||||
{:db (update-in db [:transport/chats chat-id :ack] conj message-id)})
|
||||
|
||||
(defn send
|
||||
"Sends the payload using symetric key and topic from db (looked up by `chat-id`)"
|
||||
[{:keys [payload chat-id success-event]} {:keys [db] :as cofx}]
|
||||
;; we assume that the chat contains the contact public-key
|
||||
(let [{:keys [current-public-key web3]} db
|
||||
{:keys [sym-key-id topic]} (get-in db [:transport/chats chat-id])]
|
||||
{:shh/post {:web3 web3
|
||||
:success-event success-event
|
||||
:message (merge {:sig current-public-key
|
||||
:symKeyID sym-key-id
|
||||
:payload payload
|
||||
:topic topic}
|
||||
whisper-opts)}}))
|
||||
|
||||
(defn send-with-pubkey
|
||||
"Sends the payload using asymetric key (`:current-public-key` in db) and fixed discovery topic"
|
||||
[{:keys [payload chat-id success-event]} {:keys [db] :as cofx}]
|
||||
(let [{:keys [current-public-key web3]} db]
|
||||
{:shh/post {:web3 web3
|
||||
:success-event success-event
|
||||
:message (merge {:sig current-public-key
|
||||
:pubKey chat-id
|
||||
:payload payload
|
||||
:topic (transport.utils/get-topic constants/contact-discovery)}
|
||||
whisper-opts)}}))
|
||||
|
||||
(defn- prepare-recipients [public-keys db]
|
||||
(map (fn [public-key]
|
||||
(select-keys (get-in db [:transport/chats public-key]) [:topic :sym-key-id]))
|
||||
public-keys))
|
||||
|
||||
(defn multi-send-with-pubkey
|
||||
"Sends payload to multiple participants selected by `:public-keys` key."
|
||||
[{:keys [payload public-keys success-event]} {:keys [db] :as cofx}]
|
||||
(let [{:keys [current-public-key web3]} db
|
||||
recipients (prepare-recipients public-keys db)]
|
||||
{:shh/multi-post {:web3 web3
|
||||
:success-event success-event
|
||||
:recipients recipients
|
||||
:message (merge {:sig current-public-key
|
||||
:payload payload}
|
||||
whisper-opts)}}))
|
||||
|
||||
;; TODO currently not used
|
||||
(defrecord Ack [message-ids]
|
||||
message/StatusMessage
|
||||
(send [this cofx chat-id])
|
||||
(receive [this db chat-id sig]))
|
||||
|
||||
(defrecord Seen [message-ids]
|
||||
message/StatusMessage
|
||||
(send [this cofx chat-id])
|
||||
(receive [this cofx chat-id sig]))
|
||||
|
||||
(defrecord Message [content content-type message-type to-clock-value timestamp]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(send {:chat-id chat-id
|
||||
:payload this
|
||||
:success-event [:update-message-status
|
||||
chat-id
|
||||
(transport.utils/message-id this)
|
||||
(get-in cofx [:db :current-public-key])
|
||||
:sent]}
|
||||
cofx))
|
||||
(receive [this chat-id signature cofx]
|
||||
{:dispatch [:chat-received-message/add (assoc (into {} this)
|
||||
:message-id (transport.utils/message-id this)
|
||||
:chat-id chat-id
|
||||
:from signature)]}))
|
||||
|
||||
(defrecord MessagesSeen [message-ids]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(send {:chat-id chat-id
|
||||
:payload this}
|
||||
cofx))
|
||||
(receive [this chat-id signature cofx]
|
||||
(chat/receive-seen chat-id signature (:message-ids this) cofx)))
|
|
@ -0,0 +1,36 @@
|
|||
(ns ^{:doc "Public chat API"}
|
||||
status-im.transport.message.v1.public-chat
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.utils :as transport.utils]))
|
||||
|
||||
(defn join-public-chat
|
||||
"Function producing all protocol level effects necessary for joining public chat identified by chat-id"
|
||||
[chat-id {:keys [db] :as cofx}]
|
||||
(let [on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [::add-new-sym-key {:chat-id chat-id
|
||||
:sym-key sym-key
|
||||
:sym-key-id sym-key-id}]))]
|
||||
(handlers/merge-fx cofx
|
||||
{:shh/generate-sym-key-from-password {:web3 (:web3 db)
|
||||
:password chat-id
|
||||
:on-success on-success}}
|
||||
(protocol/init-chat chat-id))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::add-new-sym-key
|
||||
(fn [{:keys [db] :as cofx} [_ {:keys [sym-key-id sym-key chat-id]}]]
|
||||
(let [{:keys [web3]} db]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
|
||||
:shh/add-filter {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:topic (transport.utils/get-topic chat-id)
|
||||
:chat-id chat-id}
|
||||
:data-store.transport/save {:chat-id chat-id
|
||||
:chat (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id)
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(assoc :sym-key sym-key))}}))))
|
|
@ -0,0 +1,19 @@
|
|||
(ns ^{:doc "Message caching API for message deduplication"}
|
||||
status-im.transport.message-cache
|
||||
(:refer-clojure :exclude [exists?]))
|
||||
|
||||
(defonce messages-set (atom #{}))
|
||||
|
||||
(defn init!
|
||||
[messages]
|
||||
(reset! messages-set (set (map :message-id messages))))
|
||||
|
||||
(defn add!
|
||||
[message-id]
|
||||
(when message-id
|
||||
(swap! messages-set conj message-id)))
|
||||
|
||||
(defn exists?
|
||||
[message-id]
|
||||
(when message-id
|
||||
(@messages-set message-id)))
|
|
@ -0,0 +1,169 @@
|
|||
(ns ^{:doc "Whisper API and events for managing keys and posting messages"}
|
||||
status-im.transport.shh
|
||||
(:require [taoensso.timbre :as log]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.transit :as transit]))
|
||||
|
||||
(defn get-new-key-pair [{:keys [web3 on-success on-error]}]
|
||||
(if web3
|
||||
(.. web3
|
||||
-shh
|
||||
(newKeyPair (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err)))))
|
||||
(on-error "web3 not available.")))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/get-new-key-pair
|
||||
(fn [{:keys [web3 success-event error-event]}]
|
||||
(get-new-key-pair {:web3 web3
|
||||
:on-success #(re-frame/dispatch [success-event %])
|
||||
:on-error #(re-frame/dispatch [error-event %])})))
|
||||
|
||||
(defn get-public-key [{:keys [web3 key-pair-id on-success on-error]}]
|
||||
(if (and web3 key-pair-id)
|
||||
(.. web3
|
||||
-shh
|
||||
(getPublicKey key-pair-id (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err)))))
|
||||
(on-error "web3 or key-pair id not available.")))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/get-public-key
|
||||
(fn [{:keys [web3 key-pair-id success-event error-event]}]
|
||||
(get-public-key {:web3 web3
|
||||
:key-pair-id key-pair-id
|
||||
:on-success #(re-frame/dispatch [success-event %])
|
||||
:on-error #(re-frame/dispatch [error-event %])})))
|
||||
|
||||
(defn generate-sym-key-from-password
|
||||
[{:keys [web3 password on-success on-error]}]
|
||||
(.. web3
|
||||
-shh
|
||||
(generateSymKeyFromPassword password (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))))
|
||||
|
||||
(defn post-message
|
||||
[{:keys [web3 whisper-message on-success on-error]}]
|
||||
(.. web3
|
||||
-shh
|
||||
(post (clj->js whisper-message) (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/post
|
||||
(fn [{:keys [web3 message success-event error-event]
|
||||
:or {error-event :protocol/send-status-message-error}}]
|
||||
(post-message {:web3 web3
|
||||
:whisper-message (update message :payload (comp transport.utils/from-utf8
|
||||
transit/serialize))
|
||||
:on-success (if success-event
|
||||
#(re-frame/dispatch success-event)
|
||||
#(log/debug :shh/post-success))
|
||||
:on-error #(re-frame/dispatch [error-event %])})))
|
||||
|
||||
;; This event params contain a recipients key because it's a vector of map with public-key and topic keys.
|
||||
;; the :shh/post event has public-key and topic keys at the top level of the args map.
|
||||
;; This event is used to send messages to multiple recipients when you can't send it on a topic.
|
||||
;; It is used for renewing keys in a private group chat, because if someone leaves/join.
|
||||
;; We want to change the symmetric key but we can't do that in the group topic with the old key
|
||||
;; otherwise leavers can still eavesdrop / joiners can read past history."
|
||||
(re-frame/reg-fx
|
||||
:shh/multi-post
|
||||
(fn [{:keys [web3 message recipients success-event error-event]
|
||||
:or {error-event :protocol/send-status-message-error}}]
|
||||
(let [whisper-message (update message :payload (comp transport.utils/from-utf8
|
||||
transit/serialize))]
|
||||
(doseq [{:keys [sym-key-id topic]} recipients]
|
||||
(post-message {:web3 web3
|
||||
:whisper-message (assoc whisper-message
|
||||
:symKeyID sym-key-id
|
||||
:topic topic)
|
||||
:on-success (if success-event
|
||||
#(re-frame/dispatch success-event)
|
||||
#(log/debug :shh/post-success))
|
||||
:on-error #(re-frame/dispatch [error-event %])})))))
|
||||
|
||||
(defn add-sym-key
|
||||
[{:keys [web3 sym-key on-success on-error]}]
|
||||
(.. web3
|
||||
-shh
|
||||
(addSymKey sym-key (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))))
|
||||
|
||||
(defn get-sym-key
|
||||
[{:keys [web3 sym-key-id on-success on-error]}]
|
||||
(.. web3
|
||||
-shh
|
||||
(getSymKey sym-key-id (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))))
|
||||
|
||||
(defn new-sym-key
|
||||
[{:keys [web3 on-success on-error]}]
|
||||
(.. web3
|
||||
-shh
|
||||
(newSymKey (fn [err resp]
|
||||
(if-not err
|
||||
(on-success resp)
|
||||
(on-error err))))))
|
||||
|
||||
(defn log-error [error]
|
||||
(log/error :shh/get-new-sym-key-error error))
|
||||
|
||||
|
||||
;;TODO (yenda) remove once go implements persistence
|
||||
(re-frame/reg-fx
|
||||
:shh/restore-sym-keys
|
||||
(fn [{:keys [web3 transport on-success]}]
|
||||
(doseq [[chat-id {:keys [sym-key]}] transport]
|
||||
(add-sym-key {:web3 web3
|
||||
:sym-key sym-key
|
||||
:on-success (fn [sym-key-id]
|
||||
(on-success chat-id sym-key sym-key-id))
|
||||
:on-error log-error}))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/add-new-sym-key
|
||||
(fn [{:keys [web3 sym-key on-success]}]
|
||||
(add-sym-key {:web3 web3
|
||||
:sym-key sym-key
|
||||
:on-success (fn [sym-key-id]
|
||||
(on-success sym-key sym-key-id))
|
||||
:on-error log-error})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/get-new-sym-key
|
||||
(fn [{:keys [web3 on-success]}]
|
||||
(new-sym-key {:web3 web3
|
||||
:on-success (fn [sym-key-id]
|
||||
(get-sym-key {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:on-success (fn [sym-key]
|
||||
(on-success sym-key sym-key-id))
|
||||
:on-error log-error}))
|
||||
:on-error log-error})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:shh/generate-sym-key-from-password
|
||||
(fn [{:keys [web3 password on-success]}]
|
||||
(generate-sym-key-from-password {:web3 web3
|
||||
:password password
|
||||
:on-success (fn [sym-key-id]
|
||||
(get-sym-key {:web3 web3
|
||||
:sym-key-id sym-key-id
|
||||
:on-success (fn [sym-key]
|
||||
(on-success sym-key sym-key-id))
|
||||
:on-error log-error}))
|
||||
:on-error log-error})))
|
|
@ -1,4 +1,5 @@
|
|||
(ns status-im.protocol.web3.utils
|
||||
(ns ^{:doc "Utils for transport layer"}
|
||||
status-im.transport.utils
|
||||
(:require [cljs-time.coerce :refer [to-long]]
|
||||
[cljs-time.core :refer [now]]
|
||||
[clojure.string :as string]
|
||||
|
@ -13,6 +14,19 @@
|
|||
(defn to-utf8 [s]
|
||||
(.toUtf8 dependencies/Web3.prototype (str s)))
|
||||
|
||||
(defn sha3 [s]
|
||||
(.sha3 dependencies/Web3.prototype s))
|
||||
|
||||
(defn message-id
|
||||
"Get a message-id"
|
||||
[message]
|
||||
(sha3 (pr-str message)))
|
||||
|
||||
(defn get-topic
|
||||
"Get the topic of a group chat or public chat from the chat-id"
|
||||
[chat-id]
|
||||
(subs (sha3 chat-id) 0 10))
|
||||
|
||||
(defn shh [web3]
|
||||
(.-shh web3))
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
(defn- wrap-key-fn [f]
|
||||
(fn [data index]
|
||||
{:post [(string? %)]}
|
||||
{:post [(some? %)]}
|
||||
(f data index)))
|
||||
|
||||
(def default-separator [react/view styles/separator])
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
(ns status-im.ui.screens.accounts.events
|
||||
(:require [status-im.data-store.accounts :as accounts-store]
|
||||
[re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.types :refer [json->clj]]
|
||||
[status-im.utils.identicon :refer [identicon]]
|
||||
|
@ -15,20 +13,13 @@
|
|||
[status-im.utils.gfycat.core :refer [generate-gfy]]
|
||||
[status-im.utils.hex :as utils.hex]
|
||||
[status-im.constants :as constants]
|
||||
status-im.ui.screens.accounts.create.navigation))
|
||||
[status-im.transport.message.v1.contact :as message.contact]
|
||||
[status-im.transport.message.core :as transport]
|
||||
status-im.ui.screens.accounts.create.navigation
|
||||
[status-im.chat.models :as chat.models]))
|
||||
|
||||
;;;; COFX
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-new-keypair!
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :keypair (protocol/new-keypair!))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::get-all-accounts
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :all-accounts (accounts-store/get-all))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
::get-signing-phrase
|
||||
(fn [coeffects _]
|
||||
|
@ -41,11 +32,6 @@
|
|||
|
||||
;;;; FX
|
||||
|
||||
(re-frame/reg-fx
|
||||
::save-account
|
||||
(fn [account]
|
||||
(accounts-store/save account true)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::create-account
|
||||
(fn [password]
|
||||
|
@ -53,33 +39,6 @@
|
|||
password
|
||||
#(re-frame/dispatch [::account-created (json->clj %) password]))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::broadcast-account-update
|
||||
(fn [{:keys [current-public-key web3 name photo-path status
|
||||
updates-public-key updates-private-key]}]
|
||||
(when web3
|
||||
(protocol/broadcast-profile!
|
||||
{:web3 web3
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)
|
||||
:keypair {:public updates-public-key
|
||||
:private updates-private-key}
|
||||
:payload {:profile {:name name
|
||||
:status status
|
||||
:profile-image photo-path}}}}))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::send-keys-update
|
||||
(fn [{:keys [web3 current-public-key contacts
|
||||
updates-public-key updates-private-key]}]
|
||||
(doseq [id (handlers/identities contacts)]
|
||||
(protocol/update-keys!
|
||||
{:web3 web3
|
||||
:message {:from current-public-key
|
||||
:to id
|
||||
:message-id (random/id)
|
||||
:payload {:keypair {:public updates-public-key
|
||||
:private updates-private-key}}}}))))
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
|
@ -96,8 +55,8 @@
|
|||
:networks networks
|
||||
:wnode wnode
|
||||
:address address)]
|
||||
{:db (assoc-in db [:accounts/accounts address] enriched-account)
|
||||
::save-account enriched-account}))
|
||||
{:db (assoc-in db [:accounts/accounts address] enriched-account)
|
||||
:data-store/save-account enriched-account}))
|
||||
|
||||
;; TODO(janherich) we have this handler here only because of the tests, refactor/improve tests ASAP
|
||||
(handlers/register-handler-fx
|
||||
|
@ -107,17 +66,16 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
::account-created
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-new-keypair!)
|
||||
(re-frame/inject-cofx ::get-signing-phrase) (re-frame/inject-cofx ::get-status)]
|
||||
(fn [{:keys [keypair signing-phrase status db]} [{:keys [pubkey address mnemonic]} password]]
|
||||
[re-frame/trim-v (re-frame/inject-cofx ::get-signing-phrase) (re-frame/inject-cofx ::get-status)]
|
||||
(fn [{:keys [signing-phrase status db] :as cofx} [{:keys [pubkey address mnemonic]} password]]
|
||||
(let [normalized-address (utils.hex/normalize-hex address)
|
||||
account {:public-key pubkey
|
||||
:address normalized-address
|
||||
:name (generate-gfy pubkey)
|
||||
:status status
|
||||
:signed-up? true
|
||||
:updates-public-key (:public keypair)
|
||||
:updates-private-key (:private keypair)
|
||||
:updates-public-key "public"
|
||||
:updates-private-key "private"
|
||||
:photo-path (identicon pubkey)
|
||||
:signing-phrase signing-phrase
|
||||
:mnemonic mnemonic
|
||||
|
@ -129,7 +87,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:load-accounts
|
||||
[(re-frame/inject-cofx ::get-all-accounts)]
|
||||
[(re-frame/inject-cofx :data-store/get-all-accounts)]
|
||||
(fn [{:keys [db all-accounts]} _]
|
||||
(let [accounts (->> all-accounts
|
||||
(map (fn [{:keys [address] :as account}]
|
||||
|
@ -147,66 +105,48 @@
|
|||
(fn [{{:accounts/keys [accounts] :networks/keys [networks] :as db} :db} [_ id]]
|
||||
(let [current-account (get accounts id)
|
||||
new-account (assoc current-account :networks networks)]
|
||||
{:db (assoc-in db [:accounts/accounts id] new-account)
|
||||
::save-account new-account})))
|
||||
{:db (assoc-in db [:accounts/accounts id] new-account)
|
||||
:data-store/save-account new-account})))
|
||||
|
||||
(defn update-wallet-settings [{:accounts/keys [current-account-id accounts] :as db} settings]
|
||||
(let [new-account (-> (get accounts current-account-id)
|
||||
(assoc :settings settings))]
|
||||
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
|
||||
::save-account new-account}))
|
||||
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
|
||||
:data-store/save-account new-account}))
|
||||
|
||||
(defn account-update
|
||||
"Takes effects (containing :db) + new account fields, adds all effects necessary for account update."
|
||||
[{{:accounts/keys [accounts current-account-id] :as db} :db :as fx} new-account-fields]
|
||||
(let [current-account (get accounts current-account-id)
|
||||
new-account (merge current-account new-account-fields)
|
||||
broadcast-fields [:name :photo-path :status
|
||||
:updates-public-key :updates-private-key]]
|
||||
(cond-> fx
|
||||
true
|
||||
(assoc-in [:db :accounts/accounts current-account-id] new-account)
|
||||
true
|
||||
(assoc ::save-account new-account)
|
||||
|
||||
(seq (clojure.set/intersection (set (keys new-account-fields)) (set broadcast-fields)))
|
||||
(assoc ::broadcast-account-update (merge (select-keys db [:current-public-key :web3])
|
||||
(select-keys new-account broadcast-fields))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:account-update-keys
|
||||
[(re-frame/inject-cofx :get-new-keypair!)]
|
||||
(fn [{:keys [db keypair now]} _]
|
||||
(let [{:accounts/keys [accounts current-account-id]} db
|
||||
{:keys [public private]} keypair
|
||||
current-account (get accounts current-account-id)
|
||||
new-account (merge current-account {:updates-public-key public
|
||||
:updates-private-key private
|
||||
:last-updated now})]
|
||||
{:db (assoc-in db [:accounts/accounts current-account-id] new-account)
|
||||
::save-account new-account
|
||||
::send-keys-update (merge
|
||||
(select-keys db [:web3 :current-public-key :contacts])
|
||||
(select-keys new-account [:updates-public-key
|
||||
:updates-private-key]))})))
|
||||
"Takes effects (containing :db) + new account fields, adds all effects necessary for account update.
|
||||
Optionally, one can specify event to be dispatched after fields are persisted."
|
||||
([new-account-fields cofx]
|
||||
(account-update new-account-fields nil cofx))
|
||||
([new-account-fields after-update-event {{:accounts/keys [accounts current-account-id] :as db} :db :as cofx}]
|
||||
(let [current-account (get accounts current-account-id)
|
||||
new-account (merge current-account new-account-fields)
|
||||
fx {:db (assoc-in db [:accounts/accounts current-account-id] new-account)
|
||||
:data-store/save-account (assoc new-account :after-update-event after-update-event)}
|
||||
{:keys [name photo-path]} new-account]
|
||||
(if (or (:name new-account-fields) (:photo-path new-account-fields))
|
||||
(handlers/merge-fx cofx fx (transport/send (message.contact/ContactUpdate. name photo-path) nil))
|
||||
fx))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-account-update-if-needed
|
||||
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db now :now} _]
|
||||
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db now :now :as cofx} _]
|
||||
(let [{:keys [last-updated]} (get accounts current-account-id)
|
||||
needs-update? (> (- now last-updated) time/week)]
|
||||
needs-update? (> (- now last-updated) time/week)]
|
||||
(log/info "Need to send account-update: " needs-update?)
|
||||
(when needs-update?
|
||||
;; TODO(janherich): this is very strange and misleading, need to figure out why it'd necessary to update
|
||||
;; account with network update when last update was more then week ago
|
||||
(account-update {:db db} nil)))))
|
||||
(account-update nil cofx)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:account-set-name
|
||||
(fn [{{:accounts/keys [create] :as db} :db} _]
|
||||
(-> {:db (assoc-in db [:accounts/create :show-welcome?] true)
|
||||
:dispatch [:navigate-to-clean :usage-data [:account-finalized]]}
|
||||
(account-update {:name (:name create)}))))
|
||||
(fn [{{:accounts/keys [create] :as db} :db :as cofx} _]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:accounts/create :show-welcome?] true)
|
||||
:dispatch [:navigate-to-clean :usage-data [:account-finalized]]}
|
||||
(account-update {:name (:name create)}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:account-finalized
|
||||
|
@ -222,8 +162,8 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:update-sign-in-time
|
||||
(fn [{db :db now :now} _]
|
||||
(account-update {:db db} {:last-sign-in now})))
|
||||
(fn [{db :db now :now :as cofx} _]
|
||||
(account-update {:last-sign-in now} cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:reset-account-creation
|
||||
|
@ -232,5 +172,5 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:switch-dev-mode
|
||||
(fn [{db :db} [_ dev-mode]]
|
||||
(account-update {:db db} {:dev-mode? dev-mode})))
|
||||
(fn [cofx [_ dev-mode]]
|
||||
(account-update {:dev-mode? dev-mode} cofx)))
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:account-recovered
|
||||
[(re-frame/inject-cofx :get-new-keypair!)]
|
||||
(fn [{:keys [db keypair]} [_ result password]]
|
||||
(let [data (types/json->clj result)
|
||||
public-key (:pubkey data)
|
||||
|
@ -38,14 +37,15 @@
|
|||
:address address
|
||||
:name (gfycat/generate-gfy public-key)
|
||||
:photo-path (identicon/identicon public-key)
|
||||
:updates-public-key public
|
||||
:updates-private-key private
|
||||
;;TODO remove those
|
||||
:updates-public-key "public"
|
||||
:updates-private-key "private"
|
||||
:mnemonic ""
|
||||
:signed-up? true
|
||||
:signing-phrase phrase
|
||||
:settings constants/default-account-settings}]
|
||||
(when-not (string/blank? public-key)
|
||||
(-> db
|
||||
(-> db
|
||||
(accounts-events/add-account account)
|
||||
(assoc :dispatch [:login-account address password])
|
||||
(assoc :dispatch-later [{:ms 2000 :dispatch [:navigate-to :usage-data]}]))))))
|
||||
|
|
|
@ -21,12 +21,13 @@
|
|||
:icon-opts {:color colors/blue}
|
||||
:on-press #(re-frame/dispatch [:navigate-to :new-chat])}]
|
||||
[action-button/action-separator]
|
||||
[action-button/action-button
|
||||
{:label (i18n/label :t/start-group-chat)
|
||||
:accessibility-label :start-group-chat-button
|
||||
:icon :icons/contacts
|
||||
:icon-opts {:color colors/blue}
|
||||
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}]
|
||||
;; TODO temporary removal before everything is fixed in group chats
|
||||
#_[action-button/action-button
|
||||
{:label (i18n/label :t/start-group-chat)
|
||||
:accessibility-label :start-group-chat-button
|
||||
:icon :icons/contacts
|
||||
:icon-opts {:color colors/blue}
|
||||
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}]
|
||||
[action-button/action-separator]
|
||||
[action-button/action-button
|
||||
{:label (i18n/label :t/new-public-group-chat)
|
||||
|
|
|
@ -1,33 +1,17 @@
|
|||
(ns status-im.ui.screens.browser.events
|
||||
(:require status-im.ui.screens.browser.navigation
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.data-store.browser :as browser-store]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:all-stored-browsers
|
||||
(fn [cofx _]
|
||||
(assoc cofx :all-stored-browsers (browser-store/get-all))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:initialize-browsers
|
||||
[(re-frame/inject-cofx :all-stored-browsers)]
|
||||
[(re-frame/inject-cofx :data-store/all-browsers)]
|
||||
(fn [{:keys [db all-stored-browsers]} _]
|
||||
(let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))]
|
||||
{:db (assoc db :browser/browsers browsers)})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:save-browser
|
||||
(fn [browser]
|
||||
(browser-store/save browser)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:remove-browser
|
||||
(fn [browser-id]
|
||||
(browser-store/delete browser-id)))
|
||||
|
||||
(defn match-url [url]
|
||||
(str (when (and url (not (re-find #"^[a-zA-Z-_]+:/" url))) "http://") url))
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
(ns status-im.ui.screens.contacts.core
|
||||
(:require [re-frame.core :as re-frame] [status-im.utils.handlers :as handlers]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.constants :as constants]))
|
||||
|
||||
(defn receive-contact-request
|
||||
[public-key
|
||||
{:keys [name profile-image address fcm-token]}
|
||||
{{:contacts/keys [contacts] :as db} :db :as cofx}]
|
||||
(when-not (get contacts public-key)
|
||||
(let [contact-props {:whisper-identity public-key
|
||||
:public-key public-key
|
||||
:address address
|
||||
:photo-path profile-image
|
||||
:name name
|
||||
:fcm-token fcm-token
|
||||
:pending? true}
|
||||
chat-props {:name name
|
||||
:chat-id public-key
|
||||
:contact-info (prn-str contact-props)}]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (update-in db [:contacts/contacts public-key] merge contact-props)
|
||||
:data-store/save-contact contact-props}
|
||||
(chat.models/add-chat public-key chat-props)))))
|
||||
|
||||
(defn receive-contact-request-confirmation
|
||||
[public-key {:keys [name profile-image address fcm-token]}
|
||||
{{:contacts/keys [contacts] :as db} :db :as cofx}]
|
||||
(when-let [contact (get contacts public-key)]
|
||||
(let [contact-props {:whisper-identity public-key
|
||||
:public-key public-key
|
||||
:address address
|
||||
:photo-path profile-image
|
||||
:name name
|
||||
:fcm-token fcm-token}
|
||||
chat-props {:name name
|
||||
:chat-id public-key}]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (update-in db [:contacts/contacts public-key] merge contact-props)
|
||||
:data-store/save-contact contact-props}
|
||||
(chat.models/upsert-chat chat-props)))))
|
||||
|
||||
|
||||
(defn- update-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
|
||||
(when (get-in db [:contacts/contacts whisper-identity])
|
||||
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
|
||||
:data-store/save-contact contact}))
|
||||
|
||||
(defn receive-contact-update [chat-id public-key {:keys [name profile-image]} {:keys [db now] :as cofx}]
|
||||
(let [{:keys [chats current-public-key]} db]
|
||||
(when (not= current-public-key public-key)
|
||||
(let [prev-last-updated (get-in db [:contacts/contacts public-key :last-updated])]
|
||||
(when (<= prev-last-updated now)
|
||||
(let [contact {:whisper-identity public-key
|
||||
:name name
|
||||
:photo-path profile-image
|
||||
:last-updated now}]
|
||||
(if (chats public-key)
|
||||
(handlers/merge-fx cofx
|
||||
(update-contact contact)
|
||||
(chat.models/update-chat {:chat-id chat-id
|
||||
:name name}))
|
||||
(update-contact contact cofx))))))))
|
|
@ -28,7 +28,7 @@
|
|||
(spec/def :contact/dapp? boolean?)
|
||||
(spec/def :contact/dapp-url (spec/nilable string?))
|
||||
(spec/def :contact/dapp-hash (spec/nilable int?))
|
||||
(spec/def :contact/bot-url (spec/nilable string?))
|
||||
(spec/def :contact/bot-url (spec/nilable string?))
|
||||
(spec/def :contact/command (spec/nilable (spec/map-of int? map?)))
|
||||
(spec/def :contact/response (spec/nilable (spec/map-of int? map?)))
|
||||
(spec/def :contact/jail-loaded? (spec/nilable boolean?))
|
||||
|
@ -48,13 +48,13 @@
|
|||
:contact/status
|
||||
:contact/last-updated
|
||||
:contact/last-online
|
||||
:contact/pending?
|
||||
:contact/hide-contact?
|
||||
:contact/pending?
|
||||
:contact/hide-contact?
|
||||
:contact/unremovable?
|
||||
:contact/dapp?
|
||||
:contact/dapp-url
|
||||
:contact/dapp-hash
|
||||
:contact/bot-url
|
||||
:contact/bot-url
|
||||
:contact/jail-loaded?
|
||||
:contact/jail-loaded-events
|
||||
:contact/command
|
||||
|
@ -84,4 +84,4 @@
|
|||
;used in modal list (for example for wallet)
|
||||
(spec/def :contacts/click-action (spec/nilable #{:send :request}))
|
||||
;used in modal list (for example for wallet)
|
||||
(spec/def :contacts/click-params (spec/nilable map?))
|
||||
(spec/def :contacts/click-params (spec/nilable map?))
|
||||
|
|
|
@ -1,120 +1,42 @@
|
|||
(ns status-im.ui.screens.contacts.events
|
||||
(:require [re-frame.core :refer [dispatch trim-v reg-fx reg-cofx inject-cofx]]
|
||||
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
|
||||
[status-im.data-store.contacts :as contacts]
|
||||
[clojure.string :as s]
|
||||
[status-im.protocol.core :as protocol]
|
||||
(:require [cljs.reader :as reader]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.contacts :as utils.contacts]
|
||||
[status-im.utils.random :as random]
|
||||
[cljs.reader :refer [read-string]]
|
||||
[status-im.utils.js-resources :as js-res]
|
||||
[status-im.i18n :refer [label]]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.js-resources :as js-res]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.utils.identicon :as identicon]
|
||||
[status-im.utils.gfycat.core :as gfycat.core]
|
||||
[status-im.ui.screens.contacts.navigation]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.chat.console :as console-chat]
|
||||
[status-im.chat.events :as chat.events]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.commands.events.loading :as loading-events]
|
||||
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.js-dependencies :as js-dependencies]))
|
||||
[status-im.js-dependencies :as js-dependencies]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.transport.message.v1.contact :as message.v1.contact]
|
||||
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]))
|
||||
|
||||
;;;; COFX
|
||||
|
||||
(reg-cofx
|
||||
:get-all-contacts
|
||||
(re-frame/reg-cofx
|
||||
::get-default-contacts-and-groups
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :all-contacts (contacts/get-all))))
|
||||
|
||||
(reg-cofx
|
||||
::get-default-contacts-and-groups
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects
|
||||
:default-contacts js-res/default-contacts
|
||||
:default-groups js-res/default-contact-groups)))
|
||||
|
||||
;;;; FX
|
||||
|
||||
(reg-fx
|
||||
::watch-contact
|
||||
(fn [{:keys [web3 whisper-identity public-key private-key]}]
|
||||
(protocol/watch-user! {:web3 web3
|
||||
:identity whisper-identity
|
||||
:keypair {:public public-key
|
||||
:private private-key}
|
||||
:callback #(dispatch [:incoming-message %1 %2])})))
|
||||
|
||||
(reg-fx
|
||||
::stop-watching-contact
|
||||
(fn [{:keys [web3 whisper-identity]}]
|
||||
(protocol/stop-watching-user! {:web3 web3
|
||||
:identity whisper-identity})))
|
||||
|
||||
(reg-fx
|
||||
::send-contact-request-fx
|
||||
(fn [{:keys [web3 current-public-key name whisper-identity
|
||||
photo-path current-account-id status fcm-token
|
||||
updates-public-key updates-private-key] :as params}]
|
||||
(protocol/contact-request!
|
||||
{:web3 web3
|
||||
:message {:from current-public-key
|
||||
:to whisper-identity
|
||||
:message-id (random/id)
|
||||
:payload {:contact {:name name
|
||||
:profile-image photo-path
|
||||
:address current-account-id
|
||||
:status status
|
||||
:fcm-token fcm-token}
|
||||
:keypair {:public updates-public-key
|
||||
:private updates-private-key}
|
||||
:timestamp (datetime/timestamp)}}})))
|
||||
|
||||
(reg-fx
|
||||
::reset-pending-messages
|
||||
(fn [from]
|
||||
(protocol/reset-pending-messages! from)))
|
||||
|
||||
(reg-fx
|
||||
::save-contact
|
||||
(fn [contact]
|
||||
(contacts/save contact)))
|
||||
|
||||
(reg-fx
|
||||
:save-all-contacts
|
||||
(fn [new-contacts]
|
||||
(contacts/save-all new-contacts)))
|
||||
|
||||
(reg-fx
|
||||
::delete-contact
|
||||
(fn [contact]
|
||||
(contacts/delete contact)))
|
||||
|
||||
(defn- contact-name [contact]
|
||||
(->> contact
|
||||
((juxt :givenName :middleName :familyName))
|
||||
(remove s/blank?)
|
||||
(s/join " ")))
|
||||
(assoc coeffects
|
||||
:default-contacts js-res/default-contacts
|
||||
:default-groups js-res/default-contact-groups)))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(defn watch-contact
|
||||
"Takes effects map, adds effects necessary to start watching contact"
|
||||
[{:keys [db] :as fx} {:keys [public-key private-key] :as contact}]
|
||||
(cond-> fx
|
||||
(and public-key private-key)
|
||||
(assoc ::watch-contact (merge
|
||||
(select-keys db [:web3])
|
||||
(select-keys contact [:whisper-identity :public-key :private-key])))))
|
||||
|
||||
(register-handler-fx
|
||||
:watch-contact
|
||||
(fn [{:keys [db]} [_ contact]]
|
||||
(watch-contact {:db db} contact)))
|
||||
|
||||
(register-handler-fx
|
||||
:update-contact!
|
||||
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
|
||||
(when (get-in db [:contacts/contacts whisper-identity])
|
||||
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
|
||||
::save-contact contact})))
|
||||
(defn- update-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
|
||||
(when (get-in db [:contacts/contacts whisper-identity])
|
||||
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
|
||||
:data-store/save-contact contact}))
|
||||
|
||||
(defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
|
||||
(let [{old-pending :pending?
|
||||
|
@ -183,9 +105,9 @@
|
|||
(:group-id (first contacts))
|
||||
(mapv :whisper-identity contacts)])))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:load-default-contacts!
|
||||
[(inject-cofx ::get-default-contacts-and-groups)]
|
||||
[(re-frame/inject-cofx ::get-default-contacts-and-groups)]
|
||||
(fn [{:keys [db default-contacts default-groups]} _]
|
||||
(let [{:contacts/keys [contacts] :group/keys [contact-groups]} db]
|
||||
{:dispatch-n (concat
|
||||
|
@ -194,21 +116,17 @@
|
|||
(prepare-add-chat-events contacts default-contacts)
|
||||
(prepare-add-contacts-to-groups-events contacts default-contacts))})))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:load-contacts
|
||||
[(inject-cofx :get-all-contacts)]
|
||||
[(re-frame/inject-cofx :data-store/get-all-contacts)]
|
||||
(fn [{:keys [db all-contacts]} _]
|
||||
(let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts)
|
||||
contacts (into {} contacts-list)]
|
||||
{:db (update db :contacts/contacts #(merge contacts %))})))
|
||||
;; TODO (yenda) this mapv was dispatching useless events, fixed but is it necessary if
|
||||
;; it was dispatching useless events before with nil
|
||||
;;:dispatch-n (mapv (fn [[_ contact]] [:watch-contact contact]) contacts)
|
||||
{:db (update db :contacts/contacts #(merge contacts %))})))
|
||||
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:add-contacts
|
||||
[(inject-cofx :get-local-storage-data)]
|
||||
[(re-frame/inject-cofx :data-store/get-local-storage-data)]
|
||||
(fn [{:keys [db] :as cofx} [_ new-contacts]]
|
||||
(let [{:contacts/keys [contacts]} db
|
||||
new-contacts' (->> new-contacts
|
||||
|
@ -217,158 +135,107 @@
|
|||
;;(remove #(identities (:whisper-identity %)))
|
||||
(map #(vector (:whisper-identity %) %))
|
||||
(into {}))
|
||||
fx {:db (update db :contacts/contacts merge new-contacts')
|
||||
:save-all-contacts (vals new-contacts')}]
|
||||
fx {:db (update db :contacts/contacts merge new-contacts')
|
||||
:data-store/save-contacts (vals new-contacts')}]
|
||||
(transduce (map second)
|
||||
(completing (partial loading-events/load-commands (assoc cofx :db (:db fx))))
|
||||
fx
|
||||
new-contacts'))))
|
||||
|
||||
(register-handler-db
|
||||
:remove-contacts-click-handler
|
||||
(fn [db _]
|
||||
(dissoc db
|
||||
:contacts/click-handler
|
||||
:contacts/click-action)))
|
||||
(defn- add-new-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
|
||||
(let [new-contact (assoc contact :pending? false)]
|
||||
{:db (-> db
|
||||
(update-in [:contacts/contacts whisper-identity] merge new-contact)
|
||||
(assoc-in [:contacts/new-identity] ""))
|
||||
:data-store/save-contact new-contact}))
|
||||
|
||||
(defn send-contact-request
|
||||
"Takes effects map, adds effects necessary to send a contact request"
|
||||
[{{:accounts/keys [accounts current-account-id] :as db} :db :as fx} contact]
|
||||
(let [current-account (get accounts current-account-id)
|
||||
(defn- own-info [{:accounts/keys [accounts current-account-id] :as db}]
|
||||
(let [{:keys [name photo-path address]} (get accounts current-account-id)
|
||||
fcm-token (get-in db [:notifications :fcm-token])]
|
||||
(assoc fx
|
||||
::send-contact-request-fx
|
||||
(merge
|
||||
(select-keys db [:current-public-key :web3])
|
||||
{:current-account-id current-account-id :fcm-token fcm-token}
|
||||
(select-keys contact [:whisper-identity])
|
||||
(select-keys current-account [:name :photo-path :status
|
||||
:updates-public-key :updates-private-key])))))
|
||||
{:name name
|
||||
:profile-image photo-path
|
||||
:address address
|
||||
:fcm-token fcm-token}))
|
||||
|
||||
(defn add-new-contact [fx {:keys [whisper-identity] :as contact}]
|
||||
(-> fx
|
||||
(send-contact-request contact)
|
||||
(update-in [:db :contacts/contacts whisper-identity] merge contact)
|
||||
(assoc-in [:db :contacts/new-identity] "")
|
||||
(assoc ::save-contact contact)))
|
||||
(defn send-contact-request [{:keys [whisper-identity pending? dapp?] :as contact} {:keys [db] :as cofx}]
|
||||
(when-not dapp?
|
||||
(if pending?
|
||||
(transport/send (message.v1.contact/map->ContactRequestConfirmed (own-info db)) whisper-identity cofx)
|
||||
(transport/send (message.v1.contact/map->ContactRequest (own-info db)) whisper-identity cofx))))
|
||||
|
||||
(defn add-contact [{:keys [db] :as fx} chat-or-whisper-id]
|
||||
(let [{:keys [chats] :contacts/keys [contacts]} db
|
||||
contact (if-let [contact-info (get-in chats [chat-or-whisper-id :contact-info])]
|
||||
(read-string contact-info)
|
||||
(or (get contacts chat-or-whisper-id)
|
||||
(utils.contacts/whisper-id->new-contact chat-or-whisper-id)))
|
||||
contact' (assoc contact
|
||||
:address (public-key->address chat-or-whisper-id)
|
||||
:pending? false)]
|
||||
(-> fx
|
||||
(watch-contact contact')
|
||||
(add-new-contact contact'))))
|
||||
(defn- build-contact [whisper-id {{:keys [chats] :contacts/keys [contacts]} :db}]
|
||||
(-> (if-let [contact-info (get-in chats [whisper-id :contact-info])]
|
||||
(reader/read-string contact-info)
|
||||
(or (get contacts whisper-id)
|
||||
(utils.contacts/whisper-id->new-contact whisper-id)))
|
||||
(assoc :address (public-key->address whisper-id))))
|
||||
|
||||
(defn add-contact-and-open-chat [fx whisper-id]
|
||||
(-> fx
|
||||
(add-contact whisper-id)
|
||||
(update :db #(navigation/navigate-to-clean % :home))
|
||||
(assoc :dispatch [:start-chat whisper-id {:navigation-replace? true}])))
|
||||
(defn add-contact [whisper-id {:keys [db] :as cofx}]
|
||||
(let [contact (build-contact whisper-id cofx)]
|
||||
(handlers/merge-fx cofx
|
||||
(add-new-contact contact)
|
||||
(send-contact-request contact))))
|
||||
|
||||
(register-handler-fx
|
||||
(defn add-contact-and-open-chat [whisper-id cofx]
|
||||
(handlers/merge-fx cofx
|
||||
(navigation/navigate-to-clean :home)
|
||||
(add-contact whisper-id)
|
||||
(chat.events/start-chat whisper-id {})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-contact
|
||||
(fn [{:keys [db]} [_ chat-or-whisper-id]]
|
||||
(add-contact {:db db} chat-or-whisper-id)))
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [cofx [_ whisper-id]]
|
||||
(add-contact whisper-id cofx)))
|
||||
|
||||
(register-handler-fx
|
||||
:add-contact-and-open-chat
|
||||
(fn [{:keys [db]} [_ whisper-id]]
|
||||
(add-contact-and-open-chat {:db db} whisper-id)))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:set-contact-identity-from-qr
|
||||
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db} [_ _ contact-identity]]
|
||||
(let [current-account (get accounts current-account-id)]
|
||||
(cond-> {:db (assoc db :contacts/new-identity contact-identity)}
|
||||
(not (new-chat.db/validate-pub-key contact-identity current-account))
|
||||
(assoc :dispatch [:add-contact-handler])))))
|
||||
[(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :data-store/get-chat)]
|
||||
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db :as cofx} [_ _ contact-identity]]
|
||||
(let [current-account (get accounts current-account-id)
|
||||
fx {:db (assoc db :contacts/new-identity contact-identity)}]
|
||||
(if (new-chat.db/validate-pub-key contact-identity current-account)
|
||||
fx
|
||||
(handlers/merge-fx cofx
|
||||
fx
|
||||
(add-contact-and-open-chat contact-identity))))))
|
||||
|
||||
(register-handler-fx
|
||||
:contact-update-received
|
||||
(fn [{:keys [db]} [_ {:keys [from payload]}]]
|
||||
(let [{:keys [chats current-public-key]} db]
|
||||
(when (not= current-public-key from)
|
||||
(let [{:keys [content timestamp]} payload
|
||||
{:keys [status name profile-image]} (:profile content)
|
||||
prev-last-updated (get-in db [:contacts/contacts from :last-updated])]
|
||||
(when (<= prev-last-updated timestamp)
|
||||
(let [contact {:whisper-identity from
|
||||
:name name
|
||||
:photo-path profile-image
|
||||
:status status
|
||||
:last-updated timestamp}]
|
||||
{:dispatch-n (concat [[:update-contact! contact]]
|
||||
(when (chats from)
|
||||
[[:update-chat! {:chat-id from
|
||||
:name name}]]))})))))))
|
||||
|
||||
(register-handler-fx
|
||||
:update-keys-received
|
||||
(fn [{:keys [db]} [_ {:keys [from payload]}]]
|
||||
(let [{{:keys [public private]} :keypair
|
||||
timestamp :timestamp} payload
|
||||
prev-last-updated (get-in db [:contacts/contacts from :keys-last-updated])]
|
||||
(when (<= prev-last-updated timestamp)
|
||||
(let [contact {:whisper-identity from
|
||||
:public-key public
|
||||
:private-key private
|
||||
:keys-last-updated timestamp}]
|
||||
{:dispatch [:update-contact! contact]})))))
|
||||
|
||||
(register-handler-fx
|
||||
:contact-online-received
|
||||
(fn [{:keys [db]} [_ {:keys [from]
|
||||
{{:keys [timestamp]} :content} :payload}]]
|
||||
(let [prev-last-online (get-in db [:contacts/contacts from :last-online])]
|
||||
(when (and timestamp (< prev-last-online timestamp))
|
||||
{::reset-pending-messages from
|
||||
:dispatch [:update-contact! {:whisper-identity from
|
||||
:last-online timestamp}]}))))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:hide-contact
|
||||
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
|
||||
{::stop-watching-contact (merge
|
||||
(select-keys db [:web3])
|
||||
(select-keys contact [:whisper-identity]))
|
||||
:dispatch-n [[:update-contact! {:whisper-identity whisper-identity
|
||||
:pending? true}]
|
||||
[:account-update-keys]]}))
|
||||
(fn [cofx [_ {:keys [whisper-identity] :as contact}]]
|
||||
(update-contact {:whisper-identity whisper-identity
|
||||
:pending? true}
|
||||
cofx)))
|
||||
|
||||
;;used only by status-dev-cli
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:remove-contact
|
||||
(fn [{:keys [db]} [_ whisper-identity pred]]
|
||||
(let [contact (get-in db [:contacts/contacts whisper-identity])]
|
||||
(when (and contact (pred contact))
|
||||
{:db (update db :contacts/contacts dissoc whisper-identity)
|
||||
::delete-contact contact}))))
|
||||
(fn [{:keys [db]} [_ whisper-identity]]
|
||||
(when-let [contact (get-in db [:contacts/contacts whisper-identity])]
|
||||
{:db (update db :contacts/contacts dissoc whisper-identity)
|
||||
:data-store/delete-contact contact})))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-db
|
||||
:open-contact-toggle-list
|
||||
(fn [{:keys [db]} [_ group-type]]
|
||||
{:db (-> db
|
||||
(assoc :group/group-type group-type
|
||||
:group/selected-contacts #{}
|
||||
:new-chat-name "")
|
||||
(navigation/navigate-to :contact-toggle-list))}))
|
||||
(fn [db [_ group-type]]
|
||||
(-> (assoc db
|
||||
:group/group-type group-type
|
||||
:group/selected-contacts #{}
|
||||
:new-chat-name "")
|
||||
(navigation/navigate-to :contact-toggle-list))))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:open-chat-with-contact
|
||||
(fn [{:keys [db]} [_ {:keys [whisper-identity dapp?] :as contact}]]
|
||||
(-> {:db db}
|
||||
(cond-> (when-not dapp?) (send-contact-request contact))
|
||||
(update :db #(navigation/navigate-to-clean % :home))
|
||||
(assoc :dispatch [:start-chat whisper-identity]))))
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [{:keys [db] :as cofx} [_ {:keys [whisper-identity] :as contact}]]
|
||||
(handlers/merge-fx cofx
|
||||
(navigation/navigate-to-clean :home)
|
||||
(add-contact whisper-identity)
|
||||
(chat.events/start-chat whisper-identity {}))))
|
||||
|
||||
(register-handler-fx
|
||||
(handlers/register-handler-fx
|
||||
:add-contact-handler
|
||||
(fn [{{:contacts/keys [new-identity] :as db} :db}]
|
||||
[(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :data-store/get-chat)]
|
||||
(fn [{{:contacts/keys [new-identity] :as db} :db :as cofx} _]
|
||||
(when (seq new-identity)
|
||||
(add-contact-and-open-chat {:db db} new-identity))))
|
||||
(add-contact-and-open-chat new-identity cofx))))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
(:require [cljs.spec.alpha :as spec]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.platform :as platform]
|
||||
status-im.transport.db
|
||||
status-im.ui.screens.accounts.db
|
||||
status-im.ui.screens.contacts.db
|
||||
status-im.ui.screens.qr-scanner.db
|
||||
|
@ -45,6 +46,7 @@
|
|||
:inbox/topic constants/inbox-topic
|
||||
:inbox/password constants/inbox-password
|
||||
:my-profile/editing? false
|
||||
:transport/chats {}
|
||||
:desktop/desktop {:tab-view-id :home}})
|
||||
|
||||
;;;;GLOBAL
|
||||
|
@ -168,6 +170,8 @@
|
|||
:browser/options
|
||||
:new/open-dapp
|
||||
:navigation/screen-params
|
||||
:transport/chats
|
||||
:transport/discovery-filter
|
||||
:desktop/desktop]
|
||||
:opt-un
|
||||
[::current-public-key
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.ui.screens.desktop.main.tabs.profile.views
|
||||
(:require-macros [status-im.utils.views :as views])
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.profile.user.views :as profile]))
|
||||
|
||||
(defn profile-badge [{:keys [name]}]
|
||||
|
@ -34,7 +35,7 @@
|
|||
[react/view
|
||||
[my-profile-info current-account]]
|
||||
[react/view {:style {:height 1 :background-color "#e8ebec" :margin-horizontal 16}}]
|
||||
[react/touchable-highlight {:on-press #(profile/navigate-to-accounts false)
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:logout])
|
||||
:style {:margin-top 60}}
|
||||
[react/view
|
||||
[react/text {:style {:color :red}} "Log out"]]]]))
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
(ns status-im.ui.screens.events
|
||||
(:require status-im.bots.events
|
||||
status-im.chat.handlers
|
||||
status-im.chat.events
|
||||
status-im.commands.handlers.jail
|
||||
status-im.commands.events.loading
|
||||
status-im.commands.handlers.debug
|
||||
status-im.network.events
|
||||
status-im.transport.handlers
|
||||
status-im.protocol.handlers
|
||||
status-im.ui.screens.accounts.events
|
||||
status-im.ui.screens.accounts.login.events
|
||||
|
@ -31,6 +32,7 @@
|
|||
[status-im.data-store.core :as data-store]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.js-dependencies :as dependencies]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.ui.screens.db :refer [app-db]]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
|
@ -50,7 +52,7 @@
|
|||
;;;; Helper fns
|
||||
|
||||
(defn- call-jail-function
|
||||
[{:keys [chat-id function callback-events-creator] :as opts}]
|
||||
[{:keys [chat-id function callback-event-creator] :as opts}]
|
||||
(let [path [:functions function]
|
||||
params (select-keys opts [:parameters :context])]
|
||||
(status/call-jail
|
||||
|
@ -58,12 +60,11 @@
|
|||
:path path
|
||||
:params params
|
||||
:callback (fn [jail-response]
|
||||
(doseq [event (if callback-events-creator
|
||||
(callback-events-creator jail-response)
|
||||
[[:chat-received-message/bot-response
|
||||
{:chat-id chat-id}
|
||||
jail-response]])
|
||||
:when event]
|
||||
(when-let [event (if callback-event-creator
|
||||
(callback-event-creator jail-response)
|
||||
[:chat-received-message/bot-response
|
||||
{:chat-id chat-id}
|
||||
jail-response])]
|
||||
(re-frame/dispatch event)))})))
|
||||
|
||||
;;;; COFX
|
||||
|
@ -89,15 +90,14 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
:call-jail
|
||||
(fn [{:keys [callback-events-creator] :as opts}]
|
||||
(fn [{:keys [callback-event-creator] :as opts}]
|
||||
(status/call-jail
|
||||
(-> opts
|
||||
(dissoc :callback-events-creator)
|
||||
(dissoc :callback-event-creator)
|
||||
(assoc :callback
|
||||
(fn [jail-response]
|
||||
(when callback-events-creator
|
||||
(doseq [event (callback-events-creator jail-response)]
|
||||
(re-frame/dispatch event)))))))))
|
||||
(when-let [event (callback-event-creator jail-response)]
|
||||
(re-frame/dispatch event))))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:call-jail-function
|
||||
|
@ -168,7 +168,7 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
::status-module-initialized-fx
|
||||
(fn []
|
||||
(fn [_]
|
||||
(status/module-initialized!)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
@ -183,7 +183,7 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
::testfairy-alert
|
||||
(fn []
|
||||
(fn [_]
|
||||
(when config/testfairy-enabled?
|
||||
(utils/show-popup
|
||||
(i18n/label :testfairy-title)
|
||||
|
@ -191,7 +191,7 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
::get-fcm-token-fx
|
||||
(fn []
|
||||
(fn [_]
|
||||
(notifications/get-fcm-token)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
@ -207,7 +207,8 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
:close-application
|
||||
(fn [] (status/close-application)))
|
||||
(fn [_]
|
||||
(status/close-application)))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
|
@ -232,6 +233,20 @@
|
|||
[:initialize-crypt]
|
||||
[:initialize-geth]]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:logout
|
||||
(fn [{:keys [db] :as cofx} _]
|
||||
(let [{:transport/keys [chats] :keys [current-account-id]} db
|
||||
sharing-usage-data? (get-in db [:accounts/accounts current-account-id :sharing-usage-data?])]
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch-n (concat [[:initialize-db]
|
||||
[:load-accounts]
|
||||
[:listen-to-network-status]
|
||||
[:navigate-to :accounts]]
|
||||
(when sharing-usage-data?
|
||||
[[:unregister-mixpanel-tracking]]))}
|
||||
(transport/stop-whisper)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:initialize-db
|
||||
(fn [{{:keys [status-module-initialized? status-node-started?
|
||||
|
@ -280,7 +295,6 @@
|
|||
:initialize-account
|
||||
(fn [_ [_ address events-after]]
|
||||
{:dispatch-n (cond-> [[:initialize-account-db address]
|
||||
[:load-processed-messages]
|
||||
[:initialize-protocol address]
|
||||
[:initialize-sync-listener]
|
||||
[:initialize-chats]
|
||||
|
|
|
@ -1,132 +1,11 @@
|
|||
(ns status-im.ui.screens.group.chat-settings.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.chats :as chats]
|
||||
[status-im.data-store.contacts :as contacts]
|
||||
[status-im.data-store.messages :as messages]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.protocol.core :as protocol]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.random :as random]))
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.transport.message.v1.group-chat :as group-chat]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
;;;; FX
|
||||
|
||||
(re-frame/reg-fx
|
||||
::save-chat-property
|
||||
(fn [[current-chat-id property-name value]]
|
||||
(chats/save-property current-chat-id property-name value)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::add-members-to-chat
|
||||
(fn [{:keys [current-chat-id selected-participants]}]
|
||||
(chats/add-contacts current-chat-id selected-participants)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::remove-members-from-chat
|
||||
(fn [[current-chat-id participants]]
|
||||
(chats/remove-contacts current-chat-id participants)))
|
||||
|
||||
(defn system-message [message-id content]
|
||||
{:from "system"
|
||||
:message-id message-id
|
||||
:content content
|
||||
:content-type constants/text-content-type})
|
||||
|
||||
(defn removed-participant-message [chat-id identity contact-name]
|
||||
(let [message-text (str "You've removed " (or contact-name identity))]
|
||||
(-> (system-message (random/id) message-text)
|
||||
(assoc :chat-id chat-id)
|
||||
(messages/save))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::create-removing-messages
|
||||
(fn [{:keys [current-chat-id participants contacts/contacts]}]
|
||||
(doseq [participant participants]
|
||||
(let [contact-name (get-in contacts [participant :name])]
|
||||
(removed-participant-message current-chat-id participant contact-name)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::notify-about-new-members
|
||||
(fn [{:keys [current-chat-id selected-participants
|
||||
current-public-key chats web3]}]
|
||||
(let [{:keys [name contacts]} (chats current-chat-id)
|
||||
identities (map :identity contacts)
|
||||
|
||||
{:keys [public private]
|
||||
:as new-keypair} (protocol/new-keypair!)
|
||||
|
||||
group-message {:web3 web3
|
||||
:group {:id current-chat-id
|
||||
:name name
|
||||
:contacts (conj identities current-public-key)
|
||||
:admin current-public-key}
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)}}]
|
||||
(re-frame/dispatch [:update-chat! {:chat-id current-chat-id
|
||||
:public-key public
|
||||
:private-key private}])
|
||||
(protocol/start-watching-group! {:web3 web3
|
||||
:group-id current-chat-id
|
||||
:identity current-public-key
|
||||
:keypair new-keypair
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])})
|
||||
(protocol/invite-to-group!
|
||||
(-> group-message
|
||||
(assoc-in [:group :keypair] new-keypair)
|
||||
(assoc :identities selected-participants)))
|
||||
(protocol/update-group!
|
||||
(-> group-message
|
||||
(assoc-in [:group :keypair] new-keypair)
|
||||
(assoc :identities identities)))
|
||||
(doseq [identity selected-participants]
|
||||
(protocol/add-to-group! {:web3 web3
|
||||
:group-id current-chat-id
|
||||
:identity identity
|
||||
:keypair new-keypair
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)}})))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::notify-about-removing
|
||||
(fn [{:keys [web3 current-chat-id participants chats current-public-key]}]
|
||||
(let [{:keys [private public] :as new-keypair} (protocol/new-keypair!)
|
||||
{:keys [name private-key public-key]
|
||||
:as chat} (get chats current-chat-id)
|
||||
old-keypair {:private private-key
|
||||
:public public-key}
|
||||
contacts (get chat :contacts)
|
||||
identities (-> (map :identity contacts)
|
||||
set
|
||||
(clojure.set/difference participants))]
|
||||
(re-frame/dispatch [:update-chat! {:chat-id current-chat-id
|
||||
:private-key private
|
||||
:public-key public}])
|
||||
(doseq [participant participants]
|
||||
(let [id (random/id)]
|
||||
(doseq [keypair [old-keypair new-keypair]]
|
||||
(protocol/remove-from-group!
|
||||
{:web3 web3
|
||||
:group-id current-chat-id
|
||||
:identity participant
|
||||
:keypair keypair
|
||||
:message {:from current-public-key
|
||||
:message-id id}}))))
|
||||
(protocol/start-watching-group!
|
||||
{:web3 web3
|
||||
:group-id current-chat-id
|
||||
:identity current-public-key
|
||||
:keypair new-keypair
|
||||
:callback #(re-frame/dispatch [:incoming-message %1 %2])})
|
||||
(protocol/update-group!
|
||||
{:web3 web3
|
||||
:group {:id current-chat-id
|
||||
:name name
|
||||
:contacts (conj identities current-public-key)
|
||||
:admin current-public-key
|
||||
:keypair new-keypair}
|
||||
:identities identities
|
||||
:message {:from current-public-key
|
||||
:message-id (random/id)}}))))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
|
@ -134,44 +13,55 @@
|
|||
:show-group-chat-profile
|
||||
(fn [{db :db} [_ chat-id]]
|
||||
{:db (assoc db :new-chat-name (get-in db [:chats chat-id :name])
|
||||
:group/group-type :chat-group)
|
||||
:group/group-type :chat-group)
|
||||
:dispatch [:navigate-to :group-chat-profile]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-new-group-chat-participants
|
||||
(fn [{{:keys [current-chat-id selected-participants] :as db} :db} _]
|
||||
(let [new-identities (map #(hash-map :identity %) selected-participants)]
|
||||
{:db (-> db
|
||||
(update-in [:chats current-chat-id :contacts] concat new-identities)
|
||||
(assoc :selected-participants #{}))
|
||||
::add-members-to-chat (select-keys db [:current-chat-id :selected-participants])
|
||||
::notify-about-new-members (select-keys db [:current-chat-id :selected-participants
|
||||
:current-public-key :chats :web3])})))
|
||||
|
||||
(defn- remove-identities [collection identities]
|
||||
(remove #(identities (:identity %)) collection))
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [{{:keys [current-chat-id selected-participants] :as db} :db now :now message-id :random-id :as cofx} _]
|
||||
(let [new-identities (map #(hash-map :identity %) selected-participants)
|
||||
participants (concat (get-in db [:chats current-chat-id :contacts])
|
||||
selected-participants)
|
||||
contacts (:contacts/contacts db)
|
||||
added-participants-names (map #(get-in contacts [% :name]) selected-participants)]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (-> db
|
||||
(assoc-in [:chats current-chat-id :contacts] participants)
|
||||
(assoc :selected-participants #{}))
|
||||
:data-store/add-chat-contacts (select-keys db [:current-chat-id :selected-participants])}
|
||||
(models.message/receive
|
||||
(models.message/system-message current-chat-id message-id now
|
||||
(str "You've added " (apply str (interpose ", " added-participants-names)))))
|
||||
(transport/send (group-chat/GroupAdminUpdate. nil participants) current-chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-group-chat-participants
|
||||
(fn [{{:keys [current-chat-id] :as db} :db} [_ participants]]
|
||||
{:db (update-in db [:chats current-chat-id :contacts] remove-identities participants)
|
||||
::remove-members-from-chat [current-chat-id participants]
|
||||
::notify-about-removing (merge {:participants participants}
|
||||
(select-keys db [:web3 :current-chat-id :chats :current-public-key]))
|
||||
::create-removing-messages (merge {:participants participants}
|
||||
(select-keys db [:current-chat-id :contacts/contacts]))}))
|
||||
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
|
||||
(fn [{{:keys [current-chat-id] :as db} :db now :now message-id :random-id :as cofx} [removed-participants]]
|
||||
(let [participants (remove #(removed-participants (:identity %))
|
||||
(get-in db [:chats current-chat-id :contacts]))
|
||||
contacts (:contacts/contacts db)
|
||||
removed-participants-names (map #(get-in contacts [% :name]) removed-participants)]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (assoc-in db [:chats current-chat-id :contacts] participants)
|
||||
:data-store/remove-chat-contacts [current-chat-id removed-participants]}
|
||||
(models.message/receive
|
||||
(models.message/system-message current-chat-id message-id now
|
||||
(str "You've removed " (apply str (interpose ", " removed-participants-names)))))
|
||||
(transport/send (group-chat/GroupAdminUpdate. nil participants) current-chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-group-chat-name
|
||||
(fn [{{:keys [current-chat-id] :as db} :db} [_ new-chat-name]]
|
||||
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
|
||||
::save-chat-property [current-chat-id :name new-chat-name]}))
|
||||
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
|
||||
:data-store/save-chat-property [current-chat-id :name new-chat-name]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history
|
||||
(fn [{{:keys [current-chat-id] :as db} :db} _]
|
||||
{:db (assoc-in db [:chats current-chat-id :messages] {})
|
||||
:delete-messages current-chat-id}))
|
||||
{:db (assoc-in db [:chats current-chat-id :messages] {})
|
||||
:data-store/hide-messages current-chat-id}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history?
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
(ns status-im.ui.screens.group.core)
|
||||
|
||||
(defn participants-added [chat-id added-participants-set {:keys [db] :as cofx}]
|
||||
(when (seq added-participants-set)
|
||||
{:db (update-in db [:chats chat-id :contacts] concat (mapv #(hash-map :identity %) added-participants-set))
|
||||
:data-store/add-chat-contacts [chat-id added-participants-set]}))
|
||||
|
||||
(defn participants-removed [chat-id removed-participants-set {:keys [now db] :as cofx}]
|
||||
(when (seq removed-participants-set)
|
||||
(let [{:keys [is-active timestamp]} (get-in db [:chats chat-id])]
|
||||
;;TODO: not sure what this condition is for
|
||||
(when (and is-active (>= now timestamp))
|
||||
{:db (update-in db [:chats chat-id :contacts]
|
||||
(partial remove (comp removed-participants-set :identity)))
|
||||
:data-store/remove-chat-contacts [chat-id removed-participants-set]}))))
|
|
@ -1,48 +1,13 @@
|
|||
(ns status-im.ui.screens.group.events
|
||||
(:require [status-im.protocol.core :as protocol]
|
||||
[re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
|
||||
(:require [re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
|
||||
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
|
||||
[status-im.ui.components.styles :refer [default-chat-color]]
|
||||
[status-im.data-store.contact-groups :as groups]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.random :as random]
|
||||
[status-im.ui.screens.group.navigation]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
;;;; COFX
|
||||
|
||||
(reg-cofx
|
||||
::get-all-contact-groups
|
||||
(fn [coeffects _]
|
||||
(let [groups (->> (groups/get-all)
|
||||
(map (fn [{:keys [group-id] :as group}]
|
||||
[group-id group]))
|
||||
(into {}))]
|
||||
(assoc coeffects :all-groups groups))))
|
||||
|
||||
;;;; FX
|
||||
|
||||
(reg-fx
|
||||
::save-contact-group
|
||||
(fn [new-group]
|
||||
(groups/save new-group)))
|
||||
|
||||
(reg-fx
|
||||
::save-contact-groups
|
||||
(fn [new-groups]
|
||||
(groups/save-all new-groups)))
|
||||
|
||||
(reg-fx
|
||||
::save-contact-group-property
|
||||
(fn [[contact-group-id property-name value]]
|
||||
(groups/save-property contact-group-id property-name value)))
|
||||
|
||||
(reg-fx
|
||||
::add-contacts-to-contact-group
|
||||
(fn [[contact-group-id selected-contacts]]
|
||||
(groups/add-contacts contact-group-id selected-contacts)))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(register-handler-db
|
||||
|
@ -76,14 +41,14 @@
|
|||
:order (count contact-groups)
|
||||
:timestamp now
|
||||
:contacts selected-contacts'}]
|
||||
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
|
||||
::save-contact-group new-group})))
|
||||
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
|
||||
:data-store/save-contact-group new-group})))
|
||||
|
||||
(register-handler-fx
|
||||
::update-contact-group
|
||||
(fn [{:keys [db]} [_ new-group]]
|
||||
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
|
||||
::save-contact-group new-group}))
|
||||
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
|
||||
:data-store/save-contact-group new-group}))
|
||||
|
||||
(defn update-pending-status [old-groups {:keys [group-id pending?] :as group}]
|
||||
(let [{old-pending :pending?
|
||||
|
@ -101,14 +66,14 @@
|
|||
(remove #(identities (:group-id %)))
|
||||
(map #(vector (:group-id %2) (assoc %2 :order %1)) (iterate inc old-groups-count))
|
||||
(into {}))]
|
||||
{:db (update db :group/contact-groups merge new-groups')
|
||||
::save-contact-groups (into [] (vals new-groups'))})))
|
||||
{:db (update db :group/contact-groups merge new-groups')
|
||||
:data-store/save-contact-groups (into [] (vals new-groups'))})))
|
||||
|
||||
(register-handler-fx
|
||||
:load-contact-groups
|
||||
[(inject-cofx ::get-all-contact-groups)]
|
||||
(fn [{:keys [db all-groups]} _]
|
||||
{:db (assoc db :group/contact-groups all-groups)}))
|
||||
[(inject-cofx :data-store/get-all-contact-groups)]
|
||||
(fn [{:keys [db all-contact-groups]} _]
|
||||
{:db (assoc db :group/contact-groups all-contact-groups)}))
|
||||
|
||||
(defn move-item [v from to]
|
||||
(if (< from to)
|
||||
|
@ -132,30 +97,37 @@
|
|||
:save-contact-group-order
|
||||
(fn [{{:group/keys [contact-groups groups-order] :as db} :db} _]
|
||||
(let [new-groups (mapv #(assoc (contact-groups (second %)) :order (first %))
|
||||
(map-indexed vector (reverse groups-order)))]
|
||||
{:db (update db :group/contact-groups merge (map #(vector (:group-id %) %) new-groups))
|
||||
::save-contact-groups new-groups})))
|
||||
(map-indexed vector (reverse groups-order)))]
|
||||
{:db (update db
|
||||
:group/contact-groups
|
||||
merge (map #(vector (:group-id %) %) new-groups))
|
||||
:data-store/save-contact-groups new-groups})))
|
||||
|
||||
(register-handler-fx
|
||||
:set-contact-group-name
|
||||
(fn [{{:keys [new-chat-name] :group/keys [contact-group-id] :as db} :db} _]
|
||||
{:db (assoc-in db [:group/contact-groups contact-group-id :name] new-chat-name)
|
||||
::save-contact-group-property [contact-group-id :name new-chat-name]}))
|
||||
{:db (assoc-in db
|
||||
[:group/contact-groups contact-group-id :name]
|
||||
new-chat-name)
|
||||
:data-store/save-contact-group-property [contact-group-id :name new-chat-name]}))
|
||||
|
||||
(register-handler-fx
|
||||
:add-selected-contacts-to-group
|
||||
(fn [{{:group/keys [contact-groups contact-group-id selected-contacts] :as db} :db} _]
|
||||
(let [new-identities (mapv #(hash-map :identity %) selected-contacts)]
|
||||
{:db (update-in db [:group/contact-groups contact-group-id :contacts] #(into [] (set (concat % new-identities))))
|
||||
::add-contacts-to-contact-group [contact-group-id selected-contacts]})))
|
||||
{:db (update-in db
|
||||
[:group/contact-groups contact-group-id :contacts]
|
||||
#(into [] (set (concat % new-identities))))
|
||||
:data-store/add-contacts-to-contact-group [contact-group-id selected-contacts]})))
|
||||
|
||||
(register-handler-fx
|
||||
:add-contacts-to-group
|
||||
(fn [{:keys [db]} [_ group-id contacts]]
|
||||
(let [new-identities (mapv #(hash-map :identity %) contacts)]
|
||||
(when (get-in db [:group/contact-groups group-id])
|
||||
{:db (update-in db [:group/contact-groups group-id :contacts] #(into [] (set (concat % new-identities))))
|
||||
::add-contacts-to-contact-group [group-id contacts]}))))
|
||||
{:db (update-in db [:group/contact-groups group-id :contacts]
|
||||
#(into [] (set (concat % new-identities))))
|
||||
:data-store/add-contacts-to-contact-group [group-id contacts]}))))
|
||||
|
||||
(defn remove-contact-from-group [whisper-identity]
|
||||
(fn [contacts]
|
||||
|
@ -171,5 +143,5 @@
|
|||
(register-handler-fx
|
||||
:delete-contact-group
|
||||
(fn [{{:group/keys [contact-group-id] :as db} :db} _]
|
||||
{:db (assoc-in db [:group/contact-groups contact-group-id :pending?] true)
|
||||
::save-contact-group-property [contact-group-id :pending? true]}))
|
||||
{:db (assoc-in db [:group/contact-groups contact-group-id :pending?] true)
|
||||
:data-store/save-contact-group-property [contact-group-id :pending? true]}))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(ns status-im.ui.screens.navigation
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :refer [register-handler-db]]
|
||||
[status-im.constants :refer [console-chat-id]]))
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
;; private helper fns
|
||||
|
||||
|
@ -16,21 +15,24 @@
|
|||
(pop stack))]
|
||||
(conj stack' view-id)))
|
||||
|
||||
(defn replace-view [db view-id]
|
||||
(-> db
|
||||
(update :navigation-stack replace-top-element view-id)
|
||||
(assoc :view-id view-id)))
|
||||
|
||||
;; public fns
|
||||
|
||||
(defn navigate-to-clean
|
||||
([db view-id] (navigate-to-clean db view-id nil))
|
||||
([db view-id screen-params]
|
||||
([view-id cofx] (navigate-to-clean view-id cofx nil))
|
||||
([view-id {:keys [db]} screen-params]
|
||||
;; TODO (jeluard) Unify all :navigate-to flavours. Maybe accept a map of parameters?
|
||||
|
||||
(let [db (cond-> db
|
||||
(seq screen-params)
|
||||
(assoc-in [:navigation/screen-params view-id] screen-params))]
|
||||
(push-view db view-id))))
|
||||
(seq screen-params)
|
||||
(assoc-in [:navigation/screen-params view-id] screen-params))]
|
||||
{:db (push-view db view-id)})))
|
||||
|
||||
(defn replace-view [view-id {:keys [db]}]
|
||||
{:db (-> (update db :navigation-stack replace-top-element view-id)
|
||||
(assoc :view-id view-id))})
|
||||
|
||||
(defn navigate-forget [view-id {:keys [db]}]
|
||||
{:db (assoc db :view-id view-id)})
|
||||
|
||||
(defmulti preload-data!
|
||||
(fn [db [_ view-id]] (or view-id (:view-id db))))
|
||||
|
@ -56,31 +58,25 @@
|
|||
|
||||
;; event handlers
|
||||
|
||||
(register-handler-db
|
||||
:navigate-forget
|
||||
(re-frame/enrich preload-data!)
|
||||
(fn [db [_ new-view-id]]
|
||||
(assoc db :view-id new-view-id)))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-db
|
||||
:navigate-to
|
||||
(re-frame/enrich preload-data!)
|
||||
(fn [db [_ & params]]
|
||||
(apply navigate-to db params)))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-db
|
||||
:navigate-to-modal
|
||||
(re-frame/enrich preload-data!)
|
||||
(fn [db [_ modal-view]]
|
||||
(assoc db :modal modal-view)))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-fx
|
||||
:navigation-replace
|
||||
(re-frame/enrich preload-data!)
|
||||
(fn [db [_ view-id]]
|
||||
(replace-view db view-id)))
|
||||
(fn [cofx [_ view-id]]
|
||||
(replace-view view-id cofx)))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-db
|
||||
:navigate-back
|
||||
(re-frame/enrich -preload-data!)
|
||||
(fn [{:keys [navigation-stack view-id modal] :as db} _]
|
||||
|
@ -98,16 +94,17 @@
|
|||
(assoc :navigation-stack navigation-stack'))
|
||||
(assoc db :view-id first-in-stack))))))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-fx
|
||||
:navigate-to-clean
|
||||
(fn [db [_ & params]]
|
||||
(apply navigate-to db params)))
|
||||
(fn [{:keys [db]} [_ & params]]
|
||||
{:db (apply navigate-to db params)}))
|
||||
|
||||
(register-handler-db
|
||||
(handlers/register-handler-fx
|
||||
:navigate-to-tab
|
||||
(re-frame/enrich preload-data!)
|
||||
(fn [db [_ view-id]]
|
||||
(-> db
|
||||
(assoc :prev-tab-view-id (:view-id db))
|
||||
(assoc :prev-view-id (:view-id db))
|
||||
(navigate-to-clean view-id))))
|
||||
(fn [{:keys [db] :as cofx} [_ view-id]]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (-> db
|
||||
(assoc :prev-tab-view-id (:view-id db))
|
||||
(assoc :prev-view-id (:view-id db)))}
|
||||
(navigate-to-clean view-id))))
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
(ns status-im.ui.screens.network-settings.events
|
||||
(:require [re-frame.core :refer [dispatch dispatch-sync after] :as re-frame]
|
||||
[status-im.utils.handlers :refer [register-handler] :as handlers]
|
||||
[status-im.data-store.networks :as networks]
|
||||
[status-im.ui.screens.accounts.events :as accounts-events]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.ethereum.core :as utils]))
|
||||
|
||||
;;;; FX
|
||||
|
||||
(re-frame/reg-fx
|
||||
::save-networks
|
||||
(fn [networks]
|
||||
(networks/save-all networks)))
|
||||
[status-im.utils.ethereum.core :as utils]
|
||||
[status-im.transport.core :as transport]))
|
||||
|
||||
;; handlers
|
||||
|
||||
|
@ -29,23 +22,30 @@
|
|||
:save-networks new-networks'})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::save-network
|
||||
(fn [{:keys [db now]} [_ network]]
|
||||
(accounts-events/account-update {:db db
|
||||
:close-application nil}
|
||||
{:network network
|
||||
:last-updated now})))
|
||||
::close-application
|
||||
(fn [_ _]
|
||||
{:close-application nil}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::save-network
|
||||
(fn [{:keys [db now] :as cofx} [_ network]]
|
||||
(handlers/merge-fx cofx
|
||||
(accounts-events/account-update {:network network
|
||||
:last-updated now}
|
||||
[::close-application]))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:connect-network
|
||||
(fn [{:keys [db now]} [_ network]]
|
||||
(fn [{:keys [db now] :as cofx} [_ network]]
|
||||
(let [current-network (:network db)
|
||||
networks (:networks/networks db)]
|
||||
networks (:networks/networks db)
|
||||
chats (:transport/chats db)]
|
||||
(if (utils/network-with-upstream-rpc? networks current-network)
|
||||
(merge (accounts-events/account-update {:db db} {:network network
|
||||
:last-updated now})
|
||||
{:dispatch [:navigate-to-clean :accounts]
|
||||
:stop-whisper nil})
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch [:navigate-to-clean :accounts]}
|
||||
(transport/stop-whisper)
|
||||
(accounts-events/account-update {:network network
|
||||
:last-updated now}))
|
||||
{:show-confirmation {:title (i18n/label :t/close-app-title)
|
||||
:content (i18n/label :t/close-app-content)
|
||||
:confirm-button-text (i18n/label :t/close-app-button)
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
(:require [re-frame.core :refer [dispatch]]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.ui.screens.accounts.events :as accounts-events]
|
||||
[status-im.i18n :as i18n]))
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transport.core :as transport]))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::save-wnode
|
||||
(fn [{:keys [db now]} [_ wnode]]
|
||||
(-> (accounts-events/account-update {:db db}
|
||||
{:wnode wnode :last-updated now})
|
||||
(merge {:dispatch [:navigate-to-clean :accounts]
|
||||
:stop-whisper nil}))))
|
||||
(fn [{:keys [db now] :as cofx} [_ wnode]]
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch [:navigate-to-clean :accounts]}
|
||||
(accounts-events/account-update {:wnode wnode :last-updated now})
|
||||
(transport/stop-whisper))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:connect-wnode
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[toolbar/content-title ""]])
|
||||
|
||||
(defn actions [{:keys [pending? whisper-identity dapp?]}]
|
||||
(concat (if pending?
|
||||
(concat (if (or (nil? pending?) pending?)
|
||||
[{:label (i18n/label :t/add-to-contacts)
|
||||
:icon :icons/add-contact
|
||||
:action #(re-frame/dispatch [:add-contact whisper-identity])
|
||||
|
@ -28,7 +28,7 @@
|
|||
:accessibility-label :in-contacts-button}])
|
||||
[{:label (i18n/label :t/send-message)
|
||||
:icon :icons/chats
|
||||
:action #(re-frame/dispatch [:start-chat whisper-identity {:navigation-replace? true}])
|
||||
:action #(re-frame/dispatch [:open-chat-with-contact {:whisper-identity whisper-identity}])
|
||||
:accessibility-label :start-conversation-button}]
|
||||
(when-not dapp?
|
||||
[{:label (i18n/label :t/send-transaction)
|
||||
|
|
|
@ -27,11 +27,12 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:profile/send-transaction
|
||||
[(re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]
|
||||
(fn [{{:contacts/keys [contacts] :as db} :db :as cofx} [chat-id]]
|
||||
(let [send-command (get-in contacts chat-const/send-command-ref)
|
||||
fx (chat-events/start-chat chat-id true cofx)]
|
||||
(merge fx (input-events/select-chat-input-command (:db fx) send-command nil true)))))
|
||||
[(re-frame/inject-cofx :data-store/get-chat) re-frame/trim-v]
|
||||
(fn [{{:contacts/keys [contacts]} :db :as cofx} [chat-id]]
|
||||
(let [send-command (get-in contacts chat-const/send-command-ref)]
|
||||
(handlers/merge-fx cofx
|
||||
(chat-events/start-chat chat-id {:navigation-replace? true})
|
||||
(input-events/select-chat-input-command send-command nil true)))))
|
||||
|
||||
(defn get-current-account [{:accounts/keys [current-account-id] :as db}]
|
||||
(get-in db [:accounts/accounts current-account-id]))
|
||||
|
@ -59,8 +60,8 @@
|
|||
name
|
||||
(get-in db [:accounts/accounts current-account-id :name]))))
|
||||
|
||||
(defn clear-profile [fx]
|
||||
(update fx :db dissoc :my-profile/profile :my-profile/default-name :my-profile/editing?))
|
||||
(defn clear-profile [{:keys [db] :as cofx}]
|
||||
{:db (dissoc db :my-profile/profile :my-profile/default-name :my-profile/editing?)})
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:my-profile/start-editing-profile
|
||||
|
@ -69,16 +70,17 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:my-profile/save-profile
|
||||
(fn [{:keys [db now]} _]
|
||||
(fn [{:keys [db now] :as cofx} _]
|
||||
(let [{:keys [photo-path]} (:my-profile/profile db)
|
||||
cleaned-name (clean-name db :my-profile/profile)
|
||||
cleaned-edit (merge {:name cleaned-name
|
||||
:last-updated now}
|
||||
(if photo-path
|
||||
{:photo-path photo-path}))]
|
||||
(-> (clear-profile {:db db})
|
||||
(accounts-events/account-update cleaned-edit)
|
||||
(update :dispatch-n concat [[:navigate-back]])))))
|
||||
(handlers/merge-fx cofx
|
||||
{:dispatch [:navigate-back]}
|
||||
(clear-profile)
|
||||
(accounts-events/account-update cleaned-edit)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:group-chat-profile/start-editing
|
||||
|
@ -107,6 +109,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:my-profile/finish
|
||||
(fn [{:keys [db]} _]
|
||||
(-> {:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)}
|
||||
(accounts-events/account-update {:seed-backed-up? true}))))
|
||||
(fn [{:keys [db] :as cofx} _]
|
||||
(handlers/merge-fx cofx
|
||||
{:db (update db :my-profile/seed assoc :step :finish :error nil :word nil)}
|
||||
(accounts-events/account-update {:seed-backed-up? true}))))
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue