Add pairing

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-10-09 12:43:07 +02:00
parent ca8a3037d8
commit 073dddcee0
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
46 changed files with 512 additions and 38 deletions

3
.env
View File

@ -8,7 +8,8 @@ POW_TARGET=0.002
POW_TIME=1
DEFAULT_NETWORK=mainnet_rpc
DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=1
GROUP_CHATS_ENABLED=0
PAIRING_ENABLED=0
CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=1
HARDWALLET_ENABLED=0

View File

@ -8,8 +8,9 @@ POW_TARGET=0.002
POW_TIME=1
DEFAULT_NETWORK=mainnet_rpc
DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=1
GROUP_CHATS_ENABLED=0
MAINNET_WARNING_ENABLED=1
CACHED_WEBVIEWS_ENABLED=1
EXTENSIONS=1
PFS_ENCRYPTION_ENABLED=0
PAIRING_ENABLED=0

View File

@ -9,6 +9,7 @@ POW_TIME=1
DEFAULT_NETWORK=mainnet_rpc
DEBUG_WEBVIEW=1
GROUP_CHATS_ENABLED=0
PAIRING_ENABLED=0
MAINNET_WARNING_ENABLED=1
EXTENSIONS=1
PFS_ENCRYPTION_ENABLED=0

View File

@ -1 +1 @@
0.16.3
0.16.3-3-g37e4ef01

View File

@ -14021,7 +14021,7 @@
}
},
"web3": {
"version": "git+https://github.com/status-im/web3.js.git#b1c2e2b75f6a190b320dda4be7931d3680ecb727",
"version": "git+https://github.com/status-im/web3.js.git#5fd0b24fd10ce55183a5253a370840540a07db1e",
"requires": {
"bignumber.js": "github:status-im/bignumber.js#cc066a0a3d6bfe0c436c9957f4ea8344bf963c89",
"crypto-js": "3.1.8",

View File

@ -84,7 +84,7 @@
"string_decoder": "0.10.31",
"text-encoding": "^0.6.4",
"url": "0.10.3",
"web3": "https://github.com/status-im/web3.js.git#feature/chat-api",
"web3": "https://github.com/status-im/web3.js.git#features/pairing-message",
"web3-utils": "1.0.0-beta.36"
},
"devDependencies": {

View File

@ -10966,7 +10966,7 @@
}
},
"web3": {
"version": "git+https://github.com/status-im/web3.js.git#b1c2e2b75f6a190b320dda4be7931d3680ecb727",
"version": "git+https://github.com/status-im/web3.js.git#5fd0b24fd10ce55183a5253a370840540a07db1e",
"requires": {
"bignumber.js": "github:status-im/bignumber.js#cc066a0a3d6bfe0c436c9957f4ea8344bf963c89",
"crypto-js": "3.1.8",

View File

@ -66,7 +66,7 @@
"string_decoder": "0.10.31",
"text-encoding": "^0.6.4",
"url": "0.10.3",
"web3": "https://github.com/status-im/web3.js.git#feature/chat-api",
"web3": "https://github.com/status-im/web3.js.git#features/pairing-message",
"web3-utils": "1.0.0-beta.36"
}
}

View File

