diff --git a/.env.prod b/.env.prod
index d0204dd8ae..5800678c76 100644
--- a/.env.prod
+++ b/.env.prod
@@ -20,3 +20,4 @@ GROUP_CHATS_ENABLED=0
USE_SYM_KEY=0
MAINNET_WARNING_ENABLED=1
SPAM_BUTTON_DETECTION_ENABLED=1
+UNIVERSAL_LINKS_ENABLED=0
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f4ceb43595..7f60c3df36 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -54,6 +54,14 @@
+
+
+
+
+
+
+
+
> selected-contacts
@@ -407,13 +410,16 @@
(navigate-to-chat random-id {})
(transport.message/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
+(defn show-profile [identity {:keys [db] :as cofx}]
+ (handlers-macro/merge-fx cofx
+ {:db (assoc db :contacts/identity identity)}
+ (navigation/navigate-forget :profile)))
+
(handlers/register-handler-fx
:show-profile
[re-frame/trim-v]
- (fn [{:keys [db] :as cofx} [identity]]
- (handlers-macro/merge-fx cofx
- {:db (assoc db :contacts/identity identity)}
- (navigation/navigate-forget :profile))))
+ (fn [cofx [identity]]
+ (show-profile identity cofx)))
(handlers/register-handler-fx
:resend-message
diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs
index 0ae6d22e39..90f2a7f55e 100644
--- a/src/status_im/ui/screens/db.cljs
+++ b/src/status_im/ui/screens/db.cljs
@@ -150,6 +150,10 @@
(spec/def ::device-UUID (spec/nilable string?))
+;;;;UNIVERSAL LINKS
+
+(spec/def :universal-links/url (spec/nilable string?))
+
(spec/def ::db (allowed-keys
:opt
[:contacts/contacts
@@ -187,6 +191,7 @@
:inbox/last-received
:inbox/current-id
:inbox/fetching?
+ :universal-links/url
:browser/browsers
:browser/options
:new/open-dapp
diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs
index d547f318ae..426985891f 100644
--- a/src/status_im/ui/screens/events.cljs
+++ b/src/status_im/ui/screens/events.cljs
@@ -13,6 +13,9 @@
status-im.ui.screens.group.chat-settings.events
status-im.ui.screens.group.events
[status-im.ui.screens.navigation :as navigation]
+ [status-im.utils.universal-links.core :as universal-links]
+
+ status-im.utils.universal-links.events
status-im.ui.screens.add-new.new-chat.navigation
status-im.ui.screens.network-settings.events
status-im.ui.screens.profile.events
@@ -315,7 +318,7 @@
(handlers/register-handler-fx
:initialize-account
- (fn [_ [_ address events-after]]
+ (fn [cofx [_ address events-after]]
{:dispatch-n (cond-> [[:initialize-account-db address]
[:initialize-protocol address]
[:fetch-web3-node-version]
@@ -329,7 +332,8 @@
[:update-transactions]
[:get-fcm-token]
[:update-sign-in-time]
- [:show-mainnet-is-default-alert]]
+ [:show-mainnet-is-default-alert]
+ (universal-links/stored-url-event cofx)]
(seq events-after) (into events-after))}))
(handlers/register-handler-fx
diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs
index fcbdb14d20..e353037589 100644
--- a/src/status_im/ui/screens/views.cljs
+++ b/src/status_im/ui/screens/views.cljs
@@ -2,6 +2,7 @@
(:require-macros [status-im.utils.views :refer [defview letsubs] :as views])
(:require [re-frame.core :refer [dispatch]]
[status-im.utils.platform :refer [android?]]
+ [status-im.utils.universal-links.core :as utils.universal-links]
[status-im.ui.components.react :refer [view modal create-main-screen-view] :as react]
[status-im.ui.components.styles :as common-styles]
[status-im.ui.screens.main-tabs.views :refer [main-tabs]]
@@ -134,7 +135,9 @@
(defview main []
(letsubs [signed-up? [:signed-up?]
view-id [:get :view-id]]
- {:component-will-update (fn [] (react/dismiss-keyboard!))}
+ {:component-did-mount utils.universal-links/initialize
+ :component-will-unmount utils.universal-links/finalize
+ :component-will-update (fn [] (react/dismiss-keyboard!))}
(when view-id
(let [component (get-main-component view-id)
main-screen-view (create-main-screen-view view-id)]
diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs
index ecd07307b3..4dd65a60df 100644
--- a/src/status_im/utils/config.cljs
+++ b/src/status_im/utils/config.cljs
@@ -22,6 +22,7 @@
(def stub-status-go? (enabled? (get-config :STUB_STATUS_GO 0)))
(def offline-inbox-enabled? (enabled? (get-config :OFFLINE_INBOX_ENABLED "1")))
(def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1")))
+(def universal-links-enabled? (enabled? (get-config :UNIVERSAL_LINK_ENABLED "1")))
(def log-level
(-> (get-config :LOG_LEVEL "error")
string/lower-case
diff --git a/src/status_im/utils/universal_links/core.cljs b/src/status_im/utils/universal_links/core.cljs
new file mode 100644
index 0000000000..0aa3282c05
--- /dev/null
+++ b/src/status_im/utils/universal_links/core.cljs
@@ -0,0 +1,100 @@
+(ns status-im.utils.universal-links.core
+ (:require
+ [taoensso.timbre :as log]
+ [re-frame.core :as re-frame]
+ [status-im.utils.config :as config]
+ [status-im.chat.events :as chat.events]
+ [status-im.models.account :as models.account]
+ [status-im.ui.components.react :as react]))
+
+(def public-chat-regex #".*/chat/public/(.*)$")
+(def profile-regex #".*/user/(.*)$")
+
+(defn handle-public-chat [public-chat cofx]
+ (log/info "universal-links: handling public chat " public-chat)
+ (chat.events/create-new-public-chat public-chat cofx))
+
+(defn handle-view-profile [profile-id cofx]
+ (log/info "universal links: handling view profile" profile-id)
+ (chat.events/show-profile profile-id cofx))
+
+(defn handle-not-found [full-url]
+ (log/info "universal links: no handler for " full-url))
+
+(defn match-url [url regex]
+ (some->> url
+ (re-matches regex)
+ peek))
+
+(defn stored-url-event
+ "Return an event description for processing a url if in the database"
+ [{:keys [db]}]
+ (when-let [url (:universal-links/url db)]
+ [:handle-universal-link url]))
+
+(defn dispatch-url
+ "Dispatch url so we can get access to re-frame/db"
+ [url]
+ (if-not (nil? url)
+ (re-frame/dispatch [:handle-universal-link url])
+ (log/debug "universal links: no url")))
+
+(defn store-url-for-later
+ "Store the url in the db to be processed on login"
+ [url {:keys [db]}]
+ (assoc-in {:db db} [:db :universal-links/url] url))
+
+(defn clear-url
+ "Remove a url from the db"
+ [{:keys [db]}]
+ (update {:db db} :db dissoc :universal-links/url))
+
+(defn route-url
+ "Match a url against a list of routes and handle accordingly"
+ [url cofx]
+ (cond
+ (match-url url public-chat-regex)
+ (handle-public-chat (match-url url public-chat-regex) cofx)
+
+ (match-url url profile-regex)
+ (handle-view-profile (match-url url profile-regex) cofx)
+
+ :else (handle-not-found url)))
+
+(defn handle-url
+ "Store url in the database if the user is not logged in, to be processed
+ on login, otherwise just handle it"
+ [url cofx]
+ (if (models.account/logged-in? cofx)
+ (do
+ (clear-url cofx)
+ (route-url url cofx))
+ (store-url-for-later url cofx)))
+
+(defn unwrap-js-url [e]
+ (-> e
+ (js->clj :keywordize-keys true)
+ :url))
+
+(def url-event-listener
+ (comp dispatch-url unwrap-js-url))
+
+(defn initialize
+ "Add an event listener for handling background->foreground transition
+ and handles incoming url if the app has been started by clicking on a link"
+ []
+ (when config/universal-links-enabled?
+ (log/debug "universal-links: initializing")
+ (.. react/linking
+ (getInitialURL)
+ (then dispatch-url))
+ (.. react/linking
+ (addEventListener "url" url-event-listener))))
+
+(defn finalize
+ "Remove event listener for url"
+ []
+ (when config/universal-links-enabled?
+ (log/debug "universal-links: finalizing")
+ (.. react/linking
+ (removeEventListener "url" url-event-listener))))
diff --git a/src/status_im/utils/universal_links/events.cljs b/src/status_im/utils/universal_links/events.cljs
new file mode 100644
index 0000000000..b34b72fcb4
--- /dev/null
+++ b/src/status_im/utils/universal_links/events.cljs
@@ -0,0 +1,14 @@
+(ns status-im.utils.universal-links.events
+ (:require [re-frame.core :as re-frame]
+ [taoensso.timbre :as log]
+ [status-im.utils.config :as config]
+ [status-im.utils.handlers :as handlers]
+ [status-im.utils.handlers-macro :as handlers-macro]
+ [status-im.utils.universal-links.core :as universal-links]))
+
+(handlers/register-handler-fx
+ :handle-universal-link
+ (fn [cofx [_ url]]
+ (log/debug "universal links: event received for " url)
+ (when config/universal-links-enabled?
+ (universal-links/handle-url url cofx))))
diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs
index 7a7edc76f2..0f123cd372 100644
--- a/test/cljs/status_im/test/runner.cljs
+++ b/test/cljs/status_im/test/runner.cljs
@@ -42,6 +42,7 @@
[status-im.test.utils.mixpanel]
[status-im.test.utils.prices]
[status-im.test.utils.keychain.core]
+ [status-im.test.utils.universal-links.core]
[status-im.test.ui.screens.accounts.login.events]))
(enable-console-print!)
@@ -95,4 +96,5 @@
'status-im.test.utils.mixpanel
'status-im.test.utils.prices
'status-im.test.utils.keychain.core
+ 'status-im.test.utils.universal-links.core
'status-im.test.ui.screens.accounts.login.events)
diff --git a/test/cljs/status_im/test/utils/universal_links/core.cljs b/test/cljs/status_im/test/utils/universal_links/core.cljs
new file mode 100644
index 0000000000..3ee21366c7
--- /dev/null
+++ b/test/cljs/status_im/test/utils/universal_links/core.cljs
@@ -0,0 +1,57 @@
+(ns status-im.test.utils.universal-links.core
+ (:require [cljs.test :refer-macros [deftest is testing]]
+ [re-frame.core :as re-frame]
+ [status-im.utils.universal-links.core :as links]))
+
+(deftest handle-url-test
+ (testing "the user is not logged in"
+ (testing "it stores the url for later processing"
+ (is (= {:db {:universal-links/url "some-url"}}
+ (links/handle-url "some-url" {:db {}})))))
+ (testing "the user is logged in"
+ (let [db {:account/account {:public-key "pk"}
+ :universal-links/url "some-url"}]
+ (testing "it clears the url"
+ (is (nil? (get-in (links/handle-url "some-url"
+ {:db db})
+ [:db :universal-links/url]))))
+ (testing "a public chat link"
+ (testing "it joins the chat"
+ (is (get-in (links/handle-url "app://get.status.im/chat/public/status"
+ {:db db})
+ [:db :chats "status"]))))
+ (testing "a user profile link"
+ (testing "it loads the profile"
+ (let [actual (links/handle-url "app://get.status.im/user/profile-id"
+ {:db db})]
+ (is (= "profile-id" (get-in actual [:db :contacts/identity])))
+ (is (= :profile (get-in actual [:db :view-id]))))))
+ (testing "a not found url"
+ (testing "it does nothing"
+ (is (nil? (links/handle-url "app://get.status.im/not-existing"
+ {:db db}))))))))
+
+(deftest url-event-listener
+ (testing "the url is not nil"
+ (testing "it dispatches the url"
+ (let [actual (atom nil)]
+ (with-redefs [re-frame/dispatch #(reset! actual %)]
+ (links/url-event-listener #js {:url "some-url"})
+ (is (= [:handle-universal-link "some-url"] @actual))))))
+ (testing "the url is nil"
+ (testing "it does not dispatches the url"
+ (let [actual (atom nil)]
+ (with-redefs [re-frame/dispatch #(reset! actual %)]
+ (links/url-event-listener #js {})
+ (is (= nil @actual)))))))
+
+(deftest stored-url-event
+ (testing "the url is in the database"
+ (testing "it returns the event"
+ (= [:handle-universal-link "some-url"]
+ (links/stored-url-event {:db {:universal-links/url "some-url"}}))))
+ (testing "the url is not in the database"
+ (testing "it returns nil"
+ (= nil
+ (links/stored-url-event {:db {}})))))
+