Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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)).asURLRequest()
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