@ -1,6 +1,7 @@
(ns status-im.chat.subs
(:require [clojure.string :as string]
[re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.config :as utils.config]
[status-im.chat.constants :as chat.constants]
[status-im.chat.commands.core :as commands]
[status-im.chat.commands.input :as commands.input]
@ -76,9 +77,13 @@
platform/ios? kb-height
:default 0)))
(defn active-chats [[contacts chats]]
(reduce (fn [acc [chat-id {:keys [is-active] :as chat}]]
(if is-active
(defn active-chats [[contacts chats {:keys [dev-mode?]}]]
(reduce (fn [acc [chat-id {:keys [group-chat public? is-active] :as chat}]]
(if (and is-active
;; not a group chat
(or (not (and group-chat (not public?)))
;; if it's a group chat
(utils.config/group-chats-enabled? dev-mode?)))
(assoc acc chat-id (if-let [contact (get contacts chat-id)]
(-> chat
(assoc :name (:name contact))
@ -93,6 +98,7 @@
:get-active-chats
:<- [:get-contacts]
:<- [:get-chats]
:<- [:get-current-account]
active-chats)
(reg-sub

View File

@ -6,6 +6,7 @@
status-im.data-store.chats
status-im.data-store.messages
status-im.data-store.contacts
status-im.data-store.installations
status-im.data-store.transport
status-im.data-store.browser
status-im.data-store.accounts

View File

@ -0,0 +1,24 @@
(ns status-im.data-store.installations
(:require [re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]))
(re-frame/reg-cofx
:data-store/get-all-installations
(fn [coeffects _]
(assoc coeffects :all-installations (-> @core/account-realm
(core/get-all :installation)
(core/all-clj :installation)))))
(defn save
"Returns tx function for saving a installation"
[installation]
(fn [realm]
(core/create realm
:installation
installation
true)))
(defn confirm
[installation-id]
(save {:installation-id installation-id
:confirmed? true}))

View File

@ -11,6 +11,7 @@
[status-im.data-store.realm.schemas.account.dapp-permissions :as dapp-permissions]
[status-im.data-store.realm.schemas.account.request :as request]
[status-im.data-store.realm.schemas.account.membership-update :as membership-update]
[status-im.data-store.realm.schemas.account.installation :as installation]
[status-im.data-store.realm.schemas.account.migrations :as migrations]
[taoensso.timbre :as log]))
@ -183,6 +184,19 @@
(def v18 v17)
(def v19 [chat/v8
transport/v7
transport-inbox-topic/v1
contact/v2
message/v7
mailserver/v11
user-status/v1
membership-update/v1
installation/v1
local-storage/v1
browser/v8
dapp-permissions/v9])
;; put schemas ordered by version
(def schemas [{:schema v1
:schemaVersion 1
@ -237,4 +251,7 @@
:migration migrations/v17}
{:schema v18
:schemaVersion 18
:migration migrations/v18}])
:migration migrations/v18}
{:schema v19
:schemaVersion 19
:migration migrations/v19}])

View File

@ -0,0 +1,6 @@
(ns status-im.data-store.realm.schemas.account.installation)
(def v1 {:name :installation
:primaryKey :installation-id
:properties {:installation-id :string
:confirmed? :bool}})

View File

@ -113,3 +113,6 @@
(.objects "transport-inbox-topic")
(.map (fn [inbox-topic _ _]
(aset inbox-topic "last-request" 1)))))
(defn v19 [old-realm new-realm]
(log/debug "migrating v19 account database"))

View File

@ -27,6 +27,7 @@
[status-im.mailserver.core :as mailserver]
[status-im.network.core :as network]
[status-im.notifications.core :as notifications]
[status-im.pairing.core :as pairing]
[status-im.privacy-policy.core :as privacy-policy]
[status-im.protocol.core :as protocol]
[status-im.qr-scanner.core :as qr-scanner]
@ -95,6 +96,7 @@
(re-frame/inject-cofx :data-store/deduplication-ids)
(re-frame/inject-cofx :data-store/get-local-storage-data)
(re-frame/inject-cofx :data-store/get-all-contacts)
(re-frame/inject-cofx :data-store/get-all-installations)
(re-frame/inject-cofx :data-store/get-all-mailservers)
(re-frame/inject-cofx :data-store/transport)
(re-frame/inject-cofx :data-store/transport-inbox-topics)
@ -1092,3 +1094,17 @@
:search/filter-changed
(fn [cofx [_ search-filter]]
(search/filter-changed cofx search-filter)))
;; pairing module
(handlers/register-handler-fx
:pairing.ui/pair-devices-pressed
[]
(fn [cofx _]
(pairing/start cofx)))
(handlers/register-handler-fx
:pairing.ui/synchronize-installation-pressed
[]
(fn [cofx _]
(pairing/send-installation-message cofx)))

View File

@ -304,16 +304,17 @@
message
membership-updates] :as membership-update}
sender-signature]
(when (and config/group-chats-enabled?
(valid-chat-id? chat-id (-> membership-updates first :from)))
(let [previous-chat (get-in cofx [:db :chats chat-id])]
(fx/merge cofx
(update-membership previous-chat membership-update)
#(when (and message
;; don't allow anything but group messages
(instance? protocol/Message message)
(= :group-user-message (:message-type message)))
(protocol/receive message chat-id sender-signature nil %))))))
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])]
(when (and (config/group-chats-enabled? dev-mode?)
(valid-chat-id? chat-id (-> membership-updates first :from)))
(let [previous-chat (get-in cofx [:db :chats chat-id])]
(fx/merge cofx
(update-membership previous-chat membership-update)
#(when (and message
;; don't allow anything but group messages
(instance? protocol/Message message)
(= :group-user-message (:message-type message)))
(protocol/receive message chat-id sender-signature nil %)))))))
(defn handle-sign-success
"Upsert chat and send signed payload to group members"

View File

@ -12,6 +12,7 @@
[status-im.contact.core :as contact]
[status-im.models.dev-server :as models.dev-server]
[status-im.protocol.core :as protocol]
[status-im.pairing.core :as pairing]
[status-im.models.transactions :as transactions]
[status-im.models.wallet :as models.wallet]
[status-im.native-module.core :as status]
@ -149,7 +150,7 @@
(let [{:universal-links/keys [url]
:keys [accounts/accounts accounts/create contacts/contacts networks/networks
network network-status peers-count peers-summary view-id navigation-stack
status-module-initialized? device-UUID semaphores]
pairing/installations status-module-initialized? device-UUID semaphores]
:node/keys [status]
:or {network (get app-db :network)}} db
current-account (get accounts address)
@ -206,6 +207,7 @@
(initialize-account-db address)
(protocol/initialize-protocol address)
(contact/load-contacts)
(pairing/load-installations)
#(when (dev-mode? %)
(models.dev-server/start))
(chat-loading/initialize-chats)

View File

@ -100,7 +100,11 @@
:RequireTopics (get-topics network)
:InstallationID installation-id
:PFSEnabled (or config/pfs-encryption-enabled?
config/group-chats-enabled?))
;; We don't check dev-mode? here as
;; otherwise we would have to restart the node
;; when the user enables it
(config/group-chats-enabled? true)
(config/pairing-enabled? true)))
(and
config/bootnodes-settings-enabled?

View File

@ -0,0 +1,75 @@
(ns status-im.pairing.core
(:require
[status-im.utils.fx :as fx]
[status-im.utils.config :as config]
[status-im.transport.message.protocol :as protocol]
[status-im.data-store.installations :as data-store.installations]
[status-im.utils.identicon :as identicon]
[status-im.data-store.contacts :as data-store.contacts]
[status-im.transport.message.pairing :as transport.pairing]))
(defn start [cofx]
(let [{:keys [current-public-key web3]} (:db cofx)]
{:shh/send-pairing-message {:web3 web3
:src current-public-key
:payload []}}))
(defn merge-contact [local remote]
(let [[old-contact new-contact] (sort-by :last-updated [local remote])]
(-> local
(merge new-contact)
(assoc :photo-path
(or (:photo-path new-contact)
(:photo-path old-contact)
(identicon/identicon (:whisper-identity local))))
(assoc :pending? (boolean
(and (:pending? local true)
(:pending? remote true)))))))
(def merge-contacts (partial merge-with merge-contact))
(defn handle-bundles-added [{:keys [db]} bundle]
(let [dev-mode? (get-in db [:account/account :dev-mode?])]
(when (config/pairing-enabled? dev-mode?)
(let [installation-id (:installationID bundle)
new-installation {:installation-id installation-id
:confirmed? false}]
(when
(and (= (:identity bundle)
(:current-public-key db))
(not (get-in db [:pairing/installations installation-id])))
{:db (assoc-in db
[:pairing/installations installation-id]
new-installation)
:data-store/tx [(data-store.installations/save new-installation)]})))))
(defn sync-installation-messages [{:keys [db]}]
(let [contacts (:contacts/contacts db)]
(map
(fn [[k v]] (transport.pairing/SyncInstallation. {k (dissoc v :photo-path)}))
contacts)))
(defn send-installation-message [cofx]
;; The message needs to be broken up in chunks as we hit the whisper size limit
(let [{:keys [current-public-key web3]} (:db cofx)
sync-messages (sync-installation-messages cofx)]
{:shh/send-direct-message
(map #(hash-map :web3 web3
:src current-public-key
:dst current-public-key
:payload %) sync-messages)}))
(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts]} sender]
(let [dev-mode? (get-in db [:account/account :dev-mode?])]
(when (and (config/pairing-enabled? dev-mode?)
(= sender (get-in cofx [:db :current-public-key])))
(let [new-contacts (merge-contacts (:contacts/contacts db) contacts)]
{:db (assoc db :contacts/contacts new-contacts)
:data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]}))))
(fx/defn load-installations [{:keys [db all-installations]}]
{:db (assoc db :pairing/installations (reduce
(fn [acc {:keys [installation-id] :as i}]
(assoc acc installation-id i))
{}
all-installations))})

