Keep username/photo-path synced across devices

We add syncing of account fields in pairing messages (only photo-path &
name for now). Also a sync message is sent each time we send a
contact-update, to keep other devices in sync. The change is compatible
with previous clients as it's just an accretion of transit.

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-11-14 14:51:20 +01:00
parent 6cf9e6136b
commit 08291a8396
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
6 changed files with 163 additions and 110 deletions

View File

@ -47,6 +47,13 @@
(def merge-contacts (partial merge-with merge-contact)) (def merge-contacts (partial merge-with merge-contact))
(def account-mergeable-keys [:name :photo-path :last-updated])
(defn merge-account [local remote]
(if (> (:last-updated remote) (:last-updated local))
(merge local (select-keys remote account-mergeable-keys))
local))
(fx/defn upsert-installation [{:keys [db]} {:keys [installation-id] :as new-installation}] (fx/defn upsert-installation [{:keys [db]} {:keys [installation-id] :as new-installation}]
(let [old-installation (get-in db [:pairing/installations installation-id]) (let [old-installation (get-in db [:pairing/installations installation-id])
updated-installation (merge old-installation new-installation)] updated-installation (merge old-installation new-installation)]
@ -68,18 +75,26 @@
(not (get-in db [:pairing/installations installation-id]))) (not (get-in db [:pairing/installations installation-id])))
(upsert-installation cofx new-installation)))))) (upsert-installation cofx new-installation))))))
(defn sync-installation-messages [{:keys [db]}] (defn sync-installation-account-message [{:keys [db]}]
(let [contacts (:contacts/contacts db)] (let [account (-> db
(map :account/account
(fn [batch] (select-keys account-mergeable-keys))]
(let [contacts-to-sync (reduce (fn [acc {:keys [public-key] :as contact}] (transport.pairing/SyncInstallation. {} account)))
(assoc acc public-key (dissoc contact :photo-path)))
{} (defn- contact-batch->sync-installation-message [batch]
batch)] (let [contacts-to-sync (reduce (fn [acc {:keys [public-key] :as contact}]
(transport.pairing/SyncInstallation. contacts-to-sync))) (assoc acc public-key (dissoc contact :photo-path)))
(partition-all contact-batch-n (->> contacts {}
vals batch)]
(remove :dapp?)))))) (transport.pairing/SyncInstallation. contacts-to-sync nil)))
(defn sync-installation-messages [{:keys [db] :as cofx}]
(let [contacts (:contacts/contacts db)
contact-batches (partition-all contact-batch-n (->> contacts
vals
(remove :dapp?)))]
(conj (mapv contact-batch->sync-installation-message contact-batches)
(sync-installation-account-message cofx))))
(defn enable [{:keys [db]} installation-id] (defn enable [{:keys [db]} installation-id]
{:db (assoc-in db {:db (assoc-in db
@ -167,12 +182,15 @@
{} {}
contacts)) contacts))
(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts]} sender] (defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account]} sender]
(let [dev-mode? (get-in db [:account/account :dev-mode?])] (let [dev-mode? (get-in db [:account/account :dev-mode?])]
(when (and (config/pairing-enabled? dev-mode?) (when (and (config/pairing-enabled? dev-mode?)
(= sender (accounts.db/current-public-key cofx))) (= sender (accounts.db/current-public-key cofx)))
(let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))] (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))
{:db (assoc db :contacts/contacts new-contacts) new-account (merge-account (:account/account db) account)]
{:db (assoc db
:contacts/contacts new-contacts
:account/account new-account)
:data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]})))) :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]}))))
(defn handle-pair-installation [{:keys [db] :as cofx} {:keys [installation-id device-type]} timestamp sender] (defn handle-pair-installation [{:keys [db] :as cofx} {:keys [installation-id device-type]} timestamp sender]

View File

@ -30,7 +30,8 @@
(let [sync-message (transport.pairing/SyncInstallation. (let [sync-message (transport.pairing/SyncInstallation.
(select-keys (select-keys
(get-in cofx [:db :contacts/contacts]) (get-in cofx [:db :contacts/contacts])
[chat-id]))] [chat-id])
nil)]
(fx/merge cofx (fx/merge cofx
(protocol/init-chat {:chat-id chat-id (protocol/init-chat {:chat-id chat-id
:resend? "contact-request"}) :resend? "contact-request"})
@ -45,7 +46,8 @@
(let [sync-message (transport.pairing/SyncInstallation. (let [sync-message (transport.pairing/SyncInstallation.
(select-keys (select-keys
(get-in cofx [:db :contacts/contacts]) (get-in cofx [:db :contacts/contacts])
[chat-id])) [chat-id])
nil)
success-event [:transport/contact-message-sent chat-id] success-event [:transport/contact-message-sent chat-id]
chat (get-in db [:transport/chats chat-id]) chat (get-in db [:transport/chats chat-id])
updated-chat (if chat updated-chat (if chat
@ -60,3 +62,35 @@
:payload this :payload this
:success-event success-event}) :success-event success-event})
(pairing/send-installation-message-fx sync-message))))) (pairing/send-installation-message-fx sync-message)))))
(fx/defn send-contact-update
[{:keys [db] :as cofx} chat-id payload]
(when-let [chat (get-in cofx [:db :transport/chats chat-id])]
(let [updated-chat (assoc chat :resend? "contact-update")
tx [(transport-store/save-transport-tx {:chat-id chat-id
:chat updated-chat})]
success-event [:transport/contact-message-sent chat-id]]
(fx/merge cofx
{:db (assoc-in db
[:transport/chats chat-id :resend?]
"contact-update")
:data-store/tx tx}
(protocol/send-with-pubkey {:chat-id chat-id
:payload payload
:success-event success-event})))))
(extend-type transport.contact/ContactUpdate
protocol/StatusMessage
(send [this _ {:keys [db] :as cofx}]
(let [contact-public-keys (reduce (fn [acc [_ {:keys [public-key dapp? pending?]}]]
(if (and (not dapp?)
(not pending?))
(conj acc public-key)
acc))
#{}
(:contacts/contacts db))
;;NOTE: chats with contacts use public-key as chat-id
send-contact-update-fxs (map #(send-contact-update % this) contact-public-keys)
sync-message (pairing/sync-installation-account-message cofx)
fxs (conj send-contact-update-fxs
(pairing/send-installation-message-fx sync-message))]
(apply fx/merge cofx fxs))))

View File

@ -18,35 +18,8 @@
(when (spec/valid? :message/contact-request-confirmed this) (when (spec/valid? :message/contact-request-confirmed this)
this))) this)))
(fx/defn send-contact-update
[{:keys [db] :as cofx} chat-id payload]
(when-let [chat (get-in cofx [:db :transport/chats chat-id])]
(let [updated-chat (assoc chat :resend? "contact-update")
tx [(transport-store/save-transport-tx {:chat-id chat-id
:chat updated-chat})]
success-event [:transport/contact-message-sent chat-id]]
(fx/merge cofx
{:db (assoc-in db
[:transport/chats chat-id :resend?]
"contact-update")
:data-store/tx tx}
(protocol/send-with-pubkey {:chat-id chat-id
:payload payload
:success-event success-event})))))
(defrecord ContactUpdate [name profile-image address fcm-token] (defrecord ContactUpdate [name profile-image address fcm-token]
protocol/StatusMessage protocol/StatusMessage
(send [this _ {:keys [db] :as cofx}]
(let [contact-public-keys (reduce (fn [acc [_ {:keys [public-key dapp? pending?]}]]
(if (and (not dapp?)
(not pending?))
(conj acc public-key)
acc))
#{}
(:contacts/contacts db))
;;NOTE: chats with contacts use public-key as chat-id
send-contact-update-fxs (map #(send-contact-update % this) contact-public-keys)]
(apply fx/merge cofx send-contact-update-fxs)))
(validate [this] (validate [this]
(when (spec/valid? :message/contact-update this) (when (spec/valid? :message/contact-update this)
this))) this)))

View File

@ -12,7 +12,7 @@
(log/warn "failed sync installation validation" (spec/explain :message/pair-installation this))))) (log/warn "failed sync installation validation" (spec/explain :message/pair-installation this)))))
(defrecord SyncInstallation (defrecord SyncInstallation
[contacts] [contacts account]
protocol/StatusMessage protocol/StatusMessage
(validate [this] (validate [this]
(if (spec/valid? :message/sync-installation this) (if (spec/valid? :message/sync-installation this)

View File

@ -86,8 +86,8 @@
(deftype SyncInstallationHandler [] (deftype SyncInstallationHandler []
Object Object
(tag [this v] "p1") (tag [this v] "p1")
(rep [this {:keys [contacts]}] (rep [this {:keys [contacts account]}]
#js [contacts])) #js [contacts account]))
(deftype PairInstallationHandler [] (deftype PairInstallationHandler []
Object Object
@ -154,8 +154,8 @@
(contact/ContactUpdate. name profile-image address fcm-token)) (contact/ContactUpdate. name profile-image address fcm-token))
"g5" (fn [[chat-id membership-updates message]] "g5" (fn [[chat-id membership-updates message]]
(group-chat/GroupMembershipUpdate. chat-id membership-updates message)) (group-chat/GroupMembershipUpdate. chat-id membership-updates message))
"p1" (fn [[contacts]] "p1" (fn [[contacts account]]
(pairing/SyncInstallation. contacts)) (pairing/SyncInstallation. contacts account))
"p2" (fn [[installation-id device-type]] "p2" (fn [[installation-id device-type]]
(pairing/PairInstallation. installation-id device-type))}})) (pairing/PairInstallation. installation-id device-type))}}))

View File

@ -75,64 +75,85 @@
(deftest handle-sync-installation-test (deftest handle-sync-installation-test
(with-redefs [config/pairing-enabled? (constantly true) (with-redefs [config/pairing-enabled? (constantly true)
identicon/identicon (constantly "generated")] identicon/identicon (constantly "generated")]
(let [old-contact-1 {:name "old-contact-one"
:public-key "contact-1" (testing "syncing contacts"
:last-updated 0 (let [old-contact-1 {:name "old-contact-one"
:photo-path "old-contact-1" :public-key "contact-1"
:pending? true} :last-updated 0
new-contact-1 {:name "new-contact-one" :photo-path "old-contact-1"
:public-key "contact-1" :pending? true}
:last-updated 1 new-contact-1 {:name "new-contact-one"
:photo-path "new-contact-1" :public-key "contact-1"
:pending? false} :last-updated 1
old-contact-2 {:name "old-contact-2" :photo-path "new-contact-1"
:public-key "contact-2" :pending? false}
:last-updated 0 old-contact-2 {:name "old-contact-2"
:photo-path "old-contact-2" :public-key "contact-2"
:pending? false} :last-updated 0
new-contact-2 {:name "new-contact-2" :photo-path "old-contact-2"
:public-key "contact-2" :pending? false}
:last-updated 1 new-contact-2 {:name "new-contact-2"
:photo-path "new-contact-2" :public-key "contact-2"
:pending? false} :last-updated 1
contact-3 {:name "contact-3" :photo-path "new-contact-2"
:public-key "contact-3" :pending? false}
:photo-path "contact-3" contact-3 {:name "contact-3"
:pending? false} :public-key "contact-3"
contact-4 {:name "contact-4" :photo-path "contact-3"
:public-key "contact-4" :pending? false}
:pending? true} contact-4 {:name "contact-4"
local-contact-5 {:name "contact-5" :public-key "contact-4"
:photo-path "local" :pending? true}
:public-key "contact-5" local-contact-5 {:name "contact-5"
:pending? true :photo-path "local"
:last-updated 1} :public-key "contact-5"
remote-contact-5 {:name "contact-5" :pending? true
:public-key "contact-5" :last-updated 1}
:photo-path "remote" remote-contact-5 {:name "contact-5"
:pending? true :public-key "contact-5"
:last-updated 1} :photo-path "remote"
cofx {:db {:account/account {:public-key "us"} :pending? true
:contacts/contacts {"contact-1" old-contact-1 :last-updated 1}
"contact-2" new-contact-2 cofx {:db {:account/account {:public-key "us"}
"contact-3" contact-3 :contacts/contacts {"contact-1" old-contact-1
"contact-5" local-contact-5}}} "contact-2" new-contact-2
sync-message {:contacts {"contact-1" new-contact-1 "contact-3" contact-3
"contact-2" old-contact-2 "contact-5" local-contact-5}}}
"contact-4" contact-4 sync-message {:contacts {"contact-1" new-contact-1
"contact-5" remote-contact-5}} "contact-2" old-contact-2
expected {"contact-1" new-contact-1 "contact-4" contact-4
"contact-2" new-contact-2 "contact-5" remote-contact-5}}
"contact-3" contact-3 expected {"contact-1" new-contact-1
"contact-4" (assoc contact-4 "contact-2" new-contact-2
:photo-path "generated") "contact-3" contact-3
"contact-5" local-contact-5}] "contact-4" (assoc contact-4
(testing "not coming from us" :photo-path "generated")
(is (not (pairing/handle-sync-installation cofx sync-message "not-us")))) "contact-5" local-contact-5}]
(testing "coming from us" (testing "not coming from us"
(is (= expected (get-in (is (not (pairing/handle-sync-installation cofx sync-message "not-us"))))
(pairing/handle-sync-installation cofx sync-message "us") (testing "coming from us"
[:db :contacts/contacts]))))))) (is (= expected (get-in
(pairing/handle-sync-installation cofx sync-message "us")
[:db :contacts/contacts]))))))
(testing "syncing account"
(let [old-account {:name "old-name"
:public-key "us"
:photo-path "old-photo-path"
:last-updated 0}
new-account {:name "new-name"
:public-key "us"
:photo-path "new-photo-path"
:last-updated 1}]
(testing "newer update"
(let [cofx {:db {:account/account old-account}}
sync-message {:account new-account}]
(is (= new-account (get-in (pairing/handle-sync-installation cofx sync-message "us")
[:db :account/account])))))
(testing "older update"
(let [cofx {:db {:account/account new-account}}
sync-message {:account old-account}]
(is (= new-account (get-in (pairing/handle-sync-installation cofx sync-message "us")
[:db :account/account])))))))))
(deftest handle-pair-installation-test (deftest handle-pair-installation-test
(with-redefs [config/pairing-enabled? (constantly true)] (with-redefs [config/pairing-enabled? (constantly true)]
@ -158,7 +179,10 @@
(deftest sync-installation-messages-test (deftest sync-installation-messages-test
(testing "it creates a sync installation message" (testing "it creates a sync installation message"
(let [cofx {:db {:account/account {:public-key "us"} (let [cofx {:db {:account/account {:public-key "us"
:name "name"
:photo-path "photo-path"
:last-updated 1}
:contacts/contacts {"contact-1" {:name "contact-1" :contacts/contacts {"contact-1" {:name "contact-1"
:public-key "contact-1"} :public-key "contact-1"}
"contact-2" {:name "contact-2" "contact-2" {:name "contact-2"
@ -176,9 +200,13 @@
"contact-3" {:name "contact-3" "contact-3" {:name "contact-3"
:public-key "contact-3"} :public-key "contact-3"}
"contact-4" {:name "contact-4" "contact-4" {:name "contact-4"
:public-key "contact-4"}}) :public-key "contact-4"}}
nil)
(transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5" (transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5"
:public-key "contact-5"}})]] :public-key "contact-5"}} nil)
(transport.pairing/SyncInstallation. {} {:photo-path "photo-path"
:name "name"
:last-updated 1})]]
(is (= expected (pairing/sync-installation-messages cofx)))))) (is (= expected (pairing/sync-installation-messages cofx))))))
(deftest handle-bundles-added-test (deftest handle-bundles-added-test