-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Extract WPUserAgent to WordPressShared #24216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
01d9835
Extract `defaultUserAgent` method to Swift
mokagio dd814b4
Extract `wordPressUserAgent` to Swift
mokagio 23eeeac
Port remaining WPUserAgent tests to Swift
mokagio d5bbce2
Port `useWordPressUserAgentInWebViews` to Swift
mokagio 24c359a
Address threading issue in tests when calling WebKit
mokagio cf73076
Replace Objective-C `WPUserAgent` with Swift one
mokagio 8bfb22a
Add CocoaLumberjack as a dependency to WordPressShared
mokagio 93d597c
Move `WPUserAgent` to WordPressShared
mokagio 71f1963
Add TODO note regarding CocoaLumberjack usage in WordPressShared
mokagio f95be44
Remove CocoaLumberjack from WordPressShared – Runtime issues
mokagio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import WebKit | ||
|
||
@objc | ||
public extension WKWebView { | ||
static let userAgentKey = "_userAgent" | ||
|
||
/// Call this method to get the user agent for the WKWebView | ||
@objc | ||
func userAgent() -> String { | ||
guard let userAgent = value(forKey: WKWebView.userAgentKey) as? String, !userAgent.isEmpty else { | ||
// TODO: Original implementation logged a message to Tracks/Sentry | ||
print("This method for retrieveing the user agent seems to be no longer working. We need to figure out an alternative.") | ||
return "" | ||
} | ||
|
||
return userAgent | ||
} | ||
|
||
/// Static version of the method that returns the current user agent. | ||
@objc | ||
static func userAgent() -> String { | ||
return WKWebView().userAgent() | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import Foundation | ||
import WebKit | ||
|
||
@objc | ||
public class WPUserAgent: NSObject { | ||
|
||
public static let userAgentKey = "UserAgent" | ||
|
||
@objc | ||
public static func defaultUserAgent(userDefaults: UserDefaults) -> String { | ||
// FIXME: What's the point of reading from user defaults then returning a possibly different value? | ||
// See original Objective-C implementation at | ||
// https://github.com/wordpress-mobile/WordPress-iOS/blob/a6eaa7aa8acb50828449df2d3fccaa50d7def821/WordPress/Classes/Utility/WPUserAgent.m#L10-L29 | ||
|
||
// 1. Extract current stored user agent | ||
let registrationDomain = userDefaults.volatileDomain(forName: UserDefaults.registrationDomain) | ||
let storedUserAgent = registrationDomain[userAgentKey] as? String | ||
|
||
// 2. Flush stored user agent | ||
userDefaults.register(defaults: [userAgentKey: ""]) | ||
|
||
// 3. Reset the stored user agent if there was one in the first place (why??) | ||
if let storedUserAgent { | ||
userDefaults.register(defaults: [userAgentKey: storedUserAgent]) | ||
} | ||
|
||
let userAgent = WPUserAgent.webViewUserAgent | ||
assert(!userAgent.isEmpty, "User agent should not be empty") | ||
return userAgent | ||
} | ||
|
||
// The original impelmentation had logic to only read this once if not nil. | ||
// But why? The performance hit should be negligible. | ||
// | ||
// See original implementation at | ||
// https://github.com/wordpress-mobile/WordPress-iOS/blob/a6eaa7aa8acb50828449df2d3fccaa50d7def821/WordPress/Classes/Utility/WPUserAgent.m#L31-L41 | ||
@objc | ||
public static func wordPressUserAgent(userDefaults: UserDefaults, bundle: Bundle = .main) -> String { | ||
let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" | ||
return "\(defaultUserAgent(userDefaults: userDefaults)) wp-iphone/\(appVersion)" | ||
} | ||
|
||
@objc | ||
public static func useWordPressInWebViews(userDefaults: UserDefaults) { | ||
// Cleanup unused UserDefaults keys from older WPUserAgent implementation | ||
// FIXME: How old are those older implementations? Can we remove this code because "old enough"? | ||
userDefaults.removeObject(forKey: "DefaultUserAgent") | ||
userDefaults.removeObject(forKey: "AppUserAgent") | ||
|
||
let userAgent = wordPressUserAgent(userDefaults: userDefaults) | ||
|
||
userDefaults.register(defaults: [userAgentKey: userAgent]) | ||
|
||
print("User-Agent set to \(userAgent)") | ||
} | ||
|
||
/// Returns a user agent string similar to (but may not exactly match) the one used in `WKWebView`. | ||
@objc static var webViewUserAgent: String { | ||
// Examples user agent strings from `WKWebView` in iOS simulators: | ||
// | ||
// ## iPhone 15 Pro (iOS 17.2) | ||
// Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 | ||
// | ||
// ## iPad Pro (iOS 17.0.1) | ||
// Mozilla/5.0 (iPad; CPU OS 17_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 | ||
// | ||
// Based on the WebKit implementation[^1], most of the components are hardcoded, and there are only a couple of dynamic components: | ||
// 1. Device model. i.e. iPhone/iPad | ||
// 2. OS name and version. i.e. iPhone OS 17_2 | ||
// | ||
// Please note the "Mobile/15E148" part is WKWebView's default and hardcoded "application name"[^2]. | ||
// | ||
// [^1]: https://github.com/WebKit/WebKit/blob/5fbb03ee1c6210c79779d6fa1a9e7290daa746d1/Source/WebCore/platform/ios/UserAgentIOS.mm#L88-L113 | ||
// [^2]: https://github.com/WebKit/WebKit/blob/492140d27dbe/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm#L612 | ||
|
||
let device = UIDevice.current | ||
|
||
let deviceModel = device.model // Example: "iPhone" | ||
var osName = device.systemName // Example: "iPhone OS" | ||
let osVersion = device.systemVersion.replacingOccurrences(of: ".", with: "_") // Example: "17_2" | ||
|
||
// WKWebView on iPad uses a static user agent. | ||
// https://github.com/WebKit/WebKit/blob/6a053cfb431bd70d5017ba881a39f004e52effc2/Source/WebCore/platform/ios/UserAgentIOS.mm#L97 | ||
if device.userInterfaceIdiom == .pad { | ||
osName = "OS" | ||
} | ||
|
||
// Use "iPhone OS" instead of "iOS", because that's what WKWebView uses. | ||
if osName == "iOS" { | ||
osName = "iPhone OS" | ||
} | ||
|
||
return "Mozilla/5.0 (\(deviceModel); CPU \(osName) \(osVersion) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" | ||
} | ||
} | ||
|
||
public extension WPUserAgent { | ||
|
||
private static let userDefaults = UserPersistentStoreFactory.userDefaultsInstance() | ||
|
||
@objc | ||
static func defaultUserAgent() -> String { | ||
defaultUserAgent(userDefaults: userDefaults) | ||
} | ||
|
||
@objc(wordPressUserAgent) | ||
static func wordPress() -> String { | ||
wordPressUserAgent(userDefaults: userDefaults) | ||
} | ||
|
||
@objc | ||
static func useWordPressInWebViews() { | ||
useWordPressInWebViews(userDefaults: userDefaults) | ||
} | ||
} | ||
115 changes: 115 additions & 0 deletions
115
Modules/Tests/WordPressSharedTests/WPUserAgentTests.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import Foundation | ||
import Testing | ||
import WebKit | ||
import WordPressShared | ||
|
||
class WPWPUserAgentTests { | ||
|
||
@Test | ||
func userAgentFormat() throws { | ||
let userAgent = WPUserAgent.defaultUserAgent(userDefaults: .standard) | ||
|
||
#expect( | ||
try webKitUserAgentRegExp().numberOfMatches( | ||
in: userAgent, | ||
options: [], | ||
range: NSRange(location: 0, length: userAgent.utf16.count) | ||
) == 1 | ||
) | ||
} | ||
|
||
@Test | ||
func wordPressUserAgentValue() throws { | ||
let userDefaults = UserDefaults.standard | ||
let appVersion = try #require(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) | ||
let defaultUserAgent = WPUserAgent.defaultUserAgent(userDefaults: userDefaults) | ||
let expectedUserAgent = String.init(format: "%@ wp-iphone/%@", defaultUserAgent, appVersion) | ||
|
||
#expect(WPUserAgent.wordPressUserAgent(userDefaults: userDefaults) == expectedUserAgent) | ||
} | ||
|
||
@Test @MainActor | ||
func usesWordPressUserAgentInWebViews() throws { | ||
if #available(iOS 17, *) { // #available cannot go as an argument in @Test(.enabled(if: ..)) | ||
print("In iOS 17, WKWebView no longer reads User Agent from UserDefaults. Skipping while working on an alternative setup.") | ||
return | ||
} | ||
|
||
let userDefaults = UserDefaults.standard | ||
let defaultUserAgent = WPUserAgent.defaultUserAgent(userDefaults: userDefaults) | ||
let wordPressUserAgent = WPUserAgent.wordPressUserAgent(userDefaults: userDefaults) | ||
|
||
// FIXME: Is this necessary? | ||
// See original implementation at | ||
// https://github.com/wordpress-mobile/WordPress-iOS/blob/a6eaa7aa8acb50828449df2d3fccaa50d7def821/WordPress/WordPressTest/WPUserAgentTests.m#L57-L75 | ||
userDefaults.removeObject(forKey: WPUserAgent.userAgentKey) | ||
userDefaults.register(defaults: [WPUserAgent.userAgentKey: defaultUserAgent]) | ||
|
||
WPUserAgent.useWordPressInWebViews(userDefaults: userDefaults) | ||
|
||
#expect(try currentUserAgent(userDefaults: userDefaults) == wordPressUserAgent) | ||
#expect(try currentUserAgentFromWebView() == wordPressUserAgent) | ||
} | ||
|
||
// FIXME: Is there even a point in testing for no throws when the method does not throw? | ||
// See original implementation at | ||
// https://github.com/wordpress-mobile/WordPress-iOS/blob/a6eaa7aa8acb50828449df2d3fccaa50d7def821/WordPress/WordPressTest/WPUserAgentTests.m#L102-L107 | ||
@Test | ||
func accessingWordPressUserAgentOutsideMainThread() { | ||
#expect(throws: Never.self, "Accessing outside the main thread should work") { | ||
DispatchQueue.global(qos: .background).sync { | ||
WPUserAgent.wordPressUserAgent(userDefaults: .standard) | ||
} | ||
} | ||
} | ||
|
||
func currentUserAgent(userDefaults: UserDefaults) throws -> String { | ||
try #require(userDefaults.object(forKey: WPUserAgent.userAgentKey) as? String) | ||
} | ||
|
||
@MainActor | ||
func currentUserAgentFromWebView() throws -> String { | ||
try #require(WKWebView.userAgent()) | ||
} | ||
|
||
func webKitUserAgentRegExp() throws -> NSRegularExpression { | ||
try NSRegularExpression( | ||
pattern: "^Mozilla/5\\.0 \\([a-zA-Z]+; CPU [\\sa-zA-Z]+ [_0-9]+ like Mac OS X\\) AppleWebKit/605\\.1\\.15 \\(KHTML, like Gecko\\) Mobile/15E148$" | ||
) | ||
} | ||
|
||
// MARK: - Tests for underlying assumptions | ||
|
||
@Test | ||
func registerInUserDefaultsAdds() throws { | ||
let userDefaults = UserDefaults.standard | ||
let domainName = try #require(userDefaults.volatileDomainNames.first) | ||
let originalDomain = userDefaults.volatileDomain(forName: domainName) | ||
|
||
userDefaults.register(defaults: ["test-key": 0]) | ||
|
||
let updatedDomain = userDefaults.volatileDomain(forName: domainName) | ||
|
||
// From the docs: | ||
// Registered defaults are never stored between runs of an application, and are visible only to the application that registers them | ||
// | ||
// So we expect the count to be +1 | ||
#expect(updatedDomain.count == originalDomain.count + 1) | ||
} | ||
|
||
// If this test fails, it may mean `WKWebView` uses a user agent with an unexpected format (see `webKitUserAgentRegExp`) | ||
// and we may need to adjust our implementation to match the new `WKWebView` user agent. | ||
@Test @MainActor | ||
func testWebKitUserAgentFormat() throws { | ||
let regExp = try webKitUserAgentRegExp() | ||
// Please note: WKWebView's user agent may be different on different test device types. | ||
let userAgent = try currentUserAgentFromWebView() | ||
#expect( | ||
try webKitUserAgentRegExp().numberOfMatches( | ||
in: userAgent, | ||
options: [], | ||
range: NSRange(location: 0, length: userAgent.utf16.count) | ||
) == 1 | ||
) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import Foundation | ||
import WordPressShared | ||
|
||
extension ReaderListTopic { | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
|
||
import Foundation | ||
import WordPressShared | ||
import WordPressKit | ||
|
||
extension WPAccount { | ||
|
1 change: 1 addition & 0 deletions
1
WordPress/Classes/Networking/WordPressOrgRestApi+WordPress.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import Foundation | ||
import WordPressShared | ||
import WordPressKit | ||
|
||
private func apiBase(blog: Blog) -> URL? { | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import AutomatticTracks | ||
import Foundation | ||
import WordPressShared | ||
|
||
class AuthenticationService { | ||
|
||
|
1 change: 1 addition & 0 deletions
1
WordPress/Classes/Services/BloggingPrompts/BloggingPromptsService.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
WordPress/Classes/Services/ReaderTopicService+FollowedInterests.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import Foundation | ||
import WordPressShared | ||
|
||
// MARK: - ReaderFollowedInterestsService | ||
|
||
|
1 change: 1 addition & 0 deletions
1
WordPress/Classes/Services/ReaderTopicService+FollowedSites.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import WordPressShared | ||
|
||
extension ReaderTopicService { | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import Foundation | ||
import WordPressKit | ||
import WordPressShared | ||
|
||
// MARK: - ReaderInterestsService | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
WordPress/Classes/Services/ReaderTopicService+Subscriptions.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import WordPressKit | ||
import WordPressShared | ||
|
||
// MARK: - SiteAddressService | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import Foundation | ||
import WordPressData | ||
import WordPressShared | ||
|
||
/// Site Creation Notification | ||
/// | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote this thinking that the code would not have access to
UserPersistentStoreFactory
, so this would have stayed in the app target.But I had forgotten about #24210
I suppose I could rewrite the definitions to use the default arguments? Or, this could stay the way it is and work as a reminder for code we should remove (i.e. the Objective-C implementation)?