Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 1 addition & 2 deletions Fakes/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ extension Networking.Account {
displayName: .fake(),
email: .fake(),
username: .fake(),
gravatarUrl: .fake(),
ipCountryCode: .fake()
gravatarUrl: .fake()
)
}
}
Expand Down
12 changes: 12 additions & 0 deletions Networking/Networking.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@
261CF1BC255AEE290090D8D3 /* PaymentsGatewayRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 261CF1BB255AEE290090D8D3 /* PaymentsGatewayRemoteTests.swift */; };
261CF2CB255C50010090D8D3 /* payment-gateway-list-half.json in Resources */ = {isa = PBXBuildFile; fileRef = 261CF2CA255C50010090D8D3 /* payment-gateway-list-half.json */; };
262E5AD5255ACD6F000B2416 /* PaymentGatewayListMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 262E5AD4255ACD6F000B2416 /* PaymentGatewayListMapperTests.swift */; };
263659DC2A264A3E00607A0D /* IPLocationRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263659DB2A264A3E00607A0D /* IPLocationRemote.swift */; };
263659DE2A2694A000607A0D /* IPLocationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263659DD2A2694A000607A0D /* IPLocationRemoteTests.swift */; };
263E37D22641ACEA00260D3B /* Codegen in Frameworks */ = {isa = PBXBuildFile; productRef = 263E37D12641ACEA00260D3B /* Codegen */; };
263E383F2641FF1600260D3B /* Codegen in Frameworks */ = {isa = PBXBuildFile; productRef = 263E383E2641FF1600260D3B /* Codegen */; };
263E38402641FF1600260D3B /* Codegen in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 263E383E2641FF1600260D3B /* Codegen */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
Expand Down Expand Up @@ -211,6 +213,7 @@
268B68FB24C87384007EBF1D /* leaderboards-products.json in Resources */ = {isa = PBXBuildFile; fileRef = 268B68FA24C87384007EBF1D /* leaderboards-products.json */; };
268B68FD24C87E37007EBF1D /* leaderboards-year-alt.json in Resources */ = {isa = PBXBuildFile; fileRef = 268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */; };
268EC45C26C169F600716F5C /* order-with-faulty-attributes.json in Resources */ = {isa = PBXBuildFile; fileRef = 268EC45B26C169F600716F5C /* order-with-faulty-attributes.json */; };
26B15E442A269F79000C35E4 /* ip-location.json in Resources */ = {isa = PBXBuildFile; fileRef = 26B15E432A269F79000C35E4 /* ip-location.json */; };
26B2F74124C1F2C10065CCC8 /* LeaderboardsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2F74024C1F2C10065CCC8 /* LeaderboardsRemote.swift */; };
26B2F74324C545D50065CCC8 /* Leaderboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2F74224C545D50065CCC8 /* Leaderboard.swift */; };
26B2F74524C5573F0065CCC8 /* LeaderboardListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2F74424C5573F0065CCC8 /* LeaderboardListMapper.swift */; };
Expand Down Expand Up @@ -1111,6 +1114,8 @@
261CF1BB255AEE290090D8D3 /* PaymentsGatewayRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentsGatewayRemoteTests.swift; sourceTree = "<group>"; };
261CF2CA255C50010090D8D3 /* payment-gateway-list-half.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "payment-gateway-list-half.json"; sourceTree = "<group>"; };
262E5AD4255ACD6F000B2416 /* PaymentGatewayListMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentGatewayListMapperTests.swift; sourceTree = "<group>"; };
263659DB2A264A3E00607A0D /* IPLocationRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemote.swift; sourceTree = "<group>"; };
263659DD2A2694A000607A0D /* IPLocationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemoteTests.swift; sourceTree = "<group>"; };
265BCA01243056E3004E53EE /* categories-all.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "categories-all.json"; sourceTree = "<group>"; };
265EFBDB285257950033BD33 /* Order+Fallbacks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Order+Fallbacks.swift"; sourceTree = "<group>"; };
26615472242D596B00A31661 /* ProductCategoriesRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductCategoriesRemote.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1138,6 +1143,7 @@
268B68FA24C87384007EBF1D /* leaderboards-products.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "leaderboards-products.json"; sourceTree = "<group>"; };
268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "leaderboards-year-alt.json"; sourceTree = "<group>"; };
268EC45B26C169F600716F5C /* order-with-faulty-attributes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-with-faulty-attributes.json"; sourceTree = "<group>"; };
26B15E432A269F79000C35E4 /* ip-location.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "ip-location.json"; sourceTree = "<group>"; };
26B2F74024C1F2C10065CCC8 /* LeaderboardsRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardsRemote.swift; sourceTree = "<group>"; };
26B2F74224C545D50065CCC8 /* Leaderboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Leaderboard.swift; sourceTree = "<group>"; };
26B2F74424C5573F0065CCC8 /* LeaderboardListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardListMapper.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2076,6 +2082,7 @@
24F98C5F2502EF8200F49B68 /* FeatureFlagRemoteTests.swift */,
4513382127A8409000AE5E78 /* InboxNotesRemoteTests.swift */,
E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */,
263659DD2A2694A000607A0D /* IPLocationRemoteTests.swift */,
03EB99892906AB0C00F06A39 /* JustInTimeMessagesRemoteTests.swift */,
26B2F74824C55ACE0065CCC8 /* LeaderboardsRemoteTests.swift */,
020D07BF23D8587700FD9580 /* MediaRemoteTests.swift */,
Expand Down Expand Up @@ -2230,6 +2237,7 @@
24F98C512502E79800F49B68 /* FeatureFlagRemote.swift */,
E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */,
4513381F27A8227F00AE5E78 /* InboxNotesRemote.swift */,
263659DB2A264A3E00607A0D /* IPLocationRemote.swift */,
03EB99872906A78400F06A39 /* JustInTimeMessagesRemote.swift */,
26B2F74024C1F2C10065CCC8 /* LeaderboardsRemote.swift */,
B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */,
Expand Down Expand Up @@ -2459,6 +2467,7 @@
DE50296228C609DE00551736 /* jetpack-user-not-connected.json */,
02B41A91296BEB3000FE3311 /* load-site-current-plan-success.json */,
02B41A93296C04BC00FE3311 /* load-site-plans-no-current-plan.json */,
26B15E432A269F79000C35E4 /* ip-location.json */,
EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */,
02B41A8F296BC85800FE3311 /* site-domains.json */,
02935AED29DFFA74001B793E /* site-enable-trial-error-already-upgraded.json */,
Expand Down Expand Up @@ -3346,6 +3355,7 @@
026CF624237D839B009563D4 /* product-variations-load-all.json in Resources */,
02AF07EC27492FDD00B2D81E /* media-library-from-wordpress-site.json in Resources */,
CC9A253C26442C71005DE56E /* shipping-label-eligibility-success.json in Resources */,
26B15E442A269F79000C35E4 /* ip-location.json in Resources */,
B5A24179217F98F600595DEF /* notifications-load-all.json in Resources */,
DEA6B1C9296D0E8B005AA5E9 /* systemStatusWithPluginsOnly-without-data.json in Resources */,
02C4325F298A55D100F14AEE /* domain-contact-info.json in Resources */,
Expand Down Expand Up @@ -3770,6 +3780,7 @@
09EA564B27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift in Sources */,
CE227093228DD44C00C0626C /* ProductStatus.swift in Sources */,
451A97E9260B657D0059D135 /* ShippingLabelPredefinedOption.swift in Sources */,
263659DC2A264A3E00607A0D /* IPLocationRemote.swift in Sources */,
02C2548425635BD000A04423 /* ShippingLabelPaperSize.swift in Sources */,
CE132BBC223859710029DB6C /* ProductTag.swift in Sources */,
DE66C5532976508300DAA978 /* CookieNonceAuthenticator.swift in Sources */,
Expand Down Expand Up @@ -4241,6 +4252,7 @@
D8FBFF0F22D3B25E006E3336 /* WooAPIVersionTests.swift in Sources */,
45152831257A8E1A0076B03C /* ProductAttributeMapperTests.swift in Sources */,
CCA1D60A2943809700B40560 /* SiteSummaryStatsMapperTests.swift in Sources */,
263659DE2A2694A000607A0D /* IPLocationRemoteTests.swift in Sources */,
26B2F74924C55ACE0065CCC8 /* LeaderboardsRemoteTests.swift in Sources */,
45CCFCE827A2E5020012E8CB /* InboxNoteListMapperTests.swift in Sources */,
74002D6C2118B88200A63C19 /* SiteStatsRemoteTests.swift in Sources */,
Expand Down
11 changes: 1 addition & 10 deletions Networking/Networking/Model/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,14 @@ public struct Account: Decodable, Equatable, GeneratedFakeable {
///
public let gravatarUrl: String?

/// Users IP country Code
/// This setting is not stored in the Storage layer because we don't want to rely on stale value.
/// But there us no problem on add it later if we believe it will be useful.
///
public let ipCountryCode: String


/// Designated Initializer.
///
public init(userID: Int64, displayName: String, email: String, username: String, gravatarUrl: String?, ipCountryCode: String) {
public init(userID: Int64, displayName: String, email: String, username: String, gravatarUrl: String?) {
self.userID = userID
self.displayName = displayName
self.email = email
self.username = username
self.gravatarUrl = gravatarUrl
self.ipCountryCode = ipCountryCode
}
}

Expand All @@ -55,6 +47,5 @@ private extension Account {
case email = "email"
case username = "username"
case gravatarUrl = "avatar_URL"
case ipCountryCode = "user_ip_country_code"
}
}
46 changes: 46 additions & 0 deletions Networking/Networking/Remote/IPLocationRemote.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation

