diff --git a/AGENTS.md b/AGENTS.md index 12b9fa580e1e..3e57c60d86a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,11 +64,15 @@ xcodebuild \ Some test targets (e.g. `WordPressDataTests`) have their own scheme and are not part of the main `WordPress` scheme's test plan. When the `WordPress` scheme build fails due to an unrelated target, try using the target's dedicated scheme instead. +### Simulator Sign-In + +To automatically sign in to the app on an iOS simulator, see @docs/simulator-sign-in.md. + ## Coding Standards - Follow Swift API Design Guidelines - Use strict access control modifiers where possible - Use four spaces (not tabs) -- Lines should not have trailing whitespace +- Lines should not have trailing whitespace - Follow the standard formatting practices enforced by SwiftLint - Don't create `body` for `View` that are too long - Use semantics text sizes like `.headline` diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 7a6abd6f378a..f61f221adb1c 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "780f2d656724964f6c08f01d7886f8bc0dba921bbc0fff8a8791840d1edb0f58", + "originHash" : "0bdc44e27c6890f11c36c3cb6a16c034a93b6b59170c3e6d3514cadd8f094da6", "pins" : [ { "identity" : "alamofire", @@ -352,10 +352,10 @@ { "identity" : "wordpress-rs", "kind" : "remoteSourceControl", - "location" : "https://github.com/Automattic/wordpress-rs", + "location" : "https://github.com/automattic/wordpress-rs", "state" : { - "branch" : "alpha-20260226", - "revision" : "9d28dfbf4d1041ea06d9d81241cabaa9a37db600" + "branch" : "pr-build/1226", + "revision" : "c6b04573e8e33538bea6551898f9004b1e0f429f" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index b63c47a9e255..985bc90a1f1a 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -56,7 +56,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "v0.14.0-alpha.0"), // We can't use wordpress-rs branches nor commits here. Only tags work. - .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20260226"), + .package(url: "https://github.com/automattic/wordpress-rs", branch: "pr-build/1226"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de" diff --git a/WordPress/Classes/ApplicationToken/ApplicationTokenListView.swift b/WordPress/Classes/ApplicationToken/ApplicationTokenListView.swift index ee54a78a7b9f..a33aae7d6c8b 100644 --- a/WordPress/Classes/ApplicationToken/ApplicationTokenListView.swift +++ b/WordPress/Classes/ApplicationToken/ApplicationTokenListView.swift @@ -141,6 +141,6 @@ class StaticTokenProvider: ApplicationTokenListDataProvider { #Preview { NavigationView { - ApplicationTokenListView(error: WpApiError.WpError(errorCode: .ApplicationPasswordsDisabledForUser, errorMessage: "Not available for the current user", statusCode: 400, response: "{}")) + ApplicationTokenListView(error: WpApiError.WpError(errorCode: .ApplicationPasswordsDisabledForUser, errorMessage: "Not available for the current user", statusCode: 400, response: "{}", requestUrl: "http://example.com", requestMethod: .get)) } } diff --git a/WordPress/Classes/Login/LoginWithUrlView.swift b/WordPress/Classes/Login/LoginWithUrlView.swift index c6dabba3bc44..094eaf59f4cb 100644 --- a/WordPress/Classes/Login/LoginWithUrlView.swift +++ b/WordPress/Classes/Login/LoginWithUrlView.swift @@ -1,7 +1,7 @@ import SwiftUI import AuthenticationServices import WordPressAPI -import WordPressAuthenticator +import WordPressAPIInternal import WordPressData import DesignSystem import WordPressShared @@ -112,47 +112,36 @@ struct LoginWithUrlView: View { isLoading = true defer { isLoading = false } + let details: AutoDiscoveryAttemptSuccess do { - let blog = try await SelfHostedSiteAuthenticator() - .signIn(site: urlField, from: presenter, context: .default) - - dismiss() - self.loginCompleted(blog) + let loginClient = WordPressLoginClient(urlSession: URLSession(configuration: .ephemeral)) + details = try await loginClient.details(ofSite: urlField) + } catch is CancellationError { + return } catch { - if await shouldRedirectToDotComLogin(error: error) { - // We need to chain the dismissing and presenting, - // which is not supported by SwiftUI's `dismiss` variable. - presenter.dismiss(animated: true) { - Notice(title: Strings.wpcomSiteRedirect).post() - presentDotComLogin() - } - } else { - errorMessage = error.localizedDescription - } + errorMessage = error.localizedDescription + return } - } - // If the error is "API root (wp-json) not found", it's possible that the user typed - // a WP.com simple site address. We should redirect to WP.com login if that's - // the case. - private func shouldRedirectToDotComLogin( - error: SelfHostedSiteAuthenticator.SignInError - ) async -> Bool { - guard case let .authentication(error) = error, - let error = error as? AutoDiscoveryAttemptFailure, - error.shouldAttemptDotComLogin else { return false} - - let api = WordPressComRestApi.anonymousApi(userAgent: WPUserAgent.defaultUserAgent()) - let remote = BlogServiceRemoteREST(wordPressComRestApi: api, siteID: 0) - let url = WordPressAuthenticator.baseSiteURL(string: urlField) - let response: [AnyHashable: Any]? = await withCheckedContinuation { continuation in - remote.fetchUnauthenticatedSiteInfo(forAddress: url) { - continuation.resume(returning: $0) - } failure: { _ in - continuation.resume(returning: nil) + if Task.isCancelled { return } + + if case let .oAuth2(endpoints) = details.authentication, + endpoints.authorizationUrl.contains("public-api.wordpress.com") { + presenter.dismiss(animated: true) { + Notice(title: Strings.wpcomSiteRedirect).post() + presentDotComLogin() } + return + } + + do { + let blog = try await SelfHostedSiteAuthenticator() + .signIn(details: details, from: presenter, context: .default) + dismiss() + self.loginCompleted(blog) + } catch { + errorMessage = error.localizedDescription } - return (response?["isWordPressDotCom"] as? Bool) == true } } @@ -176,17 +165,6 @@ private enum Strings { ) } -private extension AutoDiscoveryAttemptFailure { - var shouldAttemptDotComLogin: Bool { - switch self { - case .ParseSiteUrl: - false - case .FindApiRoot, .FetchAndParseApiRoot: - true - } - } -} - // MARK: - SwiftUI Preview #Preview { diff --git a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift index a5e15fa6e0cc..88ecc4839423 100644 --- a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift +++ b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift @@ -157,11 +157,26 @@ struct SelfHostedSiteAuthenticator { @MainActor private func authenticate(details: AutoDiscoveryAttemptSuccess, from viewController: UIViewController) async throws(SignInError) -> WpApiApplicationPasswordDetails { + guard case let .applicationPasswords(authURL) = details.authentication else { + let failure = AutoDiscoveryAttemptFailure.FetchAndParseApiRoot( + parsedSiteUrl: details.parsedSiteUrl, + apiRootUrl: details.apiRootUrl, + fetchAndParseApiRootFailure: .applicationPasswordsNotSupported(apiDetails: details.apiDetails, reason: nil) + ) + throw .authentication(failure) + } + let appId = Self.wordPressAppId let appName = Self.wordPressAppName do { - let loginURL = details.loginURL(for: .init(id: appId, name: appName, callbackUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString)) + let loginURL = createApplicationPasswordAuthenticationUrl( + loginUrl: authURL, + appName: appName, + appId: appId, + successUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString, + rejectUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString + ).asURL() let callback = try await authorize(url: loginURL, callbackURL: SelfHostedSiteAuthenticator.callbackURL, from: viewController) return try internalClient.credentials(from: callback) } catch { diff --git a/WordPress/Classes/Login/WordPressDotComAuthenticator.swift b/WordPress/Classes/Login/WordPressDotComAuthenticator.swift index 26b4c5d832ed..571a37714f23 100644 --- a/WordPress/Classes/Login/WordPressDotComAuthenticator.swift +++ b/WordPress/Classes/Login/WordPressDotComAuthenticator.swift @@ -122,7 +122,11 @@ struct WordPressDotComAuthenticator { let token: String do { - token = try await authenticate(from: viewController, prefersEphemeralWebBrowserSession: hasAlreadySignedIn, accountEmail: context.accountEmail(in: coreDataStack.mainContext)) + if let tokenLaunchArgument = UserDefaults.standard.string(forKey: "ui-test-wpcom-token") { + token = tokenLaunchArgument + } else { + token = try await authenticate(from: viewController, prefersEphemeralWebBrowserSession: hasAlreadySignedIn, accountEmail: context.accountEmail(in: coreDataStack.mainContext)) + } } catch { throw .authentication(error) } diff --git a/WordPress/Classes/Services/ApplicationPasswordRepository.swift b/WordPress/Classes/Services/ApplicationPasswordRepository.swift index bc1b360b4221..d7d88fdf8a99 100644 --- a/WordPress/Classes/Services/ApplicationPasswordRepository.swift +++ b/WordPress/Classes/Services/ApplicationPasswordRepository.swift @@ -170,7 +170,7 @@ private extension ApplicationPasswordRepository { _ = try await api.applicationPasswords.retrieveCurrentWithViewContext() validPasswords.append(password) } catch let error as WpApiError { - if case let .WpError(errorCode, _, _, _) = error { + if case let .WpError(errorCode, _, _, _, _, _) = error { if errorCode == .Unauthorized { invalidPasswords.append(password) } diff --git a/docs/simulator-sign-in.md b/docs/simulator-sign-in.md new file mode 100644 index 000000000000..99ea9bb77f87 --- /dev/null +++ b/docs/simulator-sign-in.md @@ -0,0 +1,39 @@ +# Simulator Sign-In + +Launch the app with credentials to enable automatic sign-in on the simulator. + +## Step 1: Providing Credentials + +### Self-hosted site + +```bash +xcrun simctl launch --terminate-running-process booted org.wordpress \ + -ui-test-reset-everything \ + -ui-test-site-url https://example.com \ + -ui-test-site-user \ + -ui-test-site-pass +``` + +### WordPress.com account + +```bash +xcrun simctl launch --terminate-running-process booted org.wordpress \ + -ui-test-reset-everything \ + -ui-test-wpcom-token +``` + +## Step 2: Signing In + +After launching with credentials, the app displays a sign-in page with two buttons: **"Continue with WordPress.com"** and **"Enter your existing site address"**. + +### WordPress.com account + +Tap **"Continue with WordPress.com"**. You will be automatically signed in. + +### Self-hosted site + +1. Tap **"Enter your existing site address"** +2. Enter the site address in the text field +3. Tap **"Continue"** + +You will be automatically signed in.