Signal-iOS/SignalServiceKit/src/Util/FeatureFlags.swift
2020-06-07 14:53:55 -03:00

340 lines
9.9 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
enum FeatureBuild: Int {
case dev
case internalPreview
case qa
case beta
case production
}
extension FeatureBuild {
func includes(_ level: FeatureBuild) -> Bool {
return self.rawValue <= level.rawValue
}
}
let build: FeatureBuild = OWSIsDebugBuild() ? .dev : .beta
// MARK: -
@objc
public enum StorageMode: Int {
// Only use YDB. This should be used in production until we ship
// the YDB-to-GRDB migration.
case ydbForAll
// Use GRDB, migrating if possible on every launch.
// If no YDB database exists, a throwaway db is not used.
//
// Supercedes grdbMigratesFreshDBEveryLaunch.
//
// TODO: Remove.
case grdbThrowawayIfMigrating
// Use GRDB under certain conditions.
//
// TODO: Remove.
case grdbForAlreadyMigrated
case grdbForLegacyUsersOnly
case grdbForNewUsersOnly
// Use GRDB, migrating once if necessary.
case grdbForAll
// These modes can be used while running tests.
// They are more permissive than the release modes.
//
// The build shepherd should be running the test
// suites in .ydbTests and .grdbTests modes before each release.
case ydbTests
case grdbTests
}
// MARK: -
extension StorageMode: CustomStringConvertible {
public var description: String {
switch self {
case .ydbForAll:
return ".ydbForAll"
case .grdbThrowawayIfMigrating:
return ".grdbThrowawayIfMigrating"
case .grdbForAlreadyMigrated:
return ".grdbForAlreadyMigrated"
case .grdbForLegacyUsersOnly:
return ".grdbForLegacyUsersOnly"
case .grdbForNewUsersOnly:
return ".grdbForNewUsersOnly"
case .grdbForAll:
return ".grdbForAll"
case .ydbTests:
return ".ydbTests"
case .grdbTests:
return ".grdbTests"
}
}
}
// MARK: -
@objc
public enum StorageModeStrictness: Int {
// For DEBUG, QA and beta builds only.
case fail
// For production
case failDebug
// Temporary value to be used until existing issues are resolved.
case log
}
// MARK: -
/// By centralizing feature flags here and documenting their rollout plan, it's easier to review
/// which feature flags are in play.
@objc(SSKFeatureFlags)
public class FeatureFlags: NSObject {
@objc
public static var storageMode: StorageMode {
if CurrentAppContext().isRunningTests {
// We should be running the tests using both .ydbTests or .grdbTests.
return .grdbTests
} else {
return .grdbForAll
}
}
// Don't enable this flag in production.
// At least, not yet.
@objc
public static var storageModeStrictness: StorageModeStrictness {
return build.includes(.beta) ? .fail : .failDebug
}
@objc
public static let uuidCapabilities = allowUUIDOnlyContacts && useOnlyModernContactDiscovery
@objc
public static var storageModeDescription: String {
return "\(storageMode)"
}
@objc
public static let stickerSearch = false
@objc
public static let stickerPackOrdering = false
// Don't enable this flag until the Desktop changes have been in production for a while.
@objc
public static let strictSyncTranscriptTimestamps = false
// Don't enable this flag in production.
@objc
public static let strictYDBExtensions = build.includes(.beta)
@objc
public static var allowUUIDOnlyContacts = useOnlyModernContactDiscovery
@objc
public static var uuidSafetyNumbers = allowUUIDOnlyContacts
@objc
public static let useOnlyModernContactDiscovery = false
@objc
public static let compareLegacyContactDiscoveryAgainstModern = !isUsingProductionService
@objc
public static let phoneNumberPrivacy = false
@objc
public static let socialGraphOnServer = false
@objc
public static let complainAboutSlowDBWrites = true
@objc
public static let usernames = allowUUIDOnlyContacts && build.includes(.dev)
@objc
public static let messageRequest = build.includes(.dev) && socialGraphOnServer
@objc
public static let profileDisplayChanges = build.includes(.dev)
@objc
public static var calling: Bool {
return answerCallsOnSecondaryDevice || TSAccountManager.sharedInstance().isRegisteredPrimaryDevice
}
// We can't enable this in production until sending calls to all devices has
// rolled out on iOS and Android.
//
// Note: As well as exposing incoming calls on iPads, this also exposes the outgoing call button
// on iPads, because we don't want to allow outgoing calls from an iPad until we're also ready
// to receive them on iPad.
@objc
public static let answerCallsOnSecondaryDevice: Bool = build.includes(.beta)
@objc
public static let groupsV2 = build.includes(.qa) && !isUsingProductionService
// Don't consult this feature flag directly; instead
// consult RemoteConfig.groupsV2CreateGroups.
@objc
public static let groupsV2CreateGroups = groupsV2
// Don't consult this feature flag directly; instead
// consult RemoteConfig.groupsV2IncomingMessages.
@objc
public static let groupsV2IncomingMessages = groupsV2
// The other clients don't consider this MVP, but we already implemented it.
// It enables an optimization where other clients can usually update without
// interacting with the service.
//
// GroupsV2 TODO: Decide whether or not to set this flag.
@objc
public static let groupsV2embedProtosInGroupUpdates = false
@objc
public static let groupsV2processProtosInGroupUpdates = false
// Don't consult this feature flag directly; instead
// consult RemoteConfig.groupsV2SetCapability.
@objc
public static let groupsV2SetCapability = groupsV2
@objc
public static var groupsV2reapplyCurrentRevision = false
@objc
public static let linkedPhones = build.includes(.internalPreview)
@objc
public static let isUsingProductionService = true
@objc
public static let versionedProfiledFetches = groupsV2
// When we activate this feature flag, we also need to ensure that all
// users update their profile once in a durable way.
@objc
public static let versionedProfiledUpdate = groupsV2
@objc
public static let useOrphanDataCleaner = true
@objc
public static let sendRecipientUpdates = false
@objc
public static let notificationServiceExtension = build.includes(.dev)
@objc
public static let pinsForNewUsers = true
@objc
public static let deviceTransferDestroyOldDevice = true
@objc
public static let deviceTransferThrowAway = false
}
/// Flags that we'll leave in the code base indefinitely that are helpful for
/// development should go here, rather than cluttering up FeatureFlags.
@objc(SSKDebugFlags)
public class DebugFlags: NSObject {
// DEBUG builds won't receive push notifications, which prevents receiving messages
// while the app is backgrounded or the system call screen is active.
//
// Set this flag to true to be able to download messages even when the app is in the background.
@objc
public static let keepWebSocketOpenInBackground = false
@objc
public static var audibleErrorLogging = build.includes(.qa)
@objc
public static let verboseAboutView = build.includes(.qa)
// This can be used to shut down various background operations.
@objc
public static let suppressBackgroundActivity = false
@objc
public static let logSQLQueries = build.includes(.dev)
@objc
public static let groupsV2IgnoreCapability = false
// We can use this to test recovery from "missed updates".
@objc
public static let groupsV2dontSendUpdates = false
@objc
public static let groupsV2showV2Indicator = FeatureFlags.groupsV2 && build.includes(.qa)
// If set, v2 groups will be created and updated with invalid avatars
// so that we can test clients' robustness to this case.
@objc
public static let groupsV2corruptAvatarUrlPaths = false
// If set, v2 groups will be created and updated with
// corrupt avatars, group names, and/or dm state
// so that we can test clients' robustness to this case.
@objc
public static let groupsV2corruptBlobEncryption = false
// This flag auto-enables the groupv2 flags in RemoteConfig.
@objc
public static let groupsV2IgnoreServerFlags = FeatureFlags.groupsV2
// If set, this will invite instead of adding other users.
@objc
public static let groupsV2forceInvites = false
@objc
public static var groupsV2memberStatusIndicators = FeatureFlags.groupsV2 && build.includes(.qa)
@objc
public static var groupsV2editMemberAccess = build.includes(.qa)
@objc
public static let isMessageProcessingVerbose = false
// Currently this flag is only honored by TSNetworkManager,
// but we could eventually honor in other places as well:
//
// * The socket manager.
// * Places we make requests using tasks.
@objc
public static let logCurlOnSuccess = false
// Our "group update" info messages should be robust to
// various situations that shouldn't occur in production,
// bug we want to be able to test them using the debug UI.
@objc
public static let permissiveGroupUpdateInfoMessages = build.includes(.dev)
@objc
public static let showProfileKeyAndUuidsIndicator = build.includes(.qa)
@objc
public static let verboseNotificationLogging = build.includes(.qa)
@objc
public static let verboseSignalRecipientLogging = build.includes(.qa)
@objc
public static let shouldMergeUserProfiles = build.includes(.qa)
@objc
public static let deviceTransferVerboseProgressLogging = build.includes(.qa)
@objc
public static let forceMessageRequests = build.includes(.qa) && FeatureFlags.groupsV2
}