/// Remote type to fetch the user's IP Location using the public `geo` API.
///
public final class IPLocationRemote: Remote {

/// Fetches the country code from the device ip.
///
public func getIPCountryCode(onCompletion: @escaping (Result<String, Error>) -> Void) {
let path = "geo/" // Needs the trailing slash otherwise the request will fail.
guard let url = URL(string: Settings.wordpressApiBaseURL + path) else {
return onCompletion(.failure(IPLocationError.malformedURL)) // Should not happen.
}

let request = UnauthenticatedRequest(request: .init(url: url))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be adding the custom user-agent header with .asURLRequest() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I totally missed it!

let mapper = IPCountryCodeMapper()
enqueue(request, mapper: mapper, completion: onCompletion)
}
}

/// `IPLocationRemote` known errors
///
public extension IPLocationRemote {
enum IPLocationError: Error {
case malformedURL
}
}

/// Private mapper used to extract the country code from the `IPLocationRemote` response.
///
private struct IPCountryCodeMapper: Mapper {

/// Response envelope
///
struct Response: Decodable {
enum CodingKeys: String, CodingKey {
case countryCode = "country_short"
}

let countryCode: String
}

func map(response: Data) throws -> String {
try JSONDecoder().decode(Response.self, from: response).countryCode
}
}
6 changes: 5 additions & 1 deletion Networking/Networking/Requests/UnauthenticatedRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import protocol Alamofire.URLRequestConvertible

