[iOS] Perform preflight check for local network permission (#16150)

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
Mohamed Javid 2023-06-09 18:25:15 +08:00 committed by GitHub
parent 9b52bba95d
commit 2df1b46975
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 138 additions and 57 deletions

View File

@ -304,6 +304,14 @@ RCT_EXPORT_METHOD(hashMessage:(NSString *)message
callback(@[result]); callback(@[result]);
} }
RCT_EXPORT_METHOD(localPairingPreflightOutboundCheck:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"LocalPairingPreflightOutboundCheck() method called");
#endif
NSString *result = StatusgoLocalPairingPreflightOutboundCheck();
callback(@[result]);
}
RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)configJSON RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) { callback:(RCTResponseSenderBlock)callback) {

View File

@ -248,6 +248,13 @@
(log/debug "[native-module] hash-message") (log/debug "[native-module] hash-message")
(.hashMessage ^js (status) message callback)) (.hashMessage ^js (status) message callback))
(defn local-pairing-preflight-outbound-check
"Checks whether the device has allows connecting to the local server"
[callback]
(log/info "[native-module] Performing local pairing preflight check")
(when platform/ios?
(.localPairingPreflightOutboundCheck ^js (status) callback)))
(defn get-connection-string-for-bootstrapping-another-device (defn get-connection-string-for-bootstrapping-another-device
"Generates connection string form status-go for the purpose of local pairing on the sender end" "Generates connection string form status-go for the purpose of local pairing on the sender end"
[config-json callback] [config-json callback]

View File

@ -10,7 +10,8 @@
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.contexts.chat.messages.link-preview.events :as link-preview] [status-im2.contexts.chat.messages.link-preview.events :as link-preview]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im2.constants :as constants])) [status-im2.constants :as constants]
[quo2.foundations.colors :as colors]))
(rf/defn status-node-started (rf/defn status-node-started
[{db :db :as cofx} {:keys [error]}] [{db :db :as cofx} {:keys [error]}]
@ -55,7 +56,7 @@
:peers-count (count (:peers peer-stats)))})) :peers-count (count (:peers peer-stats)))}))
(rf/defn handle-local-pairing-signals (rf/defn handle-local-pairing-signals
[{:keys [db] :as cofx} {:keys [type action data] :as event}] [{:keys [db] :as cofx} {:keys [type action data error] :as event}]
(log/info "local pairing signal received" (log/info "local pairing signal received"
{:event event}) {:event event})
(let [{:keys [account password]} data (let [{:keys [account password]} data
@ -78,7 +79,7 @@
(and (some? account) (some? password))) (and (some? account) (some? password)))
multiaccount-data (when received-account? multiaccount-data (when received-account?
(merge account {:password password})) (merge account {:password password}))
navigate-to-syncing-devices? (and connection-success? receiver?) navigate-to-syncing-devices? (and (or connection-success? error-on-pairing?) receiver?)
user-in-syncing-devices-screen? (= (:view-id db) :syncing-progress)] user-in-syncing-devices-screen? (= (:view-id db) :syncing-progress)]
(merge {:db (cond-> db (merge {:db (cond-> db
connection-success? connection-success?
@ -92,12 +93,21 @@
completed-pairing? completed-pairing?
(assoc-in [:syncing :pairing-status] :completed))} (assoc-in [:syncing :pairing-status] :completed))}
(when (and navigate-to-syncing-devices? (not user-in-syncing-devices-screen?)) (cond
{:dispatch [:navigate-to :syncing-progress]}) (and navigate-to-syncing-devices? (not user-in-syncing-devices-screen?))
(when (and completed-pairing? sender?) {:dispatch [:navigate-to :syncing-progress]}
{:dispatch [:syncing/clear-states]})
(when (and completed-pairing? receiver?) (and completed-pairing? sender?)
{:dispatch [:multiaccounts.login/local-paired-user]})))) {:dispatch [:syncing/clear-states]}
(and completed-pairing? receiver?)
{:dispatch [:multiaccounts.login/local-paired-user]}
(and error-on-pairing? (some? error))
{:dispatch [:toasts/upsert
{:icon :i/alert
:icon-color colors/danger-50
:text error}]}))))
(rf/defn process (rf/defn process
{:events [:signals/signal-received]} {:events [:signals/signal-received]}

View File

@ -1,14 +1,15 @@
(ns status-im2.contexts.syncing.events (ns status-im2.contexts.syncing.events
(:require [native-module.core :as native-module] (:require [native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.data-store.settings :as data-store.settings]
[status-im.node.core :as node]
[status-im.utils.platform :as utils.platform]
[status-im2.config :as config]
[status-im2.constants :as constants]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security] [utils.security.core :as security]
[status-im2.config :as config] [utils.transforms :as transforms]))
[status-im.node.core :as node]
[re-frame.core :as re-frame]
[status-im.data-store.settings :as data-store.settings]
[status-im.utils.platform :as utils.platform]
[status-im2.constants :as constants]))
(rf/defn local-pairing-update-role (rf/defn local-pairing-update-role
{:events [:syncing/update-role]} {:events [:syncing/update-role]}
@ -31,6 +32,19 @@
:custom-bootnodes-enabled? false}}] :custom-bootnodes-enabled? false}}]
(node/get-multiaccount-node-config db))) (node/get-multiaccount-node-config db)))
(rf/defn preflight-outbound-check-for-local-pairing
{:events [:syncing/preflight-outbound-check]}
[_ set-checks-passed]
(let [callback
(fn [raw-response]
(log/info "Local pairing preflight check"
{:response raw-response
:event :syncing/preflight-outbound-check})
(let [^js response-js (transforms/json->js raw-response)
error (transforms/js->clj (.-error response-js))]
(set-checks-passed (empty? error))))]
(native-module/local-pairing-preflight-outbound-check callback)))
(rf/defn initiate-local-pairing-with-connection-string (rf/defn initiate-local-pairing-with-connection-string
{:events [:syncing/input-connection-string-for-bootstrapping] {:events [:syncing/input-connection-string-for-bootstrapping]
:interceptors [(re-frame/inject-cofx :random-guid-generator)]} :interceptors [(re-frame/inject-cofx :random-guid-generator)]}

View File

@ -13,10 +13,35 @@
[status-im2.contexts.syncing.scan-sync-code.style :as style] [status-im2.contexts.syncing.scan-sync-code.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.contexts.syncing.utils :as sync-utils])) [status-im2.contexts.syncing.utils :as sync-utils]
[status-im.utils.platform :as platform]))
;; Android allow local network access by default. So, we need this check on iOS only.
(defonce preflight-check-passed? (reagent/atom (if platform/ios? false true)))
(defonce camera-permission-granted? (reagent/atom false)) (defonce camera-permission-granted? (reagent/atom false))
(defn request-camera-permission
[]
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/camera-permission-denied)}])}]))
(defn perform-preflight-check
"Performing the check for the first time
will trigger local network access permission in iOS.
This permission is required for local pairing
https://github.com/status-im/status-mobile/issues/16135"
[]
(rf/dispatch [:syncing/preflight-outbound-check #(reset! preflight-check-passed? %)]))
(defn- header (defn- header
[active-tab read-qr-once? title] [active-tab read-qr-once? title]
[:<> [:<>
@ -59,27 +84,49 @@
(reset! active-tab id) (reset! active-tab id)
(reset! read-qr-once? false))}]]]) (reset! read-qr-once? false))}]]])
(defn- camera-permission-view (defn get-labels-and-on-press-method
[request-camera-permission] []
(if @camera-permission-granted?
{:title-label-key :t/enable-access-to-local-network
:description-label-key :t/to-pair-with-your-other-device-in-the-network
:button-icon :i/world
:button-label :t/enable-network-access
:accessibility-label :perform-preflight-check
:on-press perform-preflight-check}
{:title-label-key :t/enable-access-to-camera
:description-label-key :t/to-scan-a-qr-enable-your-camera
:button-icon :i/camera
:button-label :t/enable-camera
:accessibility-label :request-camera-permission
:on-press request-camera-permission}))
(defn- camera-and-local-network-access-permission-view
[]
(let [{:keys [title-label-key
description-label-key
button-icon
button-label
accessibility-label
on-press]} (get-labels-and-on-press-method)]
[rn/view {:style style/camera-permission-container} [rn/view {:style style/camera-permission-container}
[quo/text [quo/text
{:size :paragraph-1 {:size :paragraph-1
:weight :medium :weight :medium
:style style/enable-camera-access-header} :style style/enable-camera-access-header}
(i18n/label :t/enable-access-to-camera)] (i18n/label title-label-key)]
[quo/text [quo/text
{:size :paragraph-2 {:size :paragraph-2
:weight :regular :weight :regular
:style style/enable-camera-access-sub-text} :style style/enable-camera-access-sub-text}
(i18n/label :t/to-scan-a-qr-enable-your-camera)] (i18n/label description-label-key)]
[quo/button [quo/button
{:before :i/camera {:before button-icon
:type :primary :type :primary
:size 32 :size 32
:accessibility-label :request-camera-permission :accessibility-label accessibility-label
:override-theme :dark :override-theme :dark
:on-press request-camera-permission} :on-press on-press}
(i18n/label :t/enable-camera)]]) (i18n/label button-label)]]))
(defn- qr-scan-hole-area (defn- qr-scan-hole-area
[qr-view-finder] [qr-view-finder]
@ -117,14 +164,16 @@
(i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]])) (i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]]))
(defn- scan-qr-code-tab (defn- scan-qr-code-tab
[qr-view-finder request-camera-permission] [qr-view-finder]
[:<> [:<>
[rn/view {:style style/scan-qr-code-container}] [rn/view {:style style/scan-qr-code-container}]
(when (empty? @qr-view-finder) (when (empty? @qr-view-finder)
[qr-scan-hole-area qr-view-finder]) [qr-scan-hole-area qr-view-finder])
(if (and @camera-permission-granted? (boolean (not-empty @qr-view-finder))) (if (and @preflight-check-passed?
@camera-permission-granted?
(boolean (not-empty @qr-view-finder)))
[viewfinder @qr-view-finder] [viewfinder @qr-view-finder]
[camera-permission-view request-camera-permission])]) [camera-and-local-network-access-permission-view])])
(defn- enter-sync-code-tab (defn- enter-sync-code-tab
[] []
@ -187,19 +236,7 @@
[{:keys [title show-bottom-view? background]}] [{:keys [title show-bottom-view? background]}]
(let [insets (safe-area/get-insets) (let [insets (safe-area/get-insets)
active-tab (reagent/atom 1) active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {}) qr-view-finder (reagent/atom {})]
request-camera-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label
:t/camera-permission-denied)}])}]))]
(fn [] (fn []
(let [camera-ref (atom nil) (let [camera-ref (atom nil)
read-qr-once? (atom false) read-qr-once? (atom false)
@ -215,7 +252,9 @@
3000) 3000)
(check-qr-code-data data))) (check-qr-code-data data)))
scan-qr-code-tab? (= @active-tab 1) scan-qr-code-tab? (= @active-tab 1)
show-camera? (and scan-qr-code-tab? @camera-permission-granted?) show-camera? (and scan-qr-code-tab?
@camera-permission-granted?
@preflight-check-passed?)
show-holes? (and show-camera? show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))] (boolean (not-empty @qr-view-finder)))]
(rn/use-effect (rn/use-effect
@ -230,7 +269,7 @@
[rn/view {:style (style/root-container (:top insets))} [rn/view {:style (style/root-container (:top insets))}
[header active-tab read-qr-once? title] [header active-tab read-qr-once? title]
(case @active-tab (case @active-tab
1 [scan-qr-code-tab qr-view-finder request-camera-permission] 1 [scan-qr-code-tab qr-view-finder]
2 [enter-sync-code-tab] 2 [enter-sync-code-tab]
nil) nil)
[rn/view {:style style/flex-spacer}] [rn/view {:style style/flex-spacer}]

View File

@ -2092,6 +2092,9 @@
"synchronise-your-data-across-your-devices": "Synchronise your data across your devices", "synchronise-your-data-across-your-devices": "Synchronise your data across your devices",
"scan-sync-qr-code": "Scan QR code", "scan-sync-qr-code": "Scan QR code",
"enter-sync-code": "Enter sync code", "enter-sync-code": "Enter sync code",
"enable-access-to-local-network": "Enable access to local network",
"to-pair-with-your-other-device-in-the-network": "To pair with your other device in the network",
"enable-network-access": "Enable network access",
"enable-access-to-camera": "Enable access to camera", "enable-access-to-camera": "Enable access to camera",
"link-preview-loading-message": "Generating preview", "link-preview-loading-message": "Generating preview",
"to-scan-a-qr-enable-your-camera": "To scan a QR, enable your camera", "to-scan-a-qr-enable-your-camera": "To scan a QR, enable your camera",