View File

@ -3,6 +3,7 @@
[status-im.accounts.login.core :as accounts.login]
[status-im.init.core :as init]
[status-im.node.core :as node]
[status-im.pairing.core :as pairing]
[status-im.transport.inbox :as inbox]
[status-im.transport.message.core :as transport.message]
[status-im.utils.fx :as fx]
@ -55,6 +56,7 @@
"module.initialized" (status-module-initialized cofx)
"envelope.sent" (transport.message/update-envelope-status cofx (:hash event) :sent)
"envelope.expired" (transport.message/update-envelope-status cofx (:hash event) :sent)
"bundles.added" (pairing/handle-bundles-added cofx event)
"mailserver.request.completed" (when (accounts.db/logged-in? cofx)
(inbox/update-inbox-topics cofx {:request-id (:requestID event)
:cursor (:cursor event)}))

View File

@ -104,6 +104,7 @@
(spec/def :message/message-seen (spec/keys :req-un [:message/ids]))
(spec/def :message/group-membership-update (spec/keys :req-un [:group-chat/membership-updates :group-chat/chat-id]))
(spec/def :message/sync-installation (spec/keys :req-un [:contacts/contacts]))
(spec/def :message/message-common (spec/keys :req-un [::content-type ::message-type ::clock-value ::timestamp]))
(spec/def :message.text/content (spec/keys :req-un [:message.content/text]

View File

@ -1,8 +1,10 @@
(ns status-im.transport.impl.receive
(:require [status-im.group-chats.core :as group-chats]
[status-im.contact.core :as contact]
[status-im.pairing.core :as pairing]
[status-im.transport.message.contact :as transport.contact]
[status-im.transport.message.group-chat :as transport.group-chat]
[status-im.transport.message.pairing :as transport.pairing]
[status-im.transport.message.protocol :as protocol]))
(extend-type transport.group-chat/GroupMembershipUpdate
@ -24,3 +26,8 @@
protocol/StatusMessage
(receive [this _ signature timestamp cofx]
(contact/receive-contact-update signature timestamp this cofx)))
(extend-type transport.pairing/SyncInstallation
protocol/StatusMessage
(receive [this _ signature _ cofx]
(pairing/handle-sync-installation cofx this signature)))

View File

@ -1,5 +1,7 @@
(ns status-im.transport.impl.send
(:require [status-im.group-chats.core :as group-chats]
[status-im.pairing.core :as pairing]
[status-im.transport.message.pairing :as transport.pairing]
[status-im.transport.message.group-chat :as transport.group-chat]
[status-im.transport.message.protocol :as protocol]))

View File

@ -0,0 +1,12 @@
(ns status-im.transport.message.pairing
(:require [cljs.spec.alpha :as spec]
[status-im.transport.message.protocol :as protocol]
[taoensso.timbre :as log]))
(defrecord SyncInstallation
[contacts]
protocol/StatusMessage
(validate [this]
(if (spec/valid? :message/sync-installation this)
this
(log/warn "failed sync installation validation" (spec/explain :message/sync-installation this)))))

View File

@ -3,6 +3,7 @@
(:require [status-im.transport.message.contact :as contact]
[status-im.transport.message.protocol :as protocol]
[status-im.transport.message.group-chat :as group-chat]
[status-im.transport.message.pairing :as pairing]
[status-im.constants :as constants]
[cognitect.transit :as transit]))
@ -87,6 +88,12 @@
(rep [this {:keys [chat-id membership-updates message]}]
#js [chat-id membership-updates message]))
(deftype SyncInstallationHandler []
Object
(tag [this v] "p1")
(rep [this {:keys [contacts]}]
#js [contacts]))
(def writer (transit/writer :json
{:handlers
{contact/NewContactKey (NewContactKeyHandler.)
@ -95,7 +102,8 @@
contact/ContactUpdate (ContactUpdateHandler.)
protocol/Message (MessageHandler.)
protocol/MessagesSeen (MessagesSeenHandler.)
group-chat/GroupMembershipUpdate (GroupMembershipUpdateHandler.)}}))
group-chat/GroupMembershipUpdate (GroupMembershipUpdateHandler.)
pairing/SyncInstallation (SyncInstallationHandler.)}}))
;;
;; Reader handlers
@ -146,7 +154,9 @@
"c6" (fn [[name profile-image address fcm-token]]
(contact/ContactUpdate. name profile-image address fcm-token))
"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]]
(pairing/SyncInstallation. contacts))}}))
(defn serialize
"Serializes a record implementing the StatusMessage protocol using the custom writers"

View File

@ -82,6 +82,21 @@
direct-message
(handle-response success-event error-event)))))))
(re-frame/reg-fx
:shh/send-pairing-message
(fn [params]
(let [{:keys [web3 payload src dsts success-event error-event]
:or {error-event :protocol/send-status-message-error}} params
message (clj->js {:sig src
:payload (-> payload
transit/serialize
transport.utils/from-utf8)})]
(.. web3
-shh
(sendPairingMessage
message
(handle-response success-event error-event))))))
(re-frame/reg-fx
:shh/send-group-message
(fn [params]

View File

@ -51,4 +51,4 @@
(def text black)
(def text-gray gray)
(def default-chat-color "#a187d5") ;; legacy
(def default-chat-color "#a187d5") ;; legacy

View File

@ -13,7 +13,7 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.utils.config :as config]))
(defn- options-list [{:keys [anon-id]}]
(defn- options-list [{:keys [dev-mode?]}]
[react/view action-button.styles/actions-list
[action-button/action-button
{:label (i18n/label :t/start-new-chat)
@ -23,14 +23,14 @@
:on-press #(re-frame/dispatch [:navigate-to :new-chat])}]
[action-button/action-separator]
;; Hide behind flag (false by default), till everything is fixed in group chats
(when config/group-chats-enabled?
(when (config/group-chats-enabled? dev-mode?)
[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 [:contact.ui/start-group-chat-pressed])}])
(when config/group-chats-enabled?
(when (config/group-chats-enabled? dev-mode?)
[action-button/action-separator])
[action-button/action-button
{:label (i18n/label :t/new-public-group-chat)
@ -69,4 +69,4 @@
[status-bar/status-bar]
[toolbar/simple-toolbar (i18n/label :t/new)]
[common/separator]
[options-list (assoc account :anon-id device-UUID)]]))
[options-list account]]))

View File

@ -26,6 +26,7 @@
:tab-bar-visible? true
:navigation-stack '()
:contacts/contacts {}
:pairing/installations {}
:qr-codes {}
:group/selected-contacts #{}
:chats {}
@ -198,6 +199,7 @@
:contacts/click-handler
:contacts/click-action
:contacts/click-params
:pairing/installations
:commands/stored-command
:group/selected-contacts
:accounts/accounts

View File

@ -92,7 +92,7 @@
[react/image {:style styles/suggested-contact-image
:source {:uri (:photo-path c)}}]
[react/text {:style styles/new-contact-subtitle} (:name c)]]]))]]
(when config/group-chats-enabled? group-chat-section)
(when (config/group-chats-enabled? true) group-chat-section)
^{:key "publicchat"}
[react/view {:style styles/new-contact-title}
[react/text {:style styles/new-contact-title-text

View File

@ -172,3 +172,6 @@
:margin-top 36
:margin-bottom 16
:font-size 16})
(def pair-button
{:margin-left 32})

View File

@ -3,6 +3,7 @@
(:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.utils.build :as build]
[status-im.utils.config :as config]
[status-im.utils.utils :as utils]
[status-im.ui.components.colors :as colors]
[status-im.i18n :as i18n]
@ -11,6 +12,7 @@
[status-im.utils.gfycat.core :as gfy]
[clojure.string :as string]
[status-im.ui.screens.offline-messaging-settings.views :as offline-messaging.views]
[status-im.ui.screens.pairing.views :as pairing.views]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im.ui.screens.desktop.main.tabs.profile.styles :as styles]
[status-im.ui.screens.profile.user.views :as profile]
@ -74,11 +76,24 @@
[react/text {:style styles/qr-code-copy-text}
(i18n/label :copy-qr)]]]]]))
(defn installations-section [installations]
[react/view
[react/view {:style styles/title-separator}]
[react/text {:style styles/mailserver-title} (i18n/label :devices)]
[react/touchable-highlight {:style styles/pair-button
:on-press pairing.views/pair!}
[react/text (i18n/label :pair)]]
(for [installation installations]
^{:key (:installation-id installation)}
[react/view {:style {:margin-vertical 8}}
[pairing.views/render-row installation]])])
(views/defview advanced-settings []
(views/letsubs [current-wnode-id [:settings/current-wnode]
installations [:pairing/installations]
wnodes [:settings/fleet-wnodes]]
(let [render-fn (offline-messaging.views/render-row current-wnode-id)]
[react/view
[react/scroll-view
[react/text {:style styles/advanced-settings-title
:font :medium}
(i18n/label :advanced-settings)]
@ -88,7 +103,9 @@
(for [node (vals wnodes)]
^{:key (:id node)}
[react/view {:style {:margin-vertical 8}}
[render-fn node]])]])))
[render-fn node]])]
(when (config/pairing-enabled? true)
(installations-section installations))])))
(views/defview backup-recovery-phrase []
[profile.recovery/backup-seed])

