Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix #6225: Ethereum Name Service (ENS) Navigation #7153

Merged
merged 12 commits into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import BraveTalk
#endif
import Onboarding
import os
import BraveWallet

extension AppDelegate {
// A model that is passed used in every scene
Expand Down Expand Up @@ -418,10 +419,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
(SessionRestoreHandler.path, SessionRestoreHandler()),
(ErrorPageHandler.path, ErrorPageHandler()),
(ReaderModeHandler.path, ReaderModeHandler(profile: profile)),
(SNSDomainHandler.path, SNSDomainHandler()),
(IPFSSchemeHandler.path, IPFSSchemeHandler())
(IPFSSchemeHandler.path, IPFSSchemeHandler()),
(Web3DomainHandler.path, Web3DomainHandler())
]

responders.forEach { (path, responder) in
InternalSchemeHandler.responders[path] = responder
}
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ var braveTarget: PackageDescription.Target = .target(
.copy("Assets/Interstitial Pages/Pages/CertificateError.html"),
.copy("Assets/Interstitial Pages/Pages/GenericError.html"),
.copy("Assets/Interstitial Pages/Pages/NetworkError.html"),
.copy("Assets/Interstitial Pages/Pages/SNSDomain.html"),
.copy("Assets/Interstitial Pages/Pages/Web3Domain.html"),
.copy("Assets/Interstitial Pages/Pages/IPFSPreference.html"),
.copy("Assets/Interstitial Pages/Images/Carret.png"),
.copy("Assets/Interstitial Pages/Images/Clock.svg"),
Expand All @@ -384,7 +384,7 @@ var braveTarget: PackageDescription.Target = .target(
.copy("Assets/Interstitial Pages/Styles/CertificateError.css"),
.copy("Assets/Interstitial Pages/Styles/InterstitialStyles.css"),
.copy("Assets/Interstitial Pages/Styles/NetworkError.css"),
.copy("Assets/Interstitial Pages/Styles/SNSDomain.css"),
.copy("Assets/Interstitial Pages/Styles/Web3Domain.css"),
.copy("Assets/Interstitial Pages/Styles/IPFSPreference.css"),
.copy("Assets/SearchPlugins"),
.copy("Frontend/Reader/Reader.css"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<meta name="referrer" content="no-referrer">
<title>%page_title%</title>
<link rel="stylesheet" href="internal://local/interstitial-style/InterstitialStyles.css">
<link rel="stylesheet" href="internal://local/interstitial-style/SNSDomain.css">
<link rel="stylesheet" href="internal://local/interstitial-style/Web3Domain.css">
</head>

<body dir="&locale.dir;" class="background content">
Expand All @@ -28,23 +28,25 @@
<br />

<div id="navigationButtons" class="navigationButtons">
<button id="disableSNSButton" class="disableButton disableButtonTitle">%button_disable%</button>
<button id="proceedSNSButton" class="proceedButton proceedButtonTitle">%button_procced%</button>
<button id="disableWeb3Button" class="disableButton disableButtonTitle">%button_disable%</button>
<button id="proceedWeb3Button" class="proceedButton proceedButtonTitle">%button_procced%</button>
</div>

<script type="text/javascript">
var disableButton = document.getElementById("disableSNSButton")
var disableButton = document.getElementById("disableWeb3Button")
disableButton.addEventListener('click', function(e) {
e.preventDefault();
webkit.messageHandlers["%message_handler%"].postMessage({
"type": "SNSDisable",
"button_type": "disable",
"service_id": "%service_id%"
});
});
var proceedButton = document.getElementById("proceedSNSButton")
var proceedButton = document.getElementById("proceedWeb3Button")
proceedButton.addEventListener('click', function(e) {
e.preventDefault();
webkit.messageHandlers["%message_handler%"].postMessage({
"type": "SNSProceed",
"button_type": "proceed",
"service_id": "%service_id%"
});
});

Expand Down
11 changes: 8 additions & 3 deletions Sources/Brave/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ public class BrowserViewController: UIViewController {
weak var walletStore: WalletStore?

var lastEnteredURLVisitType: VisitType = .unknown

var processAddressBarTask: Task<(), Never>?
var topToolbarDidPressReloadTask: Task<(), Never>?

public init(
profile: Profile,
Expand Down Expand Up @@ -1607,10 +1610,12 @@ public class BrowserViewController: UIViewController {
tab.webView?.load(PrivilegedRequest(url: internalUrl) as URLRequest)
}

func showSNSDomainInterstitialPage(originalURL: URL, visitType: VisitType) {
func showWeb3ServiceInterstitialPage(service: Web3Service, originalURL: URL, visitType: VisitType = .unknown) {
topToolbar.leaveOverlayMode()

guard let tab = tabManager.selectedTab, let encodedURL = originalURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .alphanumerics), let internalUrl = URL(string: "\(InternalURL.baseUrl)/\(SNSDomainHandler.path)?url=\(encodedURL)") else {

guard let tab = tabManager.selectedTab,
let encodedURL = originalURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .alphanumerics),
let internalUrl = URL(string: "\(InternalURL.baseUrl)/\(Web3DomainHandler.path)?\(Web3NameServiceScriptHandler.ParamKey.serviceId.rawValue)=\(service.rawValue)&url=\(encodedURL)") else {
return
}
let scriptHandler = tab.getContentScript(name: Web3NameServiceScriptHandler.scriptName) as? Web3NameServiceScriptHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,29 +107,28 @@ extension BrowserViewController: TopToolbarDelegate {
}

func topToolbarDidPressReload(_ topToolbar: TopToolbarView) {
let isPrivateMode = PrivateBrowsingManager.shared.isPrivateBrowsing

if let url = topToolbar.currentURL {
if url.isIPFSScheme {
if !handleIPFSSchemeURL(url, visitType: .unknown) {
tabManager.selectedTab?.reload()
}
} else if !isPrivateMode, url.domainURL.schemelessAbsoluteDisplayString.endsWithSupportedSNSExtension, let rpcService = BraveWallet.JsonRpcServiceFactory.get(privateMode: isPrivateMode) {
Task { @MainActor in
let currentStatus = await rpcService.snsResolveMethod()
switch currentStatus {
case .ask:
// show name service interstitial page
showSNSDomainInterstitialPage(originalURL: url, visitType: .unknown)
case .enabled:
if let resolvedURL = await resolveSNSHost(url.schemelessAbsoluteDisplayString, rpcService: rpcService) {
} else if let decentralizedDNSHelper = decentralizedDNSHelperFor(url: topToolbar.currentURL) {
topToolbarDidPressReloadTask?.cancel()
topToolbarDidPressReloadTask = Task { @MainActor in
topToolbar.locationView.loading = true
let result = await decentralizedDNSHelper.lookup(domain: url.schemelessAbsoluteDisplayString)
topToolbar.locationView.loading = tabManager.selectedTab?.loading ?? false
soner-yuksel marked this conversation as resolved.
Show resolved Hide resolved
guard !Task.isCancelled else { return } // user pressed stop, or typed new url
switch result {
case let .loadInterstitial(service):
showWeb3ServiceInterstitialPage(service: service, originalURL: url)
case let .load(resolvedURL):
if resolvedURL.isIPFSScheme {
handleIPFSSchemeURL(resolvedURL, visitType: .unknown)
} else {
tabManager.selectedTab?.loadRequest(URLRequest(url: resolvedURL))
return
}
tabManager.selectedTab?.loadRequest(URLRequest(url: url))
case .disabled:
tabManager.selectedTab?.reload()
@unknown default:
case .none:
tabManager.selectedTab?.reload()
}
}
Expand All @@ -143,6 +142,9 @@ extension BrowserViewController: TopToolbarDelegate {

func topToolbarDidPressStop(_ topToolbar: TopToolbarView) {
tabManager.selectedTab?.stop()
processAddressBarTask?.cancel()
topToolbarDidPressReloadTask?.cancel()
topToolbar.locationView.loading = tabManager.selectedTab?.loading ?? false
soner-yuksel marked this conversation as resolved.
Show resolved Hide resolved
}

func topToolbarDidLongPressReloadButton(_ topToolbar: TopToolbarView, from button: UIButton) {
Expand Down Expand Up @@ -246,7 +248,8 @@ extension BrowserViewController: TopToolbarDelegate {
}

func processAddressBar(text: String, visitType: VisitType, isBraveSearchPromotion: Bool = false) {
Task { @MainActor in
processAddressBarTask?.cancel()
processAddressBarTask = Task { @MainActor in
if !isBraveSearchPromotion, await submitValidURL(text, visitType: visitType) {
return
} else {
Expand All @@ -260,12 +263,6 @@ extension BrowserViewController: TopToolbarDelegate {
}
}

@MainActor func resolveSNSHost(_ host: String, rpcService: BraveWalletJsonRpcService) async -> URL? {
let (url, status, _) = await rpcService.snsResolveHost(host)
guard let url = url, status == .success else { return nil }
return url
}

@discardableResult
func handleIPFSSchemeURL(_ url: URL, visitType: VisitType) -> Bool {
guard !PrivateBrowsingManager.shared.isPrivateBrowsing else {
Expand Down Expand Up @@ -307,24 +304,26 @@ extension BrowserViewController: TopToolbarDelegate {
// Do not allow users to enter URLs with the following schemes.
// Instead, submit them to the search engine like Chrome-iOS does.
if !["file"].contains(fixupURL.scheme) {
// check text is SNS domain
let isPrivateMode = PrivateBrowsingManager.shared.isPrivateBrowsing
if !isPrivateMode, fixupURL.domainURL.schemelessAbsoluteDisplayString.endsWithSupportedSNSExtension, let rpcService = BraveWallet.JsonRpcServiceFactory.get(privateMode: isPrivateMode) {
let currentStatus = await rpcService.snsResolveMethod()
switch currentStatus {
case .ask:
// show name service interstitial page
showSNSDomainInterstitialPage(originalURL: fixupURL, visitType: visitType)
// check text is decentralized DNS supported domain
if let decentralizedDNSHelper = self.decentralizedDNSHelperFor(url: fixupURL) {
topToolbar.leaveOverlayMode()
updateToolbarCurrentURL(fixupURL)
topToolbar.locationView.loading = true
let result = await decentralizedDNSHelper.lookup(domain: fixupURL.schemelessAbsoluteDisplayString)
topToolbar.locationView.loading = tabManager.selectedTab?.loading ?? false
guard !Task.isCancelled else { return true } // user pressed stop, or typed new url
switch result {
case let .loadInterstitial(service):
showWeb3ServiceInterstitialPage(service: service, originalURL: fixupURL, visitType: visitType)
return true
case .enabled:
if let resolvedURL = await resolveSNSHost(text, rpcService: rpcService) {
// resolved url
case let .load(resolvedURL):
if resolvedURL.isIPFSScheme {
return handleIPFSSchemeURL(resolvedURL, visitType: visitType)
} else {
finishEditingAndSubmit(resolvedURL, visitType: visitType)
return true
}
case .disabled:
break
@unknown default:
case .none:
break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ extension BrowserViewController: WKNavigationDelegate {

@MainActor
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences) async -> (WKNavigationActionPolicy, WKWebpagePreferences) {
guard let url = navigationAction.request.url else {
guard var url = navigationAction.request.url else {
return (.cancel, preferences)
}

Expand Down Expand Up @@ -198,6 +198,31 @@ extension BrowserViewController: WKNavigationDelegate {
}
return (.cancel, preferences)
}

// handles Decentralized DNS
if let decentralizedDNSHelper = self.decentralizedDNSHelperFor(url: url),
navigationAction.targetFrame?.isMainFrame == true {
topToolbar.locationView.loading = true
let result = await decentralizedDNSHelper.lookup(domain: url.schemelessAbsoluteDisplayString)
topToolbar.locationView.loading = tabManager.selectedTab?.loading ?? false
guard !Task.isCancelled else { // user pressed stop, or typed new url
return (.cancel, preferences)
}
switch result {
case let .loadInterstitial(service):
showWeb3ServiceInterstitialPage(service: service, originalURL: url, visitType: .link)
return (.cancel, preferences)
case let .load(resolvedURL):
if resolvedURL.isIPFSScheme {
handleIPFSSchemeURL(resolvedURL, visitType: .link)
return (.cancel, preferences)
} else { // non-ipfs, treat as normal url / link tapped
url = resolvedURL
}
case .none:
break
}
}

let isPrivateBrowsing = PrivateBrowsingManager.shared.isPrivateBrowsing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,51 @@ import BraveShared
import BraveCore

extension BrowserViewController: Web3NameServiceScriptHandlerDelegate {
func web3NameServiceDecisionHandler(_ proceed: Bool, originalURL: URL, visitType: VisitType) {
/// Returns a `DecentralizedDNSHelper` for the given mode if supported and not in private mode.
func decentralizedDNSHelperFor(url: URL?) -> DecentralizedDNSHelper? {
let isPrivateMode = PrivateBrowsingManager.shared.isPrivateBrowsing
guard let rpcService = BraveWallet.JsonRpcServiceFactory.get(privateMode: isPrivateMode) else {
guard !isPrivateMode,
let url,
DecentralizedDNSHelper.isSupported(domain: url.domainURL.schemelessAbsoluteDisplayString),
let rpcService = BraveWallet.JsonRpcServiceFactory.get(privateMode: isPrivateMode) else { return nil }
return DecentralizedDNSHelper(
rpcService: rpcService,
ipfsApi: braveCore.ipfsAPI,
isPrivateMode: isPrivateMode
)
}

func web3NameServiceDecisionHandler(_ proceed: Bool, web3Service: Web3Service, originalURL: URL, visitType: VisitType) {
let isPrivateMode = PrivateBrowsingManager.shared.isPrivateBrowsing
guard let rpcService = BraveWallet.JsonRpcServiceFactory.get(privateMode: isPrivateMode),
let decentralizedDNSHelper = self.decentralizedDNSHelperFor(url: originalURL) else {
finishEditingAndSubmit(originalURL, visitType: visitType)
return
}
if proceed {
Task { @MainActor in
rpcService.setSnsResolveMethod(.enabled)
if let host = originalURL.host, let resolvedUrl = await resolveSNSHost(host, rpcService: rpcService) {
// resolved url
finishEditingAndSubmit(resolvedUrl, visitType: visitType)
Task { @MainActor in
switch web3Service {
case .solana:
soner-yuksel marked this conversation as resolved.
Show resolved Hide resolved
rpcService.setSnsResolveMethod(proceed ? .enabled : .disabled)
case .ethereum:
rpcService.setEnsResolveMethod(proceed ? .enabled : .disabled)
case .ethereumOffchain:
rpcService.setEnsOffchainLookupResolveMethod(proceed ? .enabled : .disabled)
}
let result = await decentralizedDNSHelper.lookup(domain: originalURL.host ?? originalURL.absoluteString)
switch result {
case let .load(resolvedURL):
if resolvedURL.isIPFSScheme {
handleIPFSSchemeURL(resolvedURL, visitType: visitType)
} else {
finishEditingAndSubmit(resolvedURL, visitType: visitType)
}
case let .loadInterstitial(service):
// ENS interstitial -> ENS Offchain interstitial possible
showWeb3ServiceInterstitialPage(service: service, originalURL: originalURL, visitType: visitType)
case .none:
// failed to resolve domain or disabled
finishEditingAndSubmit(originalURL, visitType: visitType)
}
} else {
rpcService.setSnsResolveMethod(.disabled)
finishEditingAndSubmit(originalURL, visitType: visitType)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class IPFSSchemeHandler: InternalSchemeResponse {
"interstitial_ipfs_title": Strings.Wallet.web3IPFSInterstitialIPFSTitle,
"interstitial_ipfs_privacy": String.localizedStringWithFormat(Strings.Wallet.web3IPFSInterstitialIPFSPrivacy, WalletConstants.ipfsLearnMoreLink.absoluteString, Strings.learnMore.lowercased().capitalizeFirstLetter),
"interstitial_ipfs_public_gateway": Strings.Wallet.web3IPFSInterstitialIPFSPublicGateway,
"button_disable": Strings.Wallet.snsDomainInterstitialPageButtonDisable,
"button_disable": Strings.Wallet.web3DomainInterstitialPageButtonDisable,
"button_procced": Strings.Wallet.web3IPFSInterstitialProceedButton,
"message_handler": Web3IPFSScriptHandler.messageHandlerName,
]
Expand Down
53 changes: 0 additions & 53 deletions Sources/Brave/Frontend/Browser/Handlers/SNSDomainHandler.swift

This file was deleted.

Loading