From 227c34170eed01564f4011acda90b63c498feea6 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 3 Dec 2025 15:06:01 +1300 Subject: [PATCH 1/5] Remove `CookieJarSharedImplementation` --- .../Networking/RequestAuthenticator.swift | 2 +- .../Utility/WebViewController/CookieJar.swift | 24 ++++--------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift index 9add47ef7e1d..4244b825b5a9 100644 --- a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift +++ b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift @@ -88,7 +88,7 @@ class RequestAuthenticator: NSObject { /// - completion: this will be called with either the request for /// authentication, or a request for the original URL. /// - @objc func request(url: URL, cookieJar: CookieJar, completion: @escaping (URLRequest) -> Void) { + func request(url: URL, cookieJar: CookieJar, completion: @escaping (URLRequest) -> Void) { switch self.credentials { case .dotCom(let username, let authToken, let authenticationType): requestForWPCom( diff --git a/WordPress/Classes/Utility/WebViewController/CookieJar.swift b/WordPress/Classes/Utility/WebViewController/CookieJar.swift index ec9ea86ce292..fde2727c3f74 100644 --- a/WordPress/Classes/Utility/WebViewController/CookieJar.swift +++ b/WordPress/Classes/Utility/WebViewController/CookieJar.swift @@ -4,7 +4,7 @@ import WebKit /// Provides a common interface to look for a logged-in WordPress cookie in different /// cookie storage systems. /// -@objc protocol CookieJar { +protocol CookieJar: AnyObject { func getCookies(url: URL, completion: @escaping ([HTTPCookie]) -> Void) func getCookies(completion: @escaping ([HTTPCookie]) -> Void) func hasWordPressSelfHostedAuthCookie(for url: URL, username: String, completion: @escaping (Bool) -> Void) @@ -14,21 +14,7 @@ import WebKit func setCookies(_ cookies: [HTTPCookie], completion: @escaping () -> Void) } -// As long as CookieJar is @objc, we can't have shared methods in protocol -// extensions, as it needs to be accessible to Obj-C. -// Whenever we migrate enough code so this doesn't need to be called from Swift, -// a regular CookieJar protocol with shared implementation on an extension would suffice. -// -// Also, although you're not supposed to use this outside this file, it can't be private -// since we're subclassing HTTPCookieStorage (which conforms to this) in MockCookieJar in -// the test target, and the swift compiler will crash when doing that ¯\_(ツ)_/¯ -// -// https://bugs.swift.org/browse/SR-2370 -// -protocol CookieJarSharedImplementation: CookieJar { -} - -extension CookieJarSharedImplementation { +extension CookieJar { func _hasWordPressComAuthCookie(username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { let url = URL(string: "https://wordpress.com/")! @@ -53,7 +39,7 @@ extension CookieJarSharedImplementation { } } -extension HTTPCookieStorage: CookieJarSharedImplementation { +extension HTTPCookieStorage: CookieJar { func getCookies(url: URL, completion: @escaping ([HTTPCookie]) -> Void) { completion(cookies(for: url) ?? []) } @@ -88,7 +74,7 @@ extension HTTPCookieStorage: CookieJarSharedImplementation { } } -extension WKHTTPCookieStore: CookieJarSharedImplementation { +extension WKHTTPCookieStore: CookieJar { func getCookies(url: URL, completion: @escaping ([HTTPCookie]) -> Void) { // This fixes an issue with `getAllCookies` not calling its completion block (related: https://stackoverflow.com/q/55565188) @@ -170,7 +156,7 @@ extension WKHTTPCookieStore: CookieJarSharedImplementation { #if DEBUG func __removeAllWordPressComCookies() { - var jars = [CookieJarSharedImplementation]() + var jars = [CookieJar]() jars.append(HTTPCookieStorage.shared) jars.append(WKWebsiteDataStore.default().httpCookieStore) From 9654af460f44c76925108f5f234b6d46b327392e Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 3 Dec 2025 15:11:16 +1300 Subject: [PATCH 2/5] Move default implementations to `extension CookieJar` --- .../Utility/WebViewController/CookieJar.swift | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/WordPress/Classes/Utility/WebViewController/CookieJar.swift b/WordPress/Classes/Utility/WebViewController/CookieJar.swift index fde2727c3f74..4dca746dc93c 100644 --- a/WordPress/Classes/Utility/WebViewController/CookieJar.swift +++ b/WordPress/Classes/Utility/WebViewController/CookieJar.swift @@ -15,13 +15,17 @@ protocol CookieJar: AnyObject { } extension CookieJar { - func _hasWordPressComAuthCookie(username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { + func hasWordPressComAuthCookie(username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { let url = URL(string: "https://wordpress.com/")! - return _hasWordPressAuthCookie(for: url, username: username, atomicSite: atomicSite, completion: completion) + return hasWordPressAuthCookie(for: url, username: username, atomicSite: atomicSite, completion: completion) + } + + func hasWordPressSelfHostedAuthCookie(for url: URL, username: String, completion: @escaping (Bool) -> Void) { + hasWordPressAuthCookie(for: url, username: username, atomicSite: false, completion: completion) } - func _hasWordPressAuthCookie(for url: URL, username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { + private func hasWordPressAuthCookie(for url: URL, username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { getCookies(url: url) { (cookies) in let cookie = cookies .contains(where: { cookie in @@ -32,7 +36,7 @@ extension CookieJar { } } - func _removeWordPressComCookies(completion: @escaping () -> Void) { + func removeWordPressComCookies(completion: @escaping () -> Void) { getCookies { [unowned self] (cookies) in self.removeCookies(cookies.filter({ $0.domain.hasSuffix(".wordpress.com") }), completion: completion) } @@ -48,23 +52,11 @@ extension HTTPCookieStorage: CookieJar { completion(cookies ?? []) } - func hasWordPressComAuthCookie(username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { - _hasWordPressComAuthCookie(username: username, atomicSite: atomicSite, completion: completion) - } - - func hasWordPressSelfHostedAuthCookie(for url: URL, username: String, completion: @escaping (Bool) -> Void) { - _hasWordPressAuthCookie(for: url, username: username, atomicSite: false, completion: completion) - } - func removeCookies(_ cookies: [HTTPCookie], completion: @escaping () -> Void) { cookies.forEach(deleteCookie(_:)) completion() } - func removeWordPressComCookies(completion: @escaping () -> Void) { - _removeWordPressComCookies(completion: completion) - } - func setCookies(_ cookies: [HTTPCookie], completion: @escaping () -> Void) { for cookie in cookies { setCookie(cookie) @@ -113,14 +105,6 @@ extension WKHTTPCookieStore: CookieJar { getAllCookies(completion) } - func hasWordPressComAuthCookie(username: String, atomicSite: Bool, completion: @escaping (Bool) -> Void) { - _hasWordPressComAuthCookie(username: username, atomicSite: atomicSite, completion: completion) - } - - func hasWordPressSelfHostedAuthCookie(for url: URL, username: String, completion: @escaping (Bool) -> Void) { - _hasWordPressAuthCookie(for: url, username: username, atomicSite: false, completion: completion) - } - func removeCookies(_ cookies: [HTTPCookie], completion: @escaping () -> Void) { let group = DispatchGroup() cookies @@ -137,10 +121,6 @@ extension WKHTTPCookieStore: CookieJar { completion() } - func removeWordPressComCookies(completion: @escaping () -> Void) { - _removeWordPressComCookies(completion: completion) - } - func setCookies(_ cookies: [HTTPCookie], completion: @escaping () -> Void) { guard let cookie = cookies.last else { return completion() From e4deb0890b453d75e2a4707f87ad7d44ad2dfa64 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 3 Dec 2025 21:13:00 +1300 Subject: [PATCH 3/5] Allow showing more login webpages in the in-app browser --- .../Networking/RequestAuthenticator.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift index 4244b825b5a9..8e6303dd7554 100644 --- a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift +++ b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift @@ -312,15 +312,31 @@ class RequestAuthenticator: NSObject { } private extension RequestAuthenticator { - static let wordPressComLoginUrl = URL(string: "https://wordpress.com/wp-login.php")! + static let wordPressComLoginUrls: Set = [ + URL(string: "https://wordpress.com/wp-login.php")!, + URL(string: "https://wordpress.com/log-in")! + ] } extension RequestAuthenticator { func isLogin(url: URL) -> Bool { + // For some reason, the in-app browser may open Safari when tapping certain links. + // The "login URLs" are displayed within the in-app browser, which includes atomic site login (wp-login.php) + // and WordPress.com sign-in web pages. + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) components?.queryItems = nil + guard let normalized = components?.url else { return false } + + if RequestAuthenticator.wordPressComLoginUrls.contains(normalized) { + return true + } + + if case let .dotCom(_, _, authenticationType: .atomic(loginURL)) = credentials, normalized.absoluteString == loginURL { + return true + } - return components?.url == RequestAuthenticator.wordPressComLoginUrl + return false } } From 3b5cf4729b644b4c3253a56f7df92eeda847c195 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 3 Dec 2025 21:13:20 +1300 Subject: [PATCH 4/5] Add another debug menu to open an in-app browser --- .../DebugMenuViewController.swift | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift b/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift index fa9716cdcf50..3f70efde07b3 100644 --- a/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/App Settings/DebugMenuViewController.swift @@ -10,6 +10,7 @@ struct DebugMenuView: View { @StateObject private var viewModel = DebugMenuViewModel() @State private var isShowingWebViewURLDialog = false @State private var webViewURL = "" + @State private var isAuthenticatedMode = true fileprivate var navigation: NavigationContext @@ -35,7 +36,11 @@ struct DebugMenuView: View { Button(SharedStrings.Button.cancel, role: .cancel) { } Button(SharedStrings.Button.view) { - presentAuthenticatedWebView() + if isAuthenticatedMode { + presentAuthenticatedWebView() + } else { + presentUnauthenticatedWebView() + } } } } @@ -86,6 +91,12 @@ struct DebugMenuView: View { @ViewBuilder private var webView: some View { Button(Strings.webViewRow) { webViewURL = Blog.lastUsed(in: ContextManager.shared.mainContext)?.url ?? "" + isAuthenticatedMode = true + isShowingWebViewURLDialog = true + } + Button(Strings.unauthenticatedWebViewRow) { + webViewURL = "" + isAuthenticatedMode = false isShowingWebViewURLDialog = true } } @@ -136,6 +147,16 @@ struct DebugMenuView: View { navigation.push(webViewController) } + private func presentUnauthenticatedWebView() { + guard let url = URL(string: webViewURL) else { + preconditionFailure("Invalid URL") + return + } + + let webViewController = WebViewControllerFactory.controller(url: url, source: "debug_menu") + navigation.push(webViewController) + } + private var readerSettings: some View { let viewController = SettingsTextViewController(text: ReaderCSS().customAddress, placeholder: Strings.readerURLPlaceholder, hint: Strings.readerURLHint) viewController.title = Strings.readerCssTitle @@ -257,6 +278,7 @@ private enum Strings { static let weeklyRoundup = NSLocalizedString("debugMenu.weeklyRoundup", value: "Weekly Roundup", comment: "Weekly Roundup debug menu item") static let booleanUserDefaults = NSLocalizedString("debugMenu.booleanUserDefaults", value: "Boolean User Defaults", comment: "Boolean User Defaults debug menu item") static let webViewRow = NSLocalizedString("debugMenu.webView.row", value: "Browse as loggedin account", comment: "Debug menu item to present an authenticated web view for the currently displayed site") + static let unauthenticatedWebViewRow = NSLocalizedString("debugMenu.webView.unauthenticatedRow", value: "Open a web browser", comment: "Debug menu item to present an unauthenticated web view") static let webViewDialogTitle = NSLocalizedString("debugMenu.webView.dialogTitle", value: "Enter URL", comment: "Title for web view URL input dialog") static let showAllTips = NSLocalizedString("debugMenu.showAllTips", value: "Show All Tips", comment: "Debug Menu action for TipKit") From 54803e1037c9e796e3fad0ed6753cecd8bc6c85f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 3 Dec 2025 21:22:43 +1300 Subject: [PATCH 5/5] Remove an unneeded label --- WordPress/Classes/Utility/Networking/RequestAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift index 8e6303dd7554..2bf93dc32667 100644 --- a/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift +++ b/WordPress/Classes/Utility/Networking/RequestAuthenticator.swift @@ -332,7 +332,7 @@ extension RequestAuthenticator { return true } - if case let .dotCom(_, _, authenticationType: .atomic(loginURL)) = credentials, normalized.absoluteString == loginURL { + if case let .dotCom(_, _, .atomic(loginURL)) = credentials, normalized.absoluteString == loginURL { return true }