Skip to content

Commit

Permalink
KM-4537: Migrate feature flags api to native (#47)
Browse files Browse the repository at this point in the history
* KM-4537: Migrate feature flags API to native

* KM-4537: Migrate feature flags API to native
  • Loading branch information
kp-laura-sempere authored Jul 3, 2024
1 parent 0bfa741 commit 0f0daed
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public class AccountFactory {
updateAccountUseCase: makeUpdateAccountUseCase(),
paymentUseCase: makePaymentUseCase(),
subscriptionsUseCase: makeSubscriptionsUseCase(),
deleteAccountUseCase: makeDeleteAccountUseCase()
deleteAccountUseCase: makeDeleteAccountUseCase(),
featureFlagsUseCase: makeFeatureFlagsUseCase()
)

}
Expand All @@ -70,6 +71,10 @@ public class AccountFactory {
refreshAuthTokensCheckerShared
}

static func makeFeatureFlagsUseCase() -> FeatureFlagsUseCaseType {
FeatureFlagsUseCase(networkClient: NetworkRequestFactory.maketNetworkRequestClient(), refreshAuthTokensChecker: makeRefreshAuthTokensChecker())
}

public static func makeClientStatusUseCase() -> ClientStatusUseCaseType {
ClientStatusUseCase(networkClient: NetworkRequestFactory.maketNetworkRequestClient(), refreshAuthTokensChecker: makeRefreshAuthTokensChecker(), clientStatusDecoder: makeClientStatusInfoDecoder())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

import Foundation
import NWHttpConnection

struct FeatureFlagsRequestConfiguration: NetworkRequestConfigurationType {

let networkRequestModule: NetworkRequestModule = .account
let path: RequestAPI.Path = .iosFeatureFlag
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .get
let contentType: NetworkRequestContentType = .json
let inlcudeAuthHeaders: Bool = false
let urlQueryParameters: [String : String]? = nil
let responseDataType: NWDataResponseType = .jsonData

var otherHeaders: [String : String]? = nil
var body: Data? = nil

let timeout: TimeInterval = 10
let requestQueue: DispatchQueue? = DispatchQueue(label: "feature_flags_request.queue")
}



19 changes: 12 additions & 7 deletions Sources/PIALibrary/Account/DefaultAccountProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
private let paymentUseCase: PaymentUseCaseType
private let subscriptionsUseCase: SubscriptionsUseCaseType
private let deleteAccountUseCase: DeleteAccountUseCaseType
private let featureFlagsUseCase: FeatureFlagsUseCaseType


init(webServices: WebServices? = nil, logoutUseCase: LogoutUseCaseType, loginUseCase: LoginUseCaseType, signupUseCase: SignupUseCaseType, apiTokenProvider: APITokenProviderType, vpnTokenProvider: VpnTokenProviderType, accountDetailsUseCase: AccountDetailsUseCaseType, updateAccountUseCase: UpdateAccountUseCaseType, paymentUseCase: PaymentUseCaseType, subscriptionsUseCase: SubscriptionsUseCaseType, deleteAccountUseCase: DeleteAccountUseCaseType) {
init(webServices: WebServices? = nil, logoutUseCase: LogoutUseCaseType, loginUseCase: LoginUseCaseType, signupUseCase: SignupUseCaseType, apiTokenProvider: APITokenProviderType, vpnTokenProvider: VpnTokenProviderType, accountDetailsUseCase: AccountDetailsUseCaseType, updateAccountUseCase: UpdateAccountUseCaseType, paymentUseCase: PaymentUseCaseType, subscriptionsUseCase: SubscriptionsUseCaseType, deleteAccountUseCase: DeleteAccountUseCaseType, featureFlagsUseCase: FeatureFlagsUseCaseType) {
self.logoutUseCase = logoutUseCase
self.loginUseCase = loginUseCase
self.signupUseCase = signupUseCase
Expand All @@ -54,6 +55,7 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
self.paymentUseCase = paymentUseCase
self.subscriptionsUseCase = subscriptionsUseCase
self.deleteAccountUseCase = deleteAccountUseCase
self.featureFlagsUseCase = featureFlagsUseCase
if let webServices = webServices {
customWebServices = webServices
} else {
Expand Down Expand Up @@ -437,13 +439,16 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
}

public func featureFlags(_ callback: SuccessLibraryCallback?) {
webServices.featureFlags { (features, nil) in
Client.configuration.featureFlags.removeAll()
if let features = features, !features.isEmpty {
Client.configuration.featureFlags.append(contentsOf: features)
featureFlagsUseCase() { result in
switch result {
case .failure(let error):
callback?(error.asClientError())
case .success(let featuresInfo):
Client.configuration.featureFlags.removeAll()
Client.configuration.featureFlags.append(contentsOf: featuresInfo.flags)
Macros.postNotification(Notification.Name.__AppDidFetchFeatureFlags)
callback?(nil)
}
Macros.postNotification(Notification.Name.__AppDidFetchFeatureFlags)
callback?(nil)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import Foundation


struct FeatureFlagsInformation: Codable {
let flags: [String]
}

extension FeatureFlagsInformation {
static func makeWith(data: Data) -> FeatureFlagsInformation? {
try? JSONDecoder().decode(FeatureFlagsInformation.self, from: data)
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

import Foundation


protocol FeatureFlagsUseCaseType {
typealias Completion = ((Result<FeatureFlagsInformation, NetworkRequestError>) -> Void)
func callAsFunction(completion: @escaping Completion)
}


class FeatureFlagsUseCase: FeatureFlagsUseCaseType {

let networkClient: NetworkRequestClientType
let refreshAuthTokensChecker: RefreshAuthTokensCheckerType

init(networkClient: NetworkRequestClientType, refreshAuthTokensChecker: RefreshAuthTokensCheckerType) {
self.networkClient = networkClient
self.refreshAuthTokensChecker = refreshAuthTokensChecker
}

func callAsFunction(completion: @escaping Completion) {
// The API auth token is not required in the feature flags request
// so refreshing the tokens can happen in parallel
refreshAuthTokensChecker.refreshIfNeeded { _ in }

// Get feature flags request
executeRequest(with: completion)
}
}

private extension FeatureFlagsUseCase {

func executeRequest(with completion: @escaping Completion) {
let configuration = FeatureFlagsRequestConfiguration()
networkClient.executeRequest(with: configuration) { error, dataResponse in
if let error {
completion(.failure(error))
} else {
guard let data = dataResponse?.data else {
completion(.failure(.noDataContent))
return
}

guard let flagsInfo = FeatureFlagsInformation.makeWith(data: data) else {
completion(.failure(.unableToDecodeData))
return
}

completion(.success(flagsInfo))
}
}
}

}
10 changes: 0 additions & 10 deletions Sources/PIALibrary/WebServices/PIAWebServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,6 @@ class PIAWebServices: WebServices, ConfigurationAccess {
}
}

func featureFlags(_ callback: LibraryCallback<[String]>?) {
self.accountAPI.featureFlags { (info, errors) in
if let flags = info?.flags {
callback?(flags, nil)
} else {
callback?([], ClientError.malformedResponseData)
}
}
}

#if os(iOS) || os(tvOS)
func signup(with request: Signup, _ callback: ((Credentials?, Error?) -> Void)?) {
var marketingJSON = ""
Expand Down
3 changes: 1 addition & 2 deletions Sources/PIALibrary/WebServices/WebServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,5 @@ protocol WebServices: class {
func taskForConnectivityCheck(_ callback: LibraryCallback<ConnectivityStatus>?)

func submitDebugReport(_ shouldSendPersistedData: Bool, _ protocolLogs: String, _ callback: LibraryCallback<String>?)

func featureFlags(_ callback: LibraryCallback<[String]>?)

}
Loading

0 comments on commit 0f0daed

Please sign in to comment.