diff --git a/StatsigInjections/StatsigInjections/StatsigInjections.swift b/StatsigInjections/StatsigInjections/StatsigInjections.swift index 4126d322..f603222d 100644 --- a/StatsigInjections/StatsigInjections/StatsigInjections.swift +++ b/StatsigInjections/StatsigInjections/StatsigInjections.swift @@ -13,6 +13,14 @@ import Combine private let apiKey: String private let environment: StatsigEnvironment + // ensures feature flag values stay constant throughout the app session after they are used the first time, even if they are initialized to null + private var sessionValues = [String: Availabilty]() + + private enum Availabilty { + case available(Bool) + // unavailable is the case for a new feature flag, or a first launch of the app with Statsig + case unavailable + } public enum Environment { case production @@ -67,7 +75,6 @@ import Combine if let error { Console.shared.log("Statsig feature flags failed to initialize: \(error)") } - self?.newValuesSubject.send() // intentionally not calling completion here since we do not want ff init to be blocking startup // this may change if we need FF pre-launch // completion() @@ -75,14 +82,30 @@ import Combine } completion() } - - private let newValuesSubject = PassthroughSubject() - public var newValuesAvailablePublisher: AnyPublisher { - newValuesSubject.eraseToAnyPublisher() - } + // https://docs.statsig.com/sdk/debugging public func isOn(feature: String) -> Bool? { - Statsig.checkGate(feature) + let featureGate = Statsig.getFeatureGate(feature) + let availability: Availabilty + if let existingAvailability = sessionValues[feature] { + // a featuregate value has already been set for this session + availability = existingAvailability + } else if featureGate.evaluationDetails.reason == .Recognized { + // a featuregate will be recognized if the feature gate has been fetched in previous Statsig inits (cache is non-empty) + availability = .available(featureGate.value) + } else { + // a featuregate will be unrecognized if the feature gate is new or this is first time initializing Statsig (cache would be empty) + availability = .unavailable + } + sessionValues[feature] = availability + + switch availability { + case .available(let bool): + return bool + case .unavailable: + // defer to calling context for default value + return nil + } } public func value(feature: String) -> String? { diff --git a/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift b/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift index f0d14e59..b3dfa05f 100644 --- a/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift +++ b/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift @@ -12,12 +12,6 @@ import Combine public class CompositeFeatureFlagsProvider: NSObject & FeatureFlagsProtocol { public var local: FeatureFlagsProtocol? public var remote: FeatureFlagsProtocol? - - public var newValuesAvailablePublisher: AnyPublisher { - Publishers.Merge(local?.newValuesAvailablePublisher ?? Just(()).eraseToAnyPublisher(), - remote?.newValuesAvailablePublisher ?? Just(()).eraseToAnyPublisher()) - .eraseToAnyPublisher() - } public func refresh(completion: @escaping () -> Void) { if let local = local { diff --git a/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift b/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift index edf6fe21..6027f10e 100644 --- a/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift +++ b/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift @@ -17,15 +17,6 @@ public protocol FeatureFlagsProtocol { func isOn(feature: String) -> Bool? func customized() -> Bool - - var newValuesAvailablePublisher: AnyPublisher { get } -} - -// default impl -extension FeatureFlagsProtocol { - public var newValuesAvailablePublisher: AnyPublisher { - return Just(()).eraseToAnyPublisher() - } } public class FeatureService { diff --git a/dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift b/dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift index c5132abc..1130ebfd 100644 --- a/dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift +++ b/dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift @@ -10,18 +10,28 @@ import Foundation import Utilities public enum dydxBoolFeatureFlag: String, CaseIterable { - case push_notification case force_mainnet case enable_app_rating case shouldUseSkip = "ff_skip_migration" + var defaultValue: Bool { + switch self { + case .force_mainnet: + return false + case .enable_app_rating: + return true + case .shouldUseSkip: + return true + } + } + private static let obj = NSObject() public var isEnabled: Bool { if FeatureService.shared == nil { Console.shared.log("WARNING: FeatureService not yet set up.") } - return FeatureService.shared?.isOn(feature: rawValue) == true + return FeatureService.shared?.isOn(feature: rawValue) ?? defaultValue } public static var enabledFlags: [String] { diff --git a/dydx/dydxStateManager/dydxStateManager/AbacusStateManager.swift b/dydx/dydxStateManager/dydxStateManager/AbacusStateManager.swift index 5645cdef..9c19fed7 100644 --- a/dydx/dydxStateManager/dydxStateManager/AbacusStateManager.swift +++ b/dydx/dydxStateManager/dydxStateManager/AbacusStateManager.swift @@ -158,6 +158,7 @@ public final class AbacusStateManager: NSObject { appConfigs.onboardingConfigs.squidVersion = OnboardingConfigs.SquidVersion.v2 appConfigs.onboardingConfigs.alchemyApiKey = CredientialConfig.shared.credential(for: "alchemyApiKey") + StatsigConfig.shared.useSkip = dydxBoolFeatureFlag.shouldUseSkip.isEnabled return AsyncAbacusStateManagerV2( deploymentUri: deploymentUri, @@ -177,8 +178,6 @@ public final class AbacusStateManager: NSObject { _ = CosmoJavascript.shared _ = foregroundToken _ = backgroundToken - - startListeningForFeatureFlagChanges() } private func start() { @@ -207,14 +206,6 @@ public final class AbacusStateManager: NSObject { isStarted = true } - private func startListeningForFeatureFlagChanges() { - FeatureService.shared?.newValuesAvailablePublisher - .sink { - StatsigConfig.shared.useSkip = dydxBoolFeatureFlag.shouldUseSkip.isEnabled - } - .store(in: &cancellables) - } - public func setMarket(market: String?) { asyncStateManager.market = market }