From 073dddcee0ba49094de0436c0d1f18d9cc38b039 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 9 Oct 2018 12:43:07 +0200 Subject: [PATCH] Add pairing Signed-off-by: Andrea Maria Piana --- .env | 3 +- .env.jenkins | 3 +- .env.nightly | 1 + STATUS_GO_VERSION | 2 +- desktop_files/package-lock.json | 2 +- desktop_files/package.json | 2 +- mobile_files/package-lock.json | 2 +- mobile_files/package.json | 2 +- src/status_im/chat/subs.cljs | 12 +- src/status_im/data_store/core.cljs | 1 + src/status_im/data_store/installations.cljs | 24 +++ .../realm/schemas/account/core.cljs | 19 ++- .../realm/schemas/account/installation.cljs | 6 + .../realm/schemas/account/migrations.cljs | 3 + src/status_im/events.cljs | 16 ++ src/status_im/group_chats/core.cljs | 21 +-- src/status_im/init/core.cljs | 4 +- src/status_im/node/core.cljs | 6 +- src/status_im/pairing/core.cljs | 75 +++++++++ src/status_im/signals/core.cljs | 2 + src/status_im/transport/db.cljs | 1 + src/status_im/transport/impl/receive.cljs | 7 + src/status_im/transport/impl/send.cljs | 2 + src/status_im/transport/message/pairing.cljs | 12 ++ src/status_im/transport/message/transit.cljs | 14 +- src/status_im/transport/shh.cljs | 15 ++ src/status_im/ui/components/colors.cljs | 2 +- src/status_im/ui/screens/add_new/views.cljs | 8 +- src/status_im/ui/screens/db.cljs | 2 + .../screens/desktop/main/add_new/views.cljs | 2 +- .../desktop/main/tabs/profile/styles.cljs | 3 + .../desktop/main/tabs/profile/views.cljs | 21 ++- src/status_im/ui/screens/pairing/styles.cljs | 25 +++ src/status_im/ui/screens/pairing/subs.cljs | 7 + src/status_im/ui/screens/pairing/views.cljs | 47 ++++++ .../ui/screens/profile/user/styles.cljs | 3 + .../ui/screens/profile/user/views.cljs | 7 + src/status_im/ui/screens/subs.cljs | 1 + src/status_im/ui/screens/views.cljs | 2 + src/status_im/utils/config.cljs | 9 +- test/cljs/status_im/test/chat/subs.cljs | 2 +- .../cljs/status_im/test/group_chats/core.cljs | 2 +- test/cljs/status_im/test/node/core.cljs | 4 +- test/cljs/status_im/test/pairing/core.cljs | 142 ++++++++++++++++++ test/cljs/status_im/test/runner.cljs | 2 + translations/en.json | 2 + 46 files changed, 512 insertions(+), 38 deletions(-) create mode 100644 src/status_im/data_store/installations.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/installation.cljs create mode 100644 src/status_im/pairing/core.cljs create mode 100644 src/status_im/transport/message/pairing.cljs create mode 100644 src/status_im/ui/screens/pairing/styles.cljs create mode 100644 src/status_im/ui/screens/pairing/subs.cljs create mode 100644 src/status_im/ui/screens/pairing/views.cljs create mode 100644 test/cljs/status_im/test/pairing/core.cljs diff --git a/.env b/.env index 2079b78b3f..7b95e16bb4 100644 --- a/.env +++ b/.env @@ -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 diff --git a/.env.jenkins b/.env.jenkins index 9d7ffc3fc2..a21fcbf235 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -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 diff --git a/.env.nightly b/.env.nightly index 294d875989..96c9ca3638 100644 --- a/.env.nightly +++ b/.env.nightly @@ -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 diff --git a/STATUS_GO_VERSION b/STATUS_GO_VERSION index 7eb3095a32..50ef52fde5 100644 --- a/STATUS_GO_VERSION +++ b/STATUS_GO_VERSION @@ -1 +1 @@ -0.16.3 +0.16.3-3-g37e4ef01 diff --git a/desktop_files/package-lock.json b/desktop_files/package-lock.json index 62fef9697e..c57a36a23e 100644 --- a/desktop_files/package-lock.json +++ b/desktop_files/package-lock.json @@ -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", diff --git a/desktop_files/package.json b/desktop_files/package.json index f760c986ee..5f3ee0cf3d 100644 --- a/desktop_files/package.json +++ b/desktop_files/package.json @@ -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": { diff --git a/mobile_files/package-lock.json b/mobile_files/package-lock.json index c035746c4f..580eb0c34f 100644 --- a/mobile_files/package-lock.json +++ b/mobile_files/package-lock.json @@ -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", diff --git a/mobile_files/package.json b/mobile_files/package.json index fc12437a17..5d630199c5 100644 --- a/mobile_files/package.json +++ b/mobile_files/package.json @@ -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" } } diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index ac28afc7fe..f1201846e2 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -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 diff --git a/src/status_im/data_store/core.cljs b/src/status_im/data_store/core.cljs index 3ac77ca3f5..10b317bb7e 100644 --- a/src/status_im/data_store/core.cljs +++ b/src/status_im/data_store/core.cljs @@ -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 diff --git a/src/status_im/data_store/installations.cljs b/src/status_im/data_store/installations.cljs new file mode 100644 index 0000000000..a0caca3dde --- /dev/null +++ b/src/status_im/data_store/installations.cljs @@ -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})) diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 7ea59c0c0f..c5e293fbdf 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -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}]) diff --git a/src/status_im/data_store/realm/schemas/account/installation.cljs b/src/status_im/data_store/realm/schemas/account/installation.cljs new file mode 100644 index 0000000000..21c399823c --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/installation.cljs @@ -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}}) diff --git a/src/status_im/data_store/realm/schemas/account/migrations.cljs b/src/status_im/data_store/realm/schemas/account/migrations.cljs index 7ee6c38a60..af0c05ba43 100644 --- a/src/status_im/data_store/realm/schemas/account/migrations.cljs +++ b/src/status_im/data_store/realm/schemas/account/migrations.cljs @@ -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")) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c821897d8f..26b89b2fbe 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -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))) diff --git a/src/status_im/group_chats/core.cljs b/src/status_im/group_chats/core.cljs index 57cf60a7f0..a76d8f3689 100644 --- a/src/status_im/group_chats/core.cljs +++ b/src/status_im/group_chats/core.cljs @@ -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" diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index 360d4f0489..2dc035d031 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -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) diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index 026c9eef08..99a7d4e232 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -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? diff --git a/src/status_im/pairing/core.cljs b/src/status_im/pairing/core.cljs new file mode 100644 index 0000000000..73df019351 --- /dev/null +++ b/src/status_im/pairing/core.cljs @@ -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))}) diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 52b7e03d0b..94094e351e 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -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)})) diff --git a/src/status_im/transport/db.cljs b/src/status_im/transport/db.cljs index a53c54bf26..2261502f30 100644 --- a/src/status_im/transport/db.cljs +++ b/src/status_im/transport/db.cljs @@ -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] diff --git a/src/status_im/transport/impl/receive.cljs b/src/status_im/transport/impl/receive.cljs index dd5efde9be..f365276a11 100644 --- a/src/status_im/transport/impl/receive.cljs +++ b/src/status_im/transport/impl/receive.cljs @@ -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))) diff --git a/src/status_im/transport/impl/send.cljs b/src/status_im/transport/impl/send.cljs index a120315136..e4237ee4c1 100644 --- a/src/status_im/transport/impl/send.cljs +++ b/src/status_im/transport/impl/send.cljs @@ -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])) diff --git a/src/status_im/transport/message/pairing.cljs b/src/status_im/transport/message/pairing.cljs new file mode 100644 index 0000000000..1fc91f56b8 --- /dev/null +++ b/src/status_im/transport/message/pairing.cljs @@ -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))))) diff --git a/src/status_im/transport/message/transit.cljs b/src/status_im/transport/message/transit.cljs index 4f9c4d8f73..cebe01c8ad 100644 --- a/src/status_im/transport/message/transit.cljs +++ b/src/status_im/transport/message/transit.cljs @@ -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" diff --git a/src/status_im/transport/shh.cljs b/src/status_im/transport/shh.cljs index 89e259a289..ef74eeec83 100644 --- a/src/status_im/transport/shh.cljs +++ b/src/status_im/transport/shh.cljs @@ -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] diff --git a/src/status_im/ui/components/colors.cljs b/src/status_im/ui/components/colors.cljs index 131a2f017a..75368c7cba 100644 --- a/src/status_im/ui/components/colors.cljs +++ b/src/status_im/ui/components/colors.cljs @@ -51,4 +51,4 @@ (def text black) (def text-gray gray) -(def default-chat-color "#a187d5") ;; legacy \ No newline at end of file +(def default-chat-color "#a187d5") ;; legacy diff --git a/src/status_im/ui/screens/add_new/views.cljs b/src/status_im/ui/screens/add_new/views.cljs index 0e8371fe0b..2638fa8e28 100644 --- a/src/status_im/ui/screens/add_new/views.cljs +++ b/src/status_im/ui/screens/add_new/views.cljs @@ -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]])) diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index f6af028d27..d7495c23d9 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -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 diff --git a/src/status_im/ui/screens/desktop/main/add_new/views.cljs b/src/status_im/ui/screens/desktop/main/add_new/views.cljs index 1f737eca5e..7539aa4358 100644 --- a/src/status_im/ui/screens/desktop/main/add_new/views.cljs +++ b/src/status_im/ui/screens/desktop/main/add_new/views.cljs @@ -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 diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs index c3bdb5611b..3edbf0e71b 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs @@ -172,3 +172,6 @@ :margin-top 36 :margin-bottom 16 :font-size 16}) + +(def pair-button + {:margin-left 32}) diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs index 6d4e0c0627..f1f96d8544 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs @@ -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]) diff --git a/src/status_im/ui/screens/pairing/styles.cljs b/src/status_im/ui/screens/pairing/styles.cljs new file mode 100644 index 0000000000..3d497720c0 --- /dev/null +++ b/src/status_im/ui/screens/pairing/styles.cljs @@ -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}}) diff --git a/src/status_im/ui/screens/pairing/subs.cljs b/src/status_im/ui/screens/pairing/subs.cljs new file mode 100644 index 0000000000..3726a4c972 --- /dev/null +++ b/src/status_im/ui/screens/pairing/subs.cljs @@ -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) diff --git a/src/status_im/ui/screens/pairing/views.cljs b/src/status_im/ui/screens/pairing/views.cljs new file mode 100644 index 0000000000..5bdd5b48bd --- /dev/null +++ b/src/status_im/ui/screens/pairing/views.cljs @@ -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)])) diff --git a/src/status_im/ui/screens/profile/user/styles.cljs b/src/status_im/ui/screens/profile/user/styles.cljs index 6b023d4b6c..5b47fc61d8 100644 --- a/src/status_im/ui/screens/profile/user/styles.cljs +++ b/src/status_im/ui/screens/profile/user/styles.cljs @@ -61,3 +61,6 @@ {:font-size 15 :letter-spacing -0.2 :color colors/blue}) + +(def pair-button + {:margin-left 32}) diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index 222a8e30e3..69aa474356 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -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 diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index 71d798957b..44605c00b5 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -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 diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 6ef33260d1..7e8ce0abe4 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -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 diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index 4e82b25721..af4c69a99f 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -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))) diff --git a/test/cljs/status_im/test/chat/subs.cljs b/test/cljs/status_im/test/chat/subs.cljs index f37fcd6873..9b5b92b542 100644 --- a/test/cljs/status_im/test/chat/subs.cljs +++ b/test/cljs/status_im/test/chat/subs.cljs @@ -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 {}])))))) diff --git a/test/cljs/status_im/test/group_chats/core.cljs b/test/cljs/status_im/test/group_chats/core.cljs index 9a940bf058..fe01d41ad3 100644 --- a/test/cljs/status_im/test/group_chats/core.cljs +++ b/test/cljs/status_im/test/group_chats/core.cljs @@ -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) diff --git a/test/cljs/status_im/test/node/core.cljs b/test/cljs/status_im/test/node/core.cljs index ab48ae6e40..b34770d7a3 100644 --- a/test/cljs/status_im/test/node/core.cljs +++ b/test/cljs/status_im/test/node/core.cljs @@ -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))))) diff --git a/test/cljs/status_im/test/pairing/core.cljs b/test/cljs/status_im/test/pairing/core.cljs new file mode 100644 index 0000000000..14641ca64d --- /dev/null +++ b/test/cljs/status_im/test/pairing/core.cljs @@ -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)))))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index fd07e176ac..a0d1a3bd59 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -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 diff --git a/translations/en.json b/translations/en.json index 3efca9d44d..fb092234a0 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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",