View File

@ -0,0 +1,25 @@
(ns status-im.ui.screens.pairing.styles
(:require [status-im.ui.components.colors :as colors])
(:require-macros [status-im.utils.styles :refer [defstyle]]))
(def wrapper
{:flex 1
:background-color :white})
(def installation-item-inner
{:padding-horizontal 16})
(defstyle installation-item
{:flex-direction :row
:background-color :white
:align-items :center
:padding-horizontal 16
:ios {:height 64}
:android {:height 56}})
(defstyle installation-item-name-text
{:color colors/black
:ios {:font-size 17
:letter-spacing -0.2
:line-height 20}
:android {:font-size 16}})

View File

@ -0,0 +1,7 @@
(ns status-im.ui.screens.pairing.subs
(:require [re-frame.core :as re-frame]
[status-im.utils.ethereum.core :as ethereum]))
(re-frame/reg-sub :pairing/installations
:<- [:get :pairing/installations]
vals)

View File

@ -0,0 +1,47 @@
(ns status-im.ui.screens.pairing.views
(:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.utils.config :as config]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.toolbar.actions :as toolbar.actions]
[status-im.ui.screens.profile.components.views :as profile.components]
[status-im.ui.screens.pairing.styles :as styles]))
(defn synchronize-installation! [id]
(re-frame/dispatch [:pairing.ui/synchronize-installation-pressed id]))
(defn pair! []
(re-frame/dispatch [:pairing.ui/pair-devices-pressed]))
(defn render-row [{:keys [installation-id]}]
[react/touchable-highlight
{:on-press #(synchronize-installation! installation-id)
:accessibility-label :installation-item}
[react/view styles/installation-item
[react/view styles/installation-item-inner
[react/text {:style styles/installation-item-name-text}
installation-id]]]])
(defn render-rows [installations]
[react/view styles/wrapper
[list/flat-list {:data installations
:default-separator? false
:key-fn :installation-id
:render-fn render-row}]])
(views/defview installations []
(views/letsubs [installations [:pairing/installations]]
[react/view {:flex 1}
[status-bar/status-bar]
[toolbar/toolbar {}
toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/devices)]
[toolbar/actions
[(toolbar.actions/add false pair!)]]]
(render-rows installations)]))

