Skip to content

Commit

Permalink
feat: support new universal/deep link format (#17480)
Browse files Browse the repository at this point in the history
  • Loading branch information
yqrashawn authored Oct 22, 2023
1 parent ca6fd3d commit 04a7f76
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 115 deletions.
6 changes: 3 additions & 3 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="join.status.im" />
<data android:scheme="https" android:host="join.status.im" />
<data android:scheme="http" android:host="status.app" />
<data android:scheme="https" android:host="status.app" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="status-im" />
<data android:scheme="status-app" />
<data android:scheme="ethereum" />
</intent-filter>
</activity>
Expand Down
2 changes: 1 addition & 1 deletion ios/StatusIm/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<string>im.status.ethereum.applink</string>
<key>CFBundleURLSchemes</key>
<array>
<string>status-im</string>
<string>status-app</string>
</array>
</dict>
<dict>
Expand Down
2 changes: 1 addition & 1 deletion ios/StatusIm/StatusIm.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:join.status.im</string>
<string>applinks:status.app</string>
</array>
<key>keychain-access-groups</key>
<array>
Expand Down
2 changes: 1 addition & 1 deletion ios/StatusImPR/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<string>im.status.ethereum.applink</string>
<key>CFBundleURLSchemes</key>
<array>
<string>status-im</string>
<string>status-app</string>
</array>
</dict>
<dict>
Expand Down
2 changes: 1 addition & 1 deletion ios/StatusImPR/StatusImPR.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:join.status.im</string>
<string>applinks:status.app</string>
</array>
<key>keychain-access-groups</key>
<array>
Expand Down
116 changes: 77 additions & 39 deletions src/status_im/router/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,29 @@
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.ens :as ens]
[status-im.ethereum.stateofus :as stateofus]
[status-im.utils.deprecated-types :as types]
[status-im.utils.wallet-connect :as wallet-connect]
[status-im2.constants :as constants]
[status-im2.contexts.chat.events :as chat.events]
[taoensso.timbre :as log]
[utils.address :as address]
[utils.ethereum.chain :as chain]
[utils.security.core :as security]
[utils.transforms :as transforms]
[utils.url :as url]
[utils.validators :as validators]))

(def ethereum-scheme "ethereum:")

(def uri-schemes ["status-im://" "status-im:"])
(def uri-schemes ["status-app://"])

(def web-prefixes ["https://" "http://" "https://www." "http://www."])

(def web2-domain "join.status.im")
(def web2-domain "status.app")