/// Wraps up a `URLRequestConvertible` instance, and injects the `UserAgent.defaultUserAgent`.
///
struct UnauthenticatedRequest: URLRequestConvertible {
struct UnauthenticatedRequest: Request {

/// Request that does not require WPCOM authentication.
///
Expand All @@ -19,4 +19,8 @@ struct UnauthenticatedRequest: URLRequestConvertible {

return unauthenticated
}

func responseDataValidator() -> ResponseDataValidator {
PlaceholderDataValidator()
}
}
33 changes: 33 additions & 0 deletions Networking/NetworkingTests/Remote/IPLocationRemoteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import XCTest
import TestKit
@testable import Networking


final class IPLocationRemoteTests: XCTestCase {
/// Dummy Network Wrapper
///
private var network: MockNetwork!

override func setUp() {
super.setUp()
network = MockNetwork()
}

func test_country_code_is_correctly_parsed() {
// Given
let remote = IPLocationRemote(network: network)
network.simulateResponse(requestUrlSuffix: "geo/", filename: "ip-location")

// When
let countryCode = waitFor { promise in
remote.getIPCountryCode { result in
if case let .success(code) = result {
promise(code)
}
}
}

// Then
XCTAssertEqual(countryCode, "CO")
}
}
8 changes: 8 additions & 0 deletions Networking/NetworkingTests/Responses/ip-location.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"latitude": "25.77427",
"longitude": "-80.1936",
"country_short": "CO",
"country_long": "United States of America",
"region": "Florida",
"city": "Miami"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ final class PrivacyBannerPresentationUseCase {
/// Currently it is shown if the user is in the EU zone & privacy choices have not been saved.
///
func shouldShowPrivacyBanner() async -> Bool {
// Early exit if privacy settings have been saved to prevent unnecessary API calls.
guard !defaults.hasSavedPrivacyBannerSettings else {
return false
}

do {
let countryCode = try await fetchUsersCountryCode()
let isCountryInEU = Country.GDPRCountryCodes.contains(countryCode)
let hasSavedPrivacySettings = defaults.hasSavedPrivacyBannerSettings
return isCountryInEU && !hasSavedPrivacySettings
return isCountryInEU
} catch {
DDLogInfo("⛔️ Could not determine users country code. Error: \(error)")
return false
Expand All @@ -43,41 +47,18 @@ final class PrivacyBannerPresentationUseCase {
// MARK: Private Helpers
private extension PrivacyBannerPresentationUseCase {

/// Determines the user country code by the following algorithm.
/// - If the user has a WPCOM account:
/// - Use the ip country code.
/// - If the user does not has a WPCOM account:
/// - Use the current locale country code.
/// Determines the user country. Relies on the public WordPress API.
///
func fetchUsersCountryCode() async throws -> String {
// Use ip country code for WPCom accounts
if !stores.isAuthenticatedWithoutWPCom {
return try await fetchIPCountryCode()
}

// Use locale country code as a fallback
return fetchLocaleCountryCode()
}

/// Fetches the ip country code using the Account API.
///
func fetchIPCountryCode() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
let action = AccountAction.synchronizeAccount { result in
let ipCountryCodeResult = result.map { $0.ipCountryCode }
continuation.resume(with: ipCountryCodeResult)
let action = UserAction.fetchUserIPCountryCode { result in
continuation.resume(with: result)
}
Task { @MainActor in
stores.dispatch(action)
}
}
}

/// Fetches the country code from the current locate.
///
func fetchLocaleCountryCode() -> String {
currentLocale.regionCode ?? ""
}
}

private extension UserDefaults {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ extension SessionManager {
displayName: displayName,
email: "",
username: credentials.username,
gravatarUrl: nil,
ipCountryCode: "US")
gravatarUrl: nil)
}
return manager
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ final class AdminRoleRequiredViewModelTests: XCTestCase {
case .retrieveUser(_, let onCompletion):
let user = User.fake().copy(roles: [User.Role.administrator.rawValue])
onCompletion(.success(user))
default:
break
}
}
let viewModel = AdminRoleRequiredViewModel(siteID: 123, stores: stores)
Expand All @@ -61,6 +63,8 @@ final class AdminRoleRequiredViewModelTests: XCTestCase {
case .retrieveUser(_, let onCompletion):
let user = User.fake().copy(roles: [User.Role.shopManager.rawValue])
onCompletion(.success(user))
default:
break
}
}
let viewModel = AdminRoleRequiredViewModel(siteID: 123, stores: stores)
Expand All @@ -80,6 +84,8 @@ final class AdminRoleRequiredViewModelTests: XCTestCase {
switch action {
case .retrieveUser(_, let onCompletion):
onCompletion(.failure(expectedError))
default:
break
}
}
let viewModel = AdminRoleRequiredViewModel(siteID: 123, stores: stores)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ final class JetpackSetupCoordinatorTests: XCTestCase {
let coordinator = JetpackSetupCoordinator(site: testSite, dotcomAuthScheme: expectedScheme, rootViewController: navigationController, stores: stores)
let url = try XCTUnwrap(URL(string: "scheme://magic-login?token=test"))

let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil, ipCountryCode: "US")
let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil)
stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in
switch action {
case let .loadWPComAccount(_, onCompletion):
Expand Down Expand Up @@ -133,7 +133,7 @@ final class JetpackSetupCoordinatorTests: XCTestCase {
let coordinator = JetpackSetupCoordinator(site: testSite, dotcomAuthScheme: expectedScheme, rootViewController: navigationController, stores: stores)
let url = try XCTUnwrap(URL(string: "scheme://magic-login?token=test"))

let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil, ipCountryCode: "US")
let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil)
stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in
switch action {
case let .loadWPComAccount(_, onCompletion):
Expand Down Expand Up @@ -162,7 +162,7 @@ final class JetpackSetupCoordinatorTests: XCTestCase {
let coordinator = JetpackSetupCoordinator(site: testSite, dotcomAuthScheme: expectedScheme, rootViewController: navigationController, stores: stores)
let url = try XCTUnwrap(URL(string: "scheme://magic-login?token=test"))

let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil, ipCountryCode: "US")
let expectedAccount = Account(userID: 123, displayName: "Test", email: "[email protected]", username: "test", gravatarUrl: nil)
stores.whenReceivingAction(ofType: JetpackConnectionAction.self) { action in
switch action {
case let .loadWPComAccount(_, onCompletion):
Expand Down
Loading