View File

@ -61,3 +61,6 @@
{:font-size 15
:letter-spacing -0.2
:color colors/blue})
(def pair-button
{:margin-left 32})

View File

@ -175,6 +175,13 @@
{:label-kw :t/bootnodes
:action-fn #(re-frame/dispatch [:navigate-to :bootnodes-settings])
:accessibility-label :bootnodes-settings-button}])
(when (config/pairing-enabled? dev-mode?)
[profile.components/settings-item-separator])
(when (config/pairing-enabled? dev-mode?)
[profile.components/settings-item
{:label-kw :t/devices
:action-fn #(re-frame/dispatch [:navigate-to :installations])
:accessibility-label :pairing-settings-button}])
(when dev-mode?
[profile.components/settings-switch-item
{:label-kw :t/web3-opt-in

View File

@ -18,6 +18,7 @@
status-im.ui.screens.fleet-settings.subs
status-im.ui.screens.offline-messaging-settings.subs
status-im.ui.screens.bootnodes-settings.subs
status-im.ui.screens.pairing.subs
status-im.ui.screens.currency-settings.subs
status-im.ui.screens.browser.subs
status-im.ui.screens.add-new.new-chat.subs

View File

@ -49,6 +49,7 @@
[status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]]
[status-im.ui.screens.extensions.add.views :refer [edit-extension show-extension]]
[status-im.ui.screens.bootnodes-settings.views :refer [bootnodes-settings]]
[status-im.ui.screens.pairing.views :refer [installations]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]]
[status-im.ui.screens.currency-settings.views :refer [currency-settings]]
[status-im.ui.screens.help-center.views :refer [help-center]]
@ -270,6 +271,7 @@
:profile-photo-capture profile-photo-capture
:about-app about-app/about-app
:bootnodes-settings bootnodes-settings
:installations installations
:edit-bootnode edit-bootnode
:offline-messaging-settings offline-messaging-settings
:edit-mailserver edit-mailserver

View File

@ -17,7 +17,14 @@
(def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1")))
(def rpc-networks-only? (enabled? (get-config :RPC_NETWORKS_ONLY "1")))
(def group-chats-enabled? (enabled? (get-config :GROUP_CHATS_ENABLED "0")))
(defn group-chats-enabled? [dev-mode?]
(and (enabled? (get-config :GROUP_CHATS_ENABLED "0"))
(or dev-mode? platform/desktop?)))
(defn pairing-enabled? [dev-mode?]
(and (enabled? (get-config :PAIRING_ENABLED "0"))
(or dev-mode? platform/desktop?)
;; Hard disable as desktop ignores build parameters
false))
(def mainnet-warning-enabled? (enabled? (get-config :MAINNET_WARNING_ENABLED 0)))
(def pfs-encryption-enabled? (enabled? (get-config :PFS_ENCRYPTION_ENABLED "0")))
(def in-app-notifications-enabled? (enabled? (get-config :IN_APP_NOTIFICATIONS_ENABLED 0)))

View File

@ -118,4 +118,4 @@
(testing "it returns only chats with is-active"
(is (= {1 active-chat-1
2 active-chat-2}
(s/active-chats [{} chats]))))))
(s/active-chats [{} chats {}]))))))

