diff --git a/Modules/Sources/WordPressShared/WKWebView+UserAgent.swift b/Modules/Sources/WordPressShared/WKWebView+UserAgent.swift new file mode 100644 index 000000000000..5a6418dfa54a --- /dev/null +++ b/Modules/Sources/WordPressShared/WKWebView+UserAgent.swift @@ -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() + } +} diff --git a/Modules/Sources/WordPressShared/WPUserAgent.swift b/Modules/Sources/WordPressShared/WPUserAgent.swift new file mode 100644 index 000000000000..b4fcc6a91615 --- /dev/null +++ b/Modules/Sources/WordPressShared/WPUserAgent.swift @@ -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) + } +} diff --git a/Modules/Tests/WordPressSharedTests/WPUserAgentTests.swift b/Modules/Tests/WordPressSharedTests/WPUserAgentTests.swift new file mode 100644 index 000000000000..fa028658b16c --- /dev/null +++ b/Modules/Tests/WordPressSharedTests/WPUserAgentTests.swift @@ -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 + ) + } +} diff --git a/WordPress/Classes/Login/WordPressDotComAuthenticator.swift b/WordPress/Classes/Login/WordPressDotComAuthenticator.swift index 6e3703c81dec..fddead0549f8 100644 --- a/WordPress/Classes/Login/WordPressDotComAuthenticator.swift +++ b/WordPress/Classes/Login/WordPressDotComAuthenticator.swift @@ -3,6 +3,7 @@ import AuthenticationServices import Foundation import UIKit import WordPressData +import WordPressShared /// Log in or sign up a WordPress.com account via web. /// diff --git a/WordPress/Classes/Models/Blog/Blog.m b/WordPress/Classes/Models/Blog/Blog.m index 00af1b831922..e41f18171680 100644 --- a/WordPress/Classes/Models/Blog/Blog.m +++ b/WordPress/Classes/Models/Blog/Blog.m @@ -2,7 +2,7 @@ #import "WPAccount.h" #import "AccountService.h" @import WordPressDataObjC; -#import "WPUserAgent.h" +@import WordPressShared; #import "WordPress-Swift.h" @import SFHFKeychainUtils; diff --git a/WordPress/Classes/Models/ReaderListTopic+Creation.swift b/WordPress/Classes/Models/ReaderListTopic+Creation.swift index 2f0abdfd6e2a..327e1bbcd8aa 100644 --- a/WordPress/Classes/Models/ReaderListTopic+Creation.swift +++ b/WordPress/Classes/Models/ReaderListTopic+Creation.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared extension ReaderListTopic { diff --git a/WordPress/Classes/Models/WPAccount+RestApi.swift b/WordPress/Classes/Models/WPAccount+RestApi.swift index 365c2521b17a..4bc92f082467 100644 --- a/WordPress/Classes/Models/WPAccount+RestApi.swift +++ b/WordPress/Classes/Models/WPAccount+RestApi.swift @@ -1,5 +1,5 @@ - import Foundation +import WordPressShared import WordPressKit extension WPAccount { diff --git a/WordPress/Classes/Networking/WordPressOrgRestApi+WordPress.swift b/WordPress/Classes/Networking/WordPressOrgRestApi+WordPress.swift index ccba1f67df81..9c22c74b2086 100644 --- a/WordPress/Classes/Networking/WordPressOrgRestApi+WordPress.swift +++ b/WordPress/Classes/Networking/WordPressOrgRestApi+WordPress.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared import WordPressKit private func apiBase(blog: Blog) -> URL? { diff --git a/WordPress/Classes/Services/AuthenticationService.swift b/WordPress/Classes/Services/AuthenticationService.swift index e8cb48b0fcec..8638808e4115 100644 --- a/WordPress/Classes/Services/AuthenticationService.swift +++ b/WordPress/Classes/Services/AuthenticationService.swift @@ -1,5 +1,6 @@ import AutomatticTracks import Foundation +import WordPressShared class AuthenticationService { diff --git a/WordPress/Classes/Services/BloggingPrompts/BloggingPromptsService.swift b/WordPress/Classes/Services/BloggingPrompts/BloggingPromptsService.swift index 0f7c7c1ed23e..e30bf1a23a5b 100644 --- a/WordPress/Classes/Services/BloggingPrompts/BloggingPromptsService.swift +++ b/WordPress/Classes/Services/BloggingPrompts/BloggingPromptsService.swift @@ -1,6 +1,7 @@ import CoreData import WordPressData import WordPressKit +import WordPressShared class BloggingPromptsService { let siteID: NSNumber diff --git a/WordPress/Classes/Services/Page Layouts/PageLayoutService.swift b/WordPress/Classes/Services/Page Layouts/PageLayoutService.swift index 937ac59af4c2..b34da9beb67c 100644 --- a/WordPress/Classes/Services/Page Layouts/PageLayoutService.swift +++ b/WordPress/Classes/Services/Page Layouts/PageLayoutService.swift @@ -2,6 +2,7 @@ import UIKit import CoreData import Gutenberg import WordPressKit +import WordPressShared class PageLayoutService { private struct Parameters { diff --git a/WordPress/Classes/Services/PostServiceRemoteFactory.swift b/WordPress/Classes/Services/PostServiceRemoteFactory.swift index 4d43327c3565..4741ba1c7b6d 100644 --- a/WordPress/Classes/Services/PostServiceRemoteFactory.swift +++ b/WordPress/Classes/Services/PostServiceRemoteFactory.swift @@ -1,6 +1,6 @@ - import Foundation import WordPressKit +import WordPressShared @objc class PostServiceRemoteFactory: NSObject { @objc func forBlog(_ blog: Blog) -> PostServiceRemote? { diff --git a/WordPress/Classes/Services/PushAuthenticationService.swift b/WordPress/Classes/Services/PushAuthenticationService.swift index 748977366e8b..dc224dbd27ba 100644 --- a/WordPress/Classes/Services/PushAuthenticationService.swift +++ b/WordPress/Classes/Services/PushAuthenticationService.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared /// The purpose of this service is to encapsulate the Restful API that performs Mobile 2FA /// Code Verification. diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index b9a8334fc2d4..5641e3519958 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -1,5 +1,6 @@ import Foundation import WordPressKit +import WordPressShared protocol ReaderCardServiceRemote { func fetchStreamCards(stream: ReaderStream, diff --git a/WordPress/Classes/Services/ReaderSiteSearchService.swift b/WordPress/Classes/Services/ReaderSiteSearchService.swift index ebc9179c1f8d..ac5d68fa1533 100644 --- a/WordPress/Classes/Services/ReaderSiteSearchService.swift +++ b/WordPress/Classes/Services/ReaderSiteSearchService.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared typealias ReaderSiteSearchSuccessBlock = (_ feeds: [ReaderFeed], _ hasMore: Bool, _ feedCount: Int) -> Void typealias ReaderSiteSearchFailureBlock = (_ error: Error?) -> Void diff --git a/WordPress/Classes/Services/ReaderTopicService+FollowedInterests.swift b/WordPress/Classes/Services/ReaderTopicService+FollowedInterests.swift index 4b0ddcc894aa..2e516d8fcce5 100644 --- a/WordPress/Classes/Services/ReaderTopicService+FollowedInterests.swift +++ b/WordPress/Classes/Services/ReaderTopicService+FollowedInterests.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared // MARK: - ReaderFollowedInterestsService diff --git a/WordPress/Classes/Services/ReaderTopicService+FollowedSites.swift b/WordPress/Classes/Services/ReaderTopicService+FollowedSites.swift index d652d3c963ae..bdf0f6704ce7 100644 --- a/WordPress/Classes/Services/ReaderTopicService+FollowedSites.swift +++ b/WordPress/Classes/Services/ReaderTopicService+FollowedSites.swift @@ -1,3 +1,4 @@ +import WordPressShared extension ReaderTopicService { diff --git a/WordPress/Classes/Services/ReaderTopicService+Interests.swift b/WordPress/Classes/Services/ReaderTopicService+Interests.swift index e7c51b78730c..4a225af12553 100644 --- a/WordPress/Classes/Services/ReaderTopicService+Interests.swift +++ b/WordPress/Classes/Services/ReaderTopicService+Interests.swift @@ -1,5 +1,6 @@ import Foundation import WordPressKit +import WordPressShared // MARK: - ReaderInterestsService diff --git a/WordPress/Classes/Services/ReaderTopicService+SiteInfo.swift b/WordPress/Classes/Services/ReaderTopicService+SiteInfo.swift index a134ac9a4881..d598eb00a08e 100644 --- a/WordPress/Classes/Services/ReaderTopicService+SiteInfo.swift +++ b/WordPress/Classes/Services/ReaderTopicService+SiteInfo.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared /// Protocol representing a service that retrieves the users followed interests/tags protocol ReaderSiteInfoService: AnyObject { diff --git a/WordPress/Classes/Services/ReaderTopicService+Subscriptions.swift b/WordPress/Classes/Services/ReaderTopicService+Subscriptions.swift index cd1fa124fbfe..67287cfdfd1c 100644 --- a/WordPress/Classes/Services/ReaderTopicService+Subscriptions.swift +++ b/WordPress/Classes/Services/ReaderTopicService+Subscriptions.swift @@ -1,5 +1,6 @@ import Foundation import WordPressKit +import WordPressShared private enum SubscriptionAction { case notifications(siteId: Int) diff --git a/WordPress/Classes/Services/SiteAddressService.swift b/WordPress/Classes/Services/SiteAddressService.swift index ef5af1bbe480..0ba3152ca403 100644 --- a/WordPress/Classes/Services/SiteAddressService.swift +++ b/WordPress/Classes/Services/SiteAddressService.swift @@ -1,4 +1,5 @@ import WordPressKit +import WordPressShared // MARK: - SiteAddressService diff --git a/WordPress/Classes/Services/SiteAssemblyService.swift b/WordPress/Classes/Services/SiteAssemblyService.swift index c94f6af44564..325f2c2891aa 100644 --- a/WordPress/Classes/Services/SiteAssemblyService.swift +++ b/WordPress/Classes/Services/SiteAssemblyService.swift @@ -1,5 +1,6 @@ import Foundation import WordPressData +import WordPressShared /// Site Creation Notification /// diff --git a/WordPress/Classes/Services/SiteSegmentsService.swift b/WordPress/Classes/Services/SiteSegmentsService.swift index 010a33cc9a38..e733e70aae58 100644 --- a/WordPress/Classes/Services/SiteSegmentsService.swift +++ b/WordPress/Classes/Services/SiteSegmentsService.swift @@ -1,6 +1,6 @@ - import Foundation import WordPressKit +import WordPressShared /// Abstracts the service to obtain site types typealias SiteSegmentsServiceCompletion = (SiteSegmentsResult) -> Void diff --git a/WordPress/Classes/Stores/ActivityStore.swift b/WordPress/Classes/Stores/ActivityStore.swift index 3a81d6752b08..58fc86e6bb32 100644 --- a/WordPress/Classes/Stores/ActivityStore.swift +++ b/WordPress/Classes/Stores/ActivityStore.swift @@ -1,6 +1,7 @@ import Foundation import WordPressKit import WordPressFlux +import WordPressShared // MARK: - Store helper types diff --git a/WordPress/Classes/Stores/PluginStore.swift b/WordPress/Classes/Stores/PluginStore.swift index 8fcb1e274daf..16e9aca567ff 100644 --- a/WordPress/Classes/Stores/PluginStore.swift +++ b/WordPress/Classes/Stores/PluginStore.swift @@ -1,6 +1,7 @@ import Foundation import WordPressFlux import WordPressKit +import WordPressShared enum PluginAction: Action { case activate(id: String, site: JetpackSiteRef) diff --git a/WordPress/Classes/Stores/StatsInsightsStore.swift b/WordPress/Classes/Stores/StatsInsightsStore.swift index 7c5ac2210d98..5e29fb7baebb 100644 --- a/WordPress/Classes/Stores/StatsInsightsStore.swift +++ b/WordPress/Classes/Stores/StatsInsightsStore.swift @@ -1,6 +1,7 @@ import Foundation import WordPressKit import WordPressFlux +import WordPressShared import WidgetKit import JetpackStatsWidgetsCore diff --git a/WordPress/Classes/Stores/StatsPeriodStore.swift b/WordPress/Classes/Stores/StatsPeriodStore.swift index 683e2bf1aaa1..c22af8ed2bc7 100644 --- a/WordPress/Classes/Stores/StatsPeriodStore.swift +++ b/WordPress/Classes/Stores/StatsPeriodStore.swift @@ -1,5 +1,6 @@ import Foundation import WordPressFlux +import WordPressShared import WidgetKit import JetpackStatsWidgetsCore diff --git a/WordPress/Classes/Stores/StatsRevampStore.swift b/WordPress/Classes/Stores/StatsRevampStore.swift index 947d931749bf..914908e9d4c9 100644 --- a/WordPress/Classes/Stores/StatsRevampStore.swift +++ b/WordPress/Classes/Stores/StatsRevampStore.swift @@ -1,5 +1,6 @@ import Foundation import WordPressFlux +import WordPressShared /// StatsRevampStore is created to support use cases in Stats that can combine /// different periods and endpoints. diff --git a/WordPress/Classes/Stores/TimeZoneStore.swift b/WordPress/Classes/Stores/TimeZoneStore.swift index 7c98d71c1bcf..33008ca4baf8 100644 --- a/WordPress/Classes/Stores/TimeZoneStore.swift +++ b/WordPress/Classes/Stores/TimeZoneStore.swift @@ -1,5 +1,6 @@ import WordPressFlux import WordPressKit +import WordPressShared struct TimeZoneQuery {} diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index a735289d6c2e..16a40febb0f3 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -76,10 +76,8 @@ #import "WPStyleGuide+Pages.h" #import "WPStyleGuide+WebView.h" #import "WPTableViewHandler.h" -#import "WPUserAgent.h" #import "WPWebViewController.h" #import "WPTabBarController.h" -#import "WPUserAgent.h" #import "WPLogger.h" #import "WPException.h" diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsTrackerWPCom.m b/WordPress/Classes/Utility/Analytics/WPAnalyticsTrackerWPCom.m index ba0f1dd911f1..44465d4b1aad 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsTrackerWPCom.m +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsTrackerWPCom.m @@ -1,5 +1,5 @@ #import "WPAnalyticsTrackerWPCom.h" -#import "WPUserAgent.h" +#import "WordPress-Swift.h" #import "Constants.h" @import WordPressShared; diff --git a/WordPress/Classes/Utility/Automated Transfer/AutomatedTransferHelper.swift b/WordPress/Classes/Utility/Automated Transfer/AutomatedTransferHelper.swift index 6749e1c5b776..cf7d209e1eca 100644 --- a/WordPress/Classes/Utility/Automated Transfer/AutomatedTransferHelper.swift +++ b/WordPress/Classes/Utility/Automated Transfer/AutomatedTransferHelper.swift @@ -1,5 +1,6 @@ import Foundation import WordPressFlux +import WordPressShared class AutomatedTransferHelper { diff --git a/WordPress/Classes/Utility/Networking/WordPressComRestApi+Defaults.swift b/WordPress/Classes/Utility/Networking/WordPressComRestApi+Defaults.swift index c00bdf3bc1e8..f7e80b761e3c 100644 --- a/WordPress/Classes/Utility/Networking/WordPressComRestApi+Defaults.swift +++ b/WordPress/Classes/Utility/Networking/WordPressComRestApi+Defaults.swift @@ -1,5 +1,6 @@ import Foundation import WordPressKit +import WordPressShared extension WordPressComRestApi { @objc public static func defaultApi(oAuthToken: String? = nil, diff --git a/WordPress/Classes/Utility/WPUserAgent.h b/WordPress/Classes/Utility/WPUserAgent.h deleted file mode 100644 index 51cf41e7331f..000000000000 --- a/WordPress/Classes/Utility/WPUserAgent.h +++ /dev/null @@ -1,24 +0,0 @@ -#import - -/** - * @class WPUserAgent - * @brief Takes care of the user-agent logic for WPiOS. - */ -@interface WPUserAgent : NSObject - -/** - * @brief Default User-Agent header. - */ -+ (NSString *)defaultUserAgent; - -/** - * @brief WordPress custom User-Agent header. - */ -+ (NSString *)wordPressUserAgent; - -/** - * @brief Sets User-Agent header of all web views to be the WordPress one. - */ -+ (void)useWordPressUserAgentInWebViews; - -@end diff --git a/WordPress/Classes/Utility/WPUserAgent.m b/WordPress/Classes/Utility/WPUserAgent.m deleted file mode 100644 index dabac750fc03..000000000000 --- a/WordPress/Classes/Utility/WPUserAgent.m +++ /dev/null @@ -1,60 +0,0 @@ -#import "WPUserAgent.h" -#import "WordPress-Swift.h" - -@import WebKit; - -static NSString* const WPUserAgentKeyUserAgent = @"UserAgent"; - -@implementation WPUserAgent - -+ (NSString *)defaultUserAgent -{ - static NSString * _defaultUserAgent; - static dispatch_once_t _onceToken; - dispatch_once(&_onceToken, ^{ - NSDictionary * registrationDomain = [[UserPersistentStoreFactory userDefaultsInstance] volatileDomainForName:NSRegistrationDomain]; - NSString *storeCurrentUA = [registrationDomain objectForKey:WPUserAgentKeyUserAgent]; - [[UserPersistentStoreFactory userDefaultsInstance] registerDefaults:@{WPUserAgentKeyUserAgent: @(0)}]; - - _defaultUserAgent = [self webViewUserAgent]; - - if (storeCurrentUA) { - [[UserPersistentStoreFactory userDefaultsInstance] registerDefaults:@{WPUserAgentKeyUserAgent: storeCurrentUA}]; - } - }); - NSAssert(_defaultUserAgent != nil, @"User agent shouldn't be nil"); - NSAssert([_defaultUserAgent length] > 0, @"User agent shouldn't be empty"); - - return _defaultUserAgent; -} - -+ (NSString *)wordPressUserAgent -{ - static NSString * _wordPressUserAgent; - if (_wordPressUserAgent == nil) { - NSString *defaultUA = [self defaultUserAgent]; - NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; - _wordPressUserAgent = [NSString stringWithFormat:@"%@ wp-iphone/%@", defaultUA, appVersion]; - } - - return _wordPressUserAgent; -} - -+ (void)useWordPressUserAgentInWebViews -{ - // Cleanup unused NSUserDefaults keys from older WPUserAgent implementation - [[UserPersistentStoreFactory userDefaultsInstance] removeObjectForKey:@"DefaultUserAgent"]; - [[UserPersistentStoreFactory userDefaultsInstance] removeObjectForKey:@"AppUserAgent"]; - - NSString *userAgent = [self wordPressUserAgent]; - - NSParameterAssert([userAgent isKindOfClass:[NSString class]]); - - NSDictionary *dictionary = @{WPUserAgentKeyUserAgent: userAgent}; - // We have to call registerDefaults else the change isn't picked up by WKWebViews. - [[UserPersistentStoreFactory userDefaultsInstance] registerDefaults:dictionary]; - - DDLogVerbose(@"User-Agent set to: %@", userAgent); -} - -@end diff --git a/WordPress/Classes/Utility/WPUserAgent.swift b/WordPress/Classes/Utility/WPUserAgent.swift deleted file mode 100644 index c86e3d9161f5..000000000000 --- a/WordPress/Classes/Utility/WPUserAgent.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation - -extension WPUserAgent { - - /// 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" - } -} diff --git a/WordPress/Classes/Utility/WebViewController/WPWebViewController.m b/WordPress/Classes/Utility/WebViewController/WPWebViewController.m index 5b61051a5a89..2b182a572dd8 100644 --- a/WordPress/Classes/Utility/WebViewController/WPWebViewController.m +++ b/WordPress/Classes/Utility/WebViewController/WPWebViewController.m @@ -1,7 +1,6 @@ #import "WPWebViewController.h" #import "ReachabilityUtils.h" #import "WPActivityDefaults.h" -#import "WPUserAgent.h" #import "Constants.h" #import "WPError.h" #import "WPStyleGuide+WebView.h" diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index 3c70c83f989d..dd96b2d7847a 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -1,4 +1,5 @@ import UIKit +import WordPressShared @preconcurrency import WebKit @objc protocol BlazeWebViewControllerDelegate { diff --git a/WordPress/Classes/ViewRelated/Reader/Navigation/ReaderNavigationPath.swift b/WordPress/Classes/ViewRelated/Reader/Navigation/ReaderNavigationPath.swift index 9c476c48b477..d2470546cee0 100644 --- a/WordPress/Classes/ViewRelated/Reader/Navigation/ReaderNavigationPath.swift +++ b/WordPress/Classes/ViewRelated/Reader/Navigation/ReaderNavigationPath.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared enum ReaderNavigationPath: Hashable { case recent diff --git a/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter.swift b/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter.swift index 1175b1d2e23f..bd6c2045169f 100644 --- a/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter.swift +++ b/WordPress/Classes/ViewRelated/Sharing/ShareAppContentPresenter.swift @@ -1,3 +1,5 @@ +import WordPressShared + /// Encapsulates the logic required to fetch, prepare, and present the contents for sharing the app to others. /// /// The contents for sharing is first fetched from the API, so the share presentation logic is not synchronously executed. diff --git a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift index e0ebe8699837..79e52f750bef 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/Preview/TemplatePreviewViewController.swift @@ -1,5 +1,5 @@ - import Foundation +import WordPressShared protocol TemplatePreviewViewDelegate: AnyObject { typealias PreviewDevice = PreviewDeviceSelectionViewController.PreviewDevice diff --git a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/SiteDesignSectionLoader.swift b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/SiteDesignSectionLoader.swift index 8c6675270de1..92adfbe90af5 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Design Selection/SiteDesignSectionLoader.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Design Selection/SiteDesignSectionLoader.swift @@ -1,5 +1,6 @@ import Foundation import WordPressKit +import WordPressShared struct SiteDesignSectionLoader { typealias Assembler = ((SiteIntentVertical?) -> [SiteDesignSection]) diff --git a/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/AssembledSiteView.swift b/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/AssembledSiteView.swift index 5b166cb2e776..41f49ae02bd6 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/AssembledSiteView.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/AssembledSiteView.swift @@ -1,5 +1,5 @@ - import UIKit +import WordPressShared @preconcurrency import WebKit // MARK: AssembledSiteView diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersStore.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersStore.swift index 424d87afe06d..8564e4533496 100644 --- a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersStore.swift +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersStore.swift @@ -1,6 +1,7 @@ import Foundation import Combine import WordPressKit +import WordPressShared protocol StatsSubscribersStoreProtocol { var emailsSummary: CurrentValueSubject, Never> { get } diff --git a/WordPress/Classes/ViewRelated/WhatsNew/Dependency container/RootViewCoordinator+WhatIsNew.swift b/WordPress/Classes/ViewRelated/WhatsNew/Dependency container/RootViewCoordinator+WhatIsNew.swift index 08cf26500b73..4da5ad380097 100644 --- a/WordPress/Classes/ViewRelated/WhatsNew/Dependency container/RootViewCoordinator+WhatIsNew.swift +++ b/WordPress/Classes/ViewRelated/WhatsNew/Dependency container/RootViewCoordinator+WhatIsNew.swift @@ -1,3 +1,4 @@ +import WordPressShared /// dependency container for the What's New / Feature Announcements scene extension RootViewCoordinator { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index e04de3b87e25..dcb9d4c570ff 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -674,7 +674,6 @@ 57B71D4E230DB5F200789A68 /* BlogBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B71D4D230DB5F200789A68 /* BlogBuilder.swift */; }; 5948AD111AB73D19006E8882 /* WPAppAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */; }; 5960967F1CF7959300848496 /* PostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5960967E1CF7959300848496 /* PostTests.swift */; }; - 5981FE051AB8A89A0009E080 /* WPUserAgentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5981FE041AB8A89A0009E080 /* WPUserAgentTests.m */; }; 59B48B621B99E132008EBB84 /* JSONObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B48B611B99E132008EBB84 /* JSONObject.swift */; }; 59ECF87B1CB7061D00E68F25 /* PostSharingControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59ECF87A1CB7061D00E68F25 /* PostSharingControllerTests.swift */; }; 59FBD5621B5684F300734466 /* ThemeServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 59FBD5611B5684F300734466 /* ThemeServiceTests.m */; }; @@ -2484,7 +2483,6 @@ 57B71D4D230DB5F200789A68 /* BlogBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogBuilder.swift; sourceTree = ""; }; 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = WPAppAnalyticsTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 5960967E1CF7959300848496 /* PostTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostTests.swift; sourceTree = ""; }; - 5981FE041AB8A89A0009E080 /* WPUserAgentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPUserAgentTests.m; sourceTree = ""; }; 59A9AB391B4C3ECD00A433DC /* LocalCoreDataServiceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalCoreDataServiceTests.m; sourceTree = ""; }; 59B48B611B99E132008EBB84 /* JSONObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONObject.swift; path = TestUtilities/JSONObject.swift; sourceTree = ""; }; 59ECF87A1CB7061D00E68F25 /* PostSharingControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PostSharingControllerTests.swift; path = Posts/PostSharingControllerTests.swift; sourceTree = ""; }; @@ -5587,7 +5585,6 @@ F4394D202A3B6F43003955C6 /* Crash Logging */, 93A379EB19FFBF7900415023 /* KeychainTest.m */, 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */, - 5981FE041AB8A89A0009E080 /* WPUserAgentTests.m */, 82301B8E1E787420009C9C4E /* AppRatingUtilityTests.swift */, F551E7F623FC9A5C00751212 /* Collection+RotateTests.swift */, E180BD4B1FB462FF00D0D781 /* CookieJarTests.swift */, @@ -9211,7 +9208,6 @@ 3F1B66A323A2F54B0075F09E /* ReaderReblogActionTests.swift in Sources */, FEE48EFF2A4C9855008A48E0 /* Blog+PublicizeTests.swift in Sources */, 08B6E51C1F037ADD00268F57 /* MediaFileManagerTests.swift in Sources */, - 5981FE051AB8A89A0009E080 /* WPUserAgentTests.m in Sources */, 73178C2C21BEE09300E37C9A /* SiteCreationHeaderDataTests.swift in Sources */, 3F28CEAF2A4ACEBE00B79686 /* PrivacySettingsViewControllerTests.swift in Sources */, 08A2AD7B1CCED8E500E84454 /* PostCategoryServiceTests.m in Sources */, diff --git a/WordPress/WordPressTest/WPUserAgentTests.m b/WordPress/WordPressTest/WPUserAgentTests.m deleted file mode 100644 index f2856a408176..000000000000 --- a/WordPress/WordPressTest/WPUserAgentTests.m +++ /dev/null @@ -1,118 +0,0 @@ -#import - -#import "WPUserAgent.h" -@import WebKit; - -static NSString* const WPUserAgentKeyUserAgent = @"UserAgent"; - -@interface WPUserAgentTests : XCTestCase -@end - -@implementation WPUserAgentTests - -- (NSString *)currentUserAgentFromUserDefaults -{ - return [[NSUserDefaults standardUserDefaults] objectForKey:WPUserAgentKeyUserAgent]; -} - -- (NSString *)currentUserAgentFromWebView -{ - return [WKWebView userAgent]; -} - -- (void)testWordPressUserAgent -{ - NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; - NSString *defaultUserAgent = [WPUserAgent defaultUserAgent]; - NSString *expectedUserAgent = [NSString stringWithFormat:@"%@ wp-iphone/%@", defaultUserAgent, appVersion]; - - XCTAssertEqualObjects([WPUserAgent wordPressUserAgent], expectedUserAgent); -} - -- (NSRegularExpression *)webkitUserAgentRegex -{ - NSError *error = nil; - NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:@"^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$" options:0 error:&error]; - XCTAssertNil(error); - return regex; -} - -- (void)testUserAgentFormat -{ - NSRegularExpression *regex = [self webkitUserAgentRegex]; - NSString *userAgent = [WPUserAgent webViewUserAgent]; - XCTAssertEqual([regex numberOfMatchesInString:userAgent options:0 range:NSMakeRange(0, userAgent.length)], 1); -} - -// If this test fails, it may mean `WKWebView` uses a user agent with an unexpected format (see `webkitUserAgentRegex`) -// and we may need to adjust `UserAgent.webkitUserAgent`'s implementation to match `WKWebView`'s user agent. -- (void)testWKWebViewUserAgentFormat -{ - NSRegularExpression *regex = [self webkitUserAgentRegex]; - // Please note: WKWebView's user agent may be different on different test device types. - NSString *userAgent = [self currentUserAgentFromWebView]; - XCTAssertEqual([regex numberOfMatchesInString:userAgent options:0 range:NSMakeRange(0, userAgent.length)], 1); -} - -- (void)testUseWordPressUserAgentInWebViews -{ - NSString *defaultUA = [WPUserAgent defaultUserAgent]; - NSString *wordPressUA = [WPUserAgent wordPressUserAgent]; - - [[NSUserDefaults standardUserDefaults] removeObjectForKey:WPUserAgentKeyUserAgent]; - [[NSUserDefaults standardUserDefaults] registerDefaults:@{WPUserAgentKeyUserAgent: defaultUA}]; - - XCTAssertEqualObjects([self currentUserAgentFromUserDefaults], defaultUA); - XCTAssertEqualObjects([self currentUserAgentFromWebView], defaultUA); - - if (@available(iOS 17, *)) { - XCTSkip("In iOS 17, WKWebView no longer reads User Agent from UserDefaults. Skipping while working on an alternative setup."); - } - - [WPUserAgent useWordPressUserAgentInWebViews]; - XCTAssertEqualObjects([self currentUserAgentFromUserDefaults], wordPressUA); - XCTAssertEqualObjects([self currentUserAgentFromWebView], wordPressUA); -} - -- (void)testThatOriginalRemovalOfWPUseKeyUserAgentDoesntWork { - if (@available(iOS 17, *)) { - XCTSkip("In iOS 17, WKWebView no longer reads User Agent from UserDefaults. Skipping while working on an alternative setup."); - } - - // get the original user agent - NSString *originalUserAgentInWebView = [self currentUserAgentFromWebView]; - NSLog(@"OriginalUserAgent (WebView): %@", originalUserAgentInWebView); - - // set a new one - [[NSUserDefaults standardUserDefaults] registerDefaults:@{WPUserAgentKeyUserAgent: @"new user agent"}]; - - NSString *changedUserAgentInWebView = [self currentUserAgentFromWebView]; - NSLog(@"changedUserAgent (WebView): %@", changedUserAgentInWebView); - - // try to remove it using old method - [[NSUserDefaults standardUserDefaults] registerDefaults:@{}]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:WPUserAgentKeyUserAgent]; - - NSString *shouldBeOriginalInWebView = [self currentUserAgentFromWebView]; - NSLog(@"shouldBeOriginal (WebView): %@", shouldBeOriginalInWebView); - - XCTAssertNotEqualObjects(originalUserAgentInWebView, shouldBeOriginalInWebView); -} - -- (void)testThatCallingFromAnotherThreadWorks { - // get the original user agent - dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - XCTAssertNoThrow([WPUserAgent wordPressUserAgent], @"Being called from out of main thread should work"); - }); -} - -- (void)testThatRegistarDefaultJustAdds { - NSDictionary * registrationDomain = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain]; - [[NSUserDefaults standardUserDefaults] registerDefaults:@{WPUserAgentKeyUserAgent: @(0)}]; - NSDictionary * changedRegistrationDomain = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain]; - - XCTAssertTrue((registrationDomain.count == changedRegistrationDomain.count) || ((registrationDomain.count +1) == changedRegistrationDomain.count), "It should add or reset"); -} - - -@end