(def web-urls (map #(str % web2-domain "/") web-prefixes))

(def handled-schemes (set (into uri-schemes web-urls)))

(def browser-extractor
{[#"(.*)" :domain] {"" :browser
"/" :browser}})

(def group-chat-extractor
{[#"(.*)" :params] {"" :group-chat
"/" :group-chat}})

(def eip-extractor
{#{[:prefix "-" :address]
[:address]}
Expand All @@ -47,26 +39,62 @@

(def routes
[""
{handled-schemes {"b/" browser-extractor
"browser/" browser-extractor
["p/" :chat-id] :private-chat
["cr/" :community-id] :community-requests
["c/" :community-id] :community
["cc/" :chat-id] :community-chat
"g/" group-chat-extractor
["wallet/" :account] :wallet-account
["u/" :user-id] :user
["user/" :user-id] :user}
{handled-schemes {["c/" :community-data] :community
["cc/" :chat-data] :community-chat
["u/" :user-data] :user}
ethereum-scheme eip-extractor}])

(defn parse-query-params
[url]
(let [url (goog.Uri. url)]
(url/query->map (.getQuery url))))

(defn parse-fragment
[url]
(let [url (goog.Uri. url)
fragment (.getFragment url)]
(when-not (string/blank? fragment)
fragment)))

(defn match-uri
[uri]
(assoc (bidi/match-route routes uri) :uri uri :query-params (parse-query-params uri)))
;;
(let [;; bidi has trouble parse path with `=` in it extract `=` here and add back to parsed
;; base64url regex based on https://datatracker.ietf.org/doc/html/rfc4648#section-5 may
;; include invalid base64 (invalid length, length of any base64 encoded string must be a
;; multiple of 4)
;; equal-end-of-base64url can be `=`, `==`, `nil`
equal-end-of-base64url
(last (re-find #"^(https|status-app)://(status\.app/)?(c|cc|u)/([a-zA-Z0-9_-]+)(={0,2})#" uri))

uri-without-equal-in-path
(if equal-end-of-base64url (string/replace-first uri equal-end-of-base64url "") uri)

fragment (parse-fragment uri)
ens? (ens/is-valid-eth-name? fragment)

{:keys [handler route-params] :as parsed}
(assoc (bidi/match-route routes uri-without-equal-in-path)
:uri uri
:query-params (parse-query-params uri))]
(cond-> parsed
ens?
(assoc-in [:route-params :ens-name] fragment)

(and (or (= handler :community) (= handler :community-chat)) fragment)
(assoc-in [:route-params :community-id] fragment)

(and equal-end-of-base64url (= handler :community) (:community-data route-params))
(update-in [:route-params :community-data] #(str % equal-end-of-base64url))

(and equal-end-of-base64url (= handler :community-chat) (:chat-data route-params))
(update-in [:route-params :chat-data] #(str % equal-end-of-base64url))

(and equal-end-of-base64url (= handler :user) (:user-data route-params))
(update-in [:route-params :user-data] #(str % equal-end-of-base64url))

(and (= handler :user) fragment)
(assoc-in [:route-params :user-id] fragment))))

(defn match-contact-async
[chain {:keys [user-id ens-name]} callback]
Expand All @@ -84,7 +112,7 @@
user-id
constants/deserialization-key
(fn [response]
(let [{:keys [error]} (types/json->clj response)]
(let [{:keys [error]} (transforms/json->clj response)]
(when-not error
(match-contact-async
chain
Expand Down Expand Up @@ -210,41 +238,51 @@
:community))

(defn handle-uri
[chain chats uri cb]
(let [{:keys [handler route-params query-params]} (match-uri uri)]
[chain _chats uri cb]
(let [{:keys [handler route-params]} (match-uri uri)]
(log/info "[router] uri " uri " matched " handler " with " route-params)
(cond

(= handler :browser)
(cb (match-browser uri route-params))
;; ;; NOTE: removed in `match-uri`, might need this in the future
;; (= handler :browser)
;; (cb (match-browser uri route-params))

(= handler :ethereum)
(cb (match-eip681 uri))

(= handler :user)
(and (= handler :user) (:user-id route-params))
(match-contact-async chain route-params cb)

(= handler :private-chat)
(match-private-chat-async chain route-params cb)
;; ;; NOTE: removed in `match-uri`, might need this in the future
;; (= handler :private-chat)
;; (match-private-chat-async chain route-params cb)

(= handler :group-chat)
(cb (match-group-chat chats query-params))
;; ;; NOTE: removed in `match-uri`, might need this in the future
;; (= handler :group-chat)
;; (cb (match-group-chat chats query-params))

(validators/valid-public-key? uri)
(match-contact-async chain {:user-id uri} cb)

(= handler :community-requests)
(cb {:type handler :community-id (:community-id route-params)})
;; ;; NOTE: removed in `match-uri`, might need this in the future
;; (= handler :community-requests)
;; (cb {:type handler :community-id (:community-id route-params)})

(= handler :community)
(and (= handler :community) (:community-id route-params))
(cb {:type (community-route-type route-params)
:community-id (:community-id route-params)})

(= handler :community-chat)
(cb {:type handler :chat-id (:chat-id route-params)})
;; ;; TODO: jump to community overview for now, should jump to community channel
;; (and (= handler :community-chat) (:chat-id route-params))
;; (cb {:type handler :chat-id (:chat-id route-params)})

(and (= handler :community-chat) (:community-id route-params))
(cb {:type (community-route-type route-params)
:community-id (:community-id route-params)})

(= handler :wallet-account)
(cb (match-wallet-account route-params))
;; ;; NOTE: removed in `match-uri`, might need this in the future
;; (= handler :wallet-account)
;; (cb (match-wallet-account route-params))

(address/address? uri)
(cb (address->eip681 uri))
Expand Down
62 changes: 35 additions & 27 deletions src/status_im/router/core_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,41 @@
:query-params (when (= 3 (count expected)) (last expected))
:uri uri})

"status-im://u/statuse2e"
[:user {:user-id "statuse2e"}]

(str "status-im://user/" public-key)
[:user {:user-id public-key}]

"status-im://b/www.cryptokitties.co"
[:browser {:domain "www.cryptokitties.c"}]

(str "status-im://g/args?a=" public-key "&a1=" chat-name-url "&a2=" chat-id)
[:group-chat {:params "arg"} {"a" public-key "a1" chat-name "a2" chat-id}]

(str "https://join.status.im/g/args?a=" public-key "&a1=" chat-name-url "&a2=" chat-id)
[:group-chat {:params "arg"} {"a" public-key "a1" chat-name "a2" chat-id}]

"https://join.status.im/u/statuse2e"
[:user {:user-id "statuse2e"}]

(str "https://join.status.im/user/" public-key)
[:user {:user-id public-key}]

;; Last char removed by: https://github.com/juxt/bidi/issues/104
"https://join.status.im/b/www.cryptokitties.co"
[:browser {:domain "www.cryptokitties.c"}]

"https://join.status.im/b/https://www.google.com/"
[:browser {:domain "https://www.google.co"}]
"https://status.app/u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
[:user
{:user-data
"G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y"
:user-id "zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"}]

"status-app://u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
[:user
{:user-data
"G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y"
:user-id "zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"}]

"https://status.app/cc/G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
[:community-chat
{:chat-data
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]

"status-app://cc/G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
[:community-chat
{:chat-data
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]

"https://status.app/c/iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
[:community
{:community-data
"iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM="
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]

"status-app://c/iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
[:community
{:community-data
"iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM="
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]

"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
[:ethereum {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}]
Expand Down
3 changes: 2 additions & 1 deletion src/status_im/ui/screens/browser/site_blocked/views.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
(i18n/label :t/browsing-site-blocked-title)]
[react/nested-text {:style styles/description-text}
(i18n/label :t/browsing-site-blocked-description1)
[{:on-press #(.openURL ^js react/linking "status-im://chat/public/status")
;; NOTE: this link is broken
[{:on-press #(.openURL ^js react/linking "status-app://chat/public/status")
:style styles/chat-link-text}
"#status"]
(i18n/label :t/browsing-site-blocked-description2)]
Expand Down
16 changes: 9 additions & 7 deletions src/status_im/utils/universal_links/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

;; domains should be without the trailing slash
(def domains
{:external "https://join.status.im"
:internal "status-im:/"})
{:external "https://status.app"
:internal "status-app:/"})

(def links
{:private-chat "%s/p/%s"
Expand Down Expand Up @@ -81,17 +81,20 @@
(log/info "universal-links: handling community" community-id)
(navigation/navigate-to cofx :community {:community-id community-id}))


(rf/defn handle-navigation-to-desktop-community-from-mobile
{:events [:handle-navigation-to-desktop-community-from-mobile]}
[cofx deserialized-key]
(navigation/navigate-to cofx :community-overview deserialized-key))
(rf/merge
cofx
{:dispatch [:navigate-to :community-overview deserialized-key]}
(navigation/pop-to-root :shell-stack)))

(rf/defn handle-desktop-community
[cofx {:keys [community-id]}]
(native-module/deserialize-and-compress-key
community-id
(fn [deserialized-key]
(rf/dispatch [:chat.ui/resolve-community-info (str deserialized-key)])
(rf/dispatch [:handle-navigation-to-desktop-community-from-mobile (str deserialized-key)]))))

(rf/defn handle-community-chat
Expand Down Expand Up @@ -212,9 +215,8 @@
(.then dispatch-url))
200)
(.addEventListener ^js react/linking "url" url-event-listener)
;;StartSearchForLocalPairingPeers() shouldn't be called ATM from the UI
;;It can be called after the error "route ip+net: netlinkrib: permission denied" is fixed on status-go
;;side
;;StartSearchForLocalPairingPeers() shouldn't be called ATM from the UI It can be called after the
;;error "route ip+net: netlinkrib: permission denied" is fixed on status-go side
#_(native-module/start-searching-for-local-pairing-peers
#(log/info "[local-pairing] errors from local-pairing-preflight-outbound-check ->" %)))

Expand Down
4 changes: 2 additions & 2 deletions src/status_im/utils/universal_links/core_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
(is (nil? (get-in (links/handle-url {:db db} "some-url")
[:db :universal-links/url]))))
(testing "Handle a custom string"
(is (= (get-in (links/handle-url {:db db} "https://join.status.im/u/statuse2e")
(is (= (get-in (links/handle-url {:db db} "https://status.app/u/statuse2e")
[::router/handle-uri :uri])
"https://join.status.im/u/statuse2e")))))))
"https://status.app/u/statuse2e")))))))

(deftest url-event-listener
(testing "the url is not nil"
Expand Down
4 changes: 2 additions & 2 deletions src/status_im/utils/universal_links/utils.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

;; domains should be without the trailing slash
(def domains
{:external "https://join.status.im"
:internal "status-im:/"})
{:external "https://status.app"
:internal "status-app:/"})

(def links
{:private-chat "%s/p/%s"
Expand Down
Loading

0 comments on commit 04a7f76

Please sign in to comment.