View File

@ -29,7 +29,7 @@
(is (= 3 (group-chats/get-last-clock-value {:db {:chats {chat-id initial-message}}} chat-id))))
(deftest handle-group-membership-update
(with-redefs [config/group-chats-enabled? true]
(with-redefs [config/group-chats-enabled? (constantly true)]
(testing "a brand new chat"
(let [actual (->
(group-chats/handle-membership-update {:db {}} initial-message admin)

View File

@ -21,7 +21,7 @@
(is (= "id" (:InstallationID actual))))))
(testing "pfs & group chats disabled"
(with-redefs [config/pfs-encryption-enabled? false
config/group-chats-enabled? false]
config/group-chats-enabled? (constantly false)]
(testing "the user is not logged in"
(let [actual (parse-node-config (node/start cofx nil))]
(is (not (:PFSEnabled actual)))))
@ -37,7 +37,7 @@
(let [actual (parse-node-config (node/start cofx address))]
(is (:PFSEnabled actual))))))
(testing "group chats is enabled"
(with-redefs [config/group-chats-enabled? true]
(with-redefs [config/group-chats-enabled? (constantly true)]
(testing "the user is not logged in"
(let [actual (parse-node-config (node/start cofx nil))]
(is (not (:PFSEnabled actual)))))

View File

@ -0,0 +1,142 @@
(ns status-im.test.pairing.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.transport.message.pairing :as transport.pairing]
[status-im.utils.config :as config]
[status-im.pairing.core :as pairing]))
(deftest merge-contact-test
(testing "vanilla contacts"
(let [contact-1 {:pending? false
:this-should-be-kept true
:last-updated 1
:name "name-v1"
:photo-path "photo-v1"}
contact-2 {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}
expected {:pending? false
:this-should-be-kept true
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}]
(is (= expected (pairing/merge-contact contact-1 contact-2)))))
(testing "without last-updated"
(let [contact-1 {:pending? false
:name "name-v1"
:photo-path "photo-v1"}
contact-2 {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}
expected {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}]
(is (= expected (pairing/merge-contact contact-1 contact-2)))))
(testing "nil contact"
(let [contact-1 nil
contact-2 {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}
expected {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}]
(is (= expected (pairing/merge-contact contact-1 contact-2)))))
(testing "not pending in one device"
(let [contact-1 {:pending? false
:last-updated 1
:name "name-v1"
:photo-path "photo-v1"}
contact-2 {:pending? true
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}
expected {:pending? false
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}]
(is (= expected (pairing/merge-contact contact-1 contact-2)))))
(testing "pending in one device and nil"
(let [contact-1 nil
contact-2 {:pending? true
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}
expected {:pending? true
:last-updated 2
:name "name-v2"
:photo-path "photo-v2"}]
(is (= expected (pairing/merge-contact contact-1 contact-2))))))
(deftest handle-sync-installation-test
(with-redefs [config/pairing-enabled? (constantly true)]
(let [old-contact-1 {:name "old-contact-one"
:last-updated 0
:photo-path "old-contact-1"
:pending? true}
new-contact-1 {:name "new-contact-one"
:last-updated 1
:photo-path "new-contact-1"
:pending? false}
old-contact-2 {:name "old-contact-2"
:last-updated 0
:photo-path "old-contact-2"
:pending? false}
new-contact-2 {:name "new-contact-2"
:last-updated 1
:photo-path "new-contact-2"
:pending? false}
contact-3 {:name "contact-3"
:photo-path "contact-3"
:pending? false}
contact-4 {:name "contact-4"
:photo-path "contact-4"
:pending? true}
cofx {:db {:current-public-key "us"
:contacts/contacts {"contact-1" old-contact-1
"contact-2" new-contact-2
"contact-3" contact-3}}}
sync-message {:contacts {"contact-1" new-contact-1
"contact-2" old-contact-2
"contact-4" contact-4}}
expected {"contact-1" new-contact-1
"contact-2" new-contact-2
"contact-3" contact-3
"contact-4" contact-4}]
(testing "not coming from us"
(is (not (pairing/handle-sync-installation cofx sync-message "not-us"))))
(testing "coming from us"
(is (= expected (get-in
(pairing/handle-sync-installation cofx sync-message "us")
[:db :contacts/contacts])))))))
(deftest sync-installation-messages-test
(testing "it creates a sync installation message"
(let [cofx {:db {:current-public-key "us"
:contacts/contacts {"contact-1" {:name "contact-1"}
"contact-2" {:name "contact-2"}}}}
expected [(transport.pairing/SyncInstallation. {"contact-1" {:name "contact-1"}})
(transport.pairing/SyncInstallation. {"contact-2" {:name "contact-2"}})]]
(is (= expected (pairing/sync-installation-messages cofx))))))
(deftest handle-bundles-added-test
(with-redefs [config/pairing-enabled? (constantly true)]
(let [installation-1 {:confirmed? true
:installation-id "installation-1"}
cofx {:db {:current-public-key "us"
:pairing/installations {"installation-1" installation-1}}}]
(testing "new installations"
(let [new-installation {:identity "us" :installationID "installation-2"}
expected {"installation-1" installation-1
"installation-2" {:confirmed? false
:installation-id "installation-2"}}]
(is (= expected (get-in (pairing/handle-bundles-added cofx new-installation) [:db :pairing/installations])))))
(testing "already existing installation"
(let [old-installation {:identity "us" :installationID "installation-1"}]
(is (not (pairing/handle-bundles-added cofx old-installation)))))
(testing "not from us"
(let [new-installation {:identity "not-us" :installationID "does-not-matter"}]
(is (not (pairing/handle-bundles-added cofx new-installation))))))))

View File

@ -11,6 +11,7 @@
[status-im.test.wallet.transactions.views]
[status-im.test.mailserver.core]
[status-im.test.group-chats.core]
[status-im.test.pairing.core]
[status-im.test.node.core]
[status-im.test.models.bootnode]
[status-im.test.models.account]
@ -78,6 +79,7 @@
'status-im.test.extensions.core
'status-im.test.mailserver.core
'status-im.test.group-chats.core
'status-im.test.pairing.core
'status-im.test.node.core
'status-im.test.models.bootnode
'status-im.test.models.account

View File

@ -4,6 +4,8 @@
"confirm": "Confirm",
"public-chat": "Public chat",
"description": "Description",
"devices": "Devices",
"pair": "Pair devices",
"currency-display-name-tzs": "Tanzanian Shilling",
"currency-display-name-brl": "Brazil Real",
"mainnet-network": "Main network",