This can be forced on or off using the new RemoteConfig setting: `ios.useLibsignalForChat`. The change will take place the *next* time the app is launched, since the prior value of these settings (including the default, previously always false) is recorded in UserDefaults for use during app startup.
344 lines
12 KiB
Swift
344 lines
12 KiB
Swift
//
|
|
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum FeatureBuild: Int {
|
|
case dev
|
|
case `internal`
|
|
case beta
|
|
case production
|
|
}
|
|
|
|
private extension FeatureBuild {
|
|
func includes(_ level: FeatureBuild) -> Bool {
|
|
return self.rawValue <= level.rawValue
|
|
}
|
|
}
|
|
|
|
private let build = FeatureBuild.current
|
|
|
|
// MARK: -
|
|
|
|
/// By centralizing feature flags here and documenting their rollout plan,
|
|
/// it's easier to review which feature flags are in play.
|
|
public enum FeatureFlags {
|
|
|
|
public static let choochoo = build.includes(.internal)
|
|
|
|
public static let failDebug = build.includes(.internal)
|
|
|
|
public static let linkedPhones = build.includes(.internal)
|
|
|
|
public static let preRegDeviceTransfer = build.includes(.dev)
|
|
|
|
public static let isPrerelease = build.includes(.beta)
|
|
|
|
public static let shouldUseTestIntervals = build.includes(.beta)
|
|
|
|
/// If we ever need to internally detect database corruption again in the
|
|
/// future, we can re-enable this.
|
|
public static let periodicallyCheckDatabaseIntegrity: Bool = false
|
|
|
|
public static let messageBackupErrorDisplay = build.includes(.internal)
|
|
public static let messageBackupFileAlpha = build.includes(.internal)
|
|
public static let messageBackupRemoteExportAlpha = build.includes(.dev)
|
|
public static let messageBackupFileAlphaRegistrationFlow = build.includes(.dev)
|
|
public static let messageBackupQuickRestoreFlow = false
|
|
public static let messageBackupRestoreFailOnAnyError = build.includes(.beta)
|
|
public static let messageBackupDetailedBenchLogging = build.includes(.internal)
|
|
|
|
public static let linkAndSyncPrimaryExport = true
|
|
public static let linkAndSyncLinkedImport = build.includes(.beta)
|
|
|
|
public static let storageServiceRecordIkmMigration = true
|
|
|
|
public static let runTSAttachmentMigrationInMainAppBackground = true
|
|
public static let runTSAttachmentMigrationBlockingOnLaunch = false
|
|
|
|
public static let useNewConversationLoadIndex = build.includes(.beta)
|
|
|
|
public static let enableAccountEntropyPool = build.includes(.dev)
|
|
|
|
public static let libsignalForChat = build.includes(.beta)
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension FeatureFlags {
|
|
public static var buildVariantString: String? {
|
|
// Leaving this internal only for now. If we ever move this to
|
|
// HelpSettings we need to localize these strings
|
|
guard DebugFlags.internalSettings else {
|
|
owsFailDebug("Incomplete implementation. Needs localization")
|
|
return nil
|
|
}
|
|
|
|
let featureFlagString: String?
|
|
switch build {
|
|
case .dev:
|
|
featureFlagString = LocalizationNotNeeded("Dev")
|
|
case .internal:
|
|
featureFlagString = LocalizationNotNeeded("Internal")
|
|
case .beta:
|
|
featureFlagString = LocalizationNotNeeded("Beta")
|
|
case .production:
|
|
// Production can be inferred from the lack of flag
|
|
featureFlagString = nil
|
|
}
|
|
|
|
let configuration: String? = {
|
|
#if DEBUG
|
|
LocalizationNotNeeded("Debug")
|
|
#elseif TESTABLE_BUILD
|
|
LocalizationNotNeeded("Testable")
|
|
#else
|
|
// RELEASE can be inferred from the lack of configuration. This will only be hit if the outer #if is removed.
|
|
nil
|
|
#endif
|
|
}()
|
|
|
|
// If we're Production+Release, this will return nil and won't show up in Help Settings
|
|
return [featureFlagString, configuration]
|
|
.compactMap { $0 }
|
|
.joined(separator: " — ")
|
|
.nilIfEmpty
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
/// Flags that we'll leave in the code base indefinitely that are helpful for
|
|
/// development should go here, rather than cluttering up FeatureFlags.
|
|
public enum DebugFlags {
|
|
public static let internalLogging = build.includes(.internal)
|
|
|
|
public static let betaLogging = build.includes(.beta)
|
|
|
|
public static let testPopulationErrorAlerts = build.includes(.beta)
|
|
|
|
public static let audibleErrorLogging = build.includes(.internal)
|
|
|
|
public static let internalSettings = build.includes(.internal)
|
|
|
|
public static let internalMegaphoneEligible = build.includes(.internal)
|
|
|
|
public static let aggressiveProfileFetching = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Aggressive profile fetching"),
|
|
details: LocalizationNotNeeded("Client will update profiles aggressively.")
|
|
)
|
|
|
|
// Currently this flag is only honored by NetworkManager,
|
|
// but we could eventually honor in other places as well:
|
|
//
|
|
// * The socket manager.
|
|
// * Places we make requests using tasks.
|
|
public static let logCurlOnSuccess = false
|
|
|
|
public static let verboseNotificationLogging = build.includes(.internal)
|
|
|
|
public static let deviceTransferVerboseProgressLogging = build.includes(.internal)
|
|
|
|
public static let messageDetailsExtraInfo = build.includes(.internal)
|
|
|
|
public static let exposeCensorshipCircumvention = build.includes(.internal)
|
|
|
|
public static let dontSendContactOrGroupSyncMessages = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Don't send contact or group sync messages"),
|
|
details: LocalizationNotNeeded("Client will not send contact or group info to linked devices.")
|
|
)
|
|
|
|
public static let forceAttachmentDownloadFailures = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Force attachment download failures."),
|
|
details: LocalizationNotNeeded("All attachment downloads will fail.")
|
|
)
|
|
|
|
public static let forceAttachmentDownloadPendingMessageRequest = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Attachment download vs. message request."),
|
|
details: LocalizationNotNeeded("Attachment downloads will be blocked by pending message request.")
|
|
)
|
|
|
|
public static let forceAttachmentDownloadPendingManualDownload = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Attachment download vs. manual download."),
|
|
details: LocalizationNotNeeded("Attachment downloads will be blocked by manual download.")
|
|
)
|
|
|
|
public static let extraDebugLogs = build.includes(.internal)
|
|
|
|
public static let paymentsIgnoreCurrencyConversions = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Ignore currency conversions"),
|
|
details: LocalizationNotNeeded("App will behave as though currency conversions are unavailable")
|
|
)
|
|
|
|
public static let paymentsHaltProcessing = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Halt Processing"),
|
|
details: LocalizationNotNeeded("Processing of payments will pause")
|
|
)
|
|
|
|
public static let paymentsIgnoreBadData = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Ignore bad data"),
|
|
details: LocalizationNotNeeded("App will skip asserts for invalid data")
|
|
)
|
|
|
|
public static let paymentsFailOutgoingSubmission = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Fail outgoing submission"),
|
|
details: LocalizationNotNeeded("Submission of outgoing transactions will always fail")
|
|
)
|
|
|
|
public static let paymentsFailOutgoingVerification = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Fail outgoing verification"),
|
|
details: LocalizationNotNeeded("Verification of outgoing transactions will always fail")
|
|
)
|
|
|
|
public static let paymentsFailIncomingVerification = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Fail incoming verification"),
|
|
details: LocalizationNotNeeded("Verification of incoming receipts will always fail")
|
|
)
|
|
|
|
public static let paymentsDoubleNotify = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Double notify"),
|
|
details: LocalizationNotNeeded("App will send two payment notifications and sync messages for each outgoing payment")
|
|
)
|
|
|
|
public static let paymentsNoRequestsComplete = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: No requests complete"),
|
|
details: LocalizationNotNeeded("MC SDK network activity never completes")
|
|
)
|
|
|
|
public static let paymentsMalformedMessages = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Malformed messages"),
|
|
details: LocalizationNotNeeded("Payment notifications and sync messages are malformed.")
|
|
)
|
|
|
|
public static let paymentsSkipSubmissionAndOutgoingVerification = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Payments: Skip Submission And Verification"),
|
|
details: LocalizationNotNeeded("Outgoing payments won't be submitted or verified.")
|
|
)
|
|
|
|
public static let messageSendsFail = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Message Sends Fail"),
|
|
details: LocalizationNotNeeded("All outgoing message sends will fail.")
|
|
)
|
|
|
|
public static let disableUD = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Disable sealed sender"),
|
|
details: LocalizationNotNeeded("Sealed sender will be disabled for all messages.")
|
|
)
|
|
|
|
public static let callingUseTestSFU = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Calling: Use Test SFU"),
|
|
details: LocalizationNotNeeded("Group calls will connect to sfu.test.voip.signal.org.")
|
|
)
|
|
|
|
public static let delayedMessageResend = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Sender Key: Delayed message resend"),
|
|
details: LocalizationNotNeeded("Waits 10s before responding to a resend request.")
|
|
)
|
|
|
|
public static let fastPlaceholderExpiration = TestableFlag(
|
|
false,
|
|
title: LocalizationNotNeeded("Sender Key: Early placeholder expiration"),
|
|
details: LocalizationNotNeeded("Shortens the valid window for message resend+recovery."),
|
|
toggleHandler: { _ in
|
|
SSKEnvironment.shared.messageDecrypterRef.cleanUpExpiredPlaceholders()
|
|
}
|
|
)
|
|
|
|
public static func allTestableFlags() -> [TestableFlag] {
|
|
return [
|
|
aggressiveProfileFetching,
|
|
callingUseTestSFU,
|
|
delayedMessageResend,
|
|
disableUD,
|
|
dontSendContactOrGroupSyncMessages,
|
|
fastPlaceholderExpiration,
|
|
forceAttachmentDownloadFailures,
|
|
forceAttachmentDownloadPendingManualDownload,
|
|
forceAttachmentDownloadPendingMessageRequest,
|
|
messageSendsFail,
|
|
paymentsDoubleNotify,
|
|
paymentsFailIncomingVerification,
|
|
paymentsFailOutgoingSubmission,
|
|
paymentsFailOutgoingVerification,
|
|
paymentsHaltProcessing,
|
|
paymentsIgnoreBadData,
|
|
paymentsIgnoreCurrencyConversions,
|
|
paymentsMalformedMessages,
|
|
paymentsNoRequestsComplete,
|
|
paymentsSkipSubmissionAndOutgoingVerification,
|
|
]
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public class TestableFlag {
|
|
private let defaultValue: Bool
|
|
private let flag: AtomicBool
|
|
public let title: String
|
|
public let details: String
|
|
public let toggleHandler: ((Bool) -> Void)?
|
|
|
|
fileprivate init(_ defaultValue: Bool,
|
|
title: String,
|
|
details: String,
|
|
toggleHandler: ((Bool) -> Void)? = nil) {
|
|
self.defaultValue = defaultValue
|
|
self.title = title
|
|
self.details = details
|
|
self.flag = AtomicBool(defaultValue, lock: .sharedGlobal)
|
|
self.toggleHandler = toggleHandler
|
|
|
|
// Normally we'd store the observer here and remove it in deinit.
|
|
// But TestableFlags are always static; they don't *get* deinitialized except in testing.
|
|
NotificationCenter.default.addObserver(forName: Self.ResetAllTestableFlagsNotification,
|
|
object: nil, queue: nil) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
self.set(self.defaultValue)
|
|
}
|
|
}
|
|
|
|
public func get() -> Bool {
|
|
guard build.includes(.internal) else {
|
|
return defaultValue
|
|
}
|
|
return flag.get()
|
|
}
|
|
|
|
public func set(_ value: Bool) {
|
|
flag.set(value)
|
|
|
|
toggleHandler?(value)
|
|
}
|
|
|
|
@objc
|
|
private func switchDidChange(_ sender: UISwitch) {
|
|
set(sender.isOn)
|
|
}
|
|
|
|
public var switchSelector: Selector { #selector(switchDidChange(_:)) }
|
|
|
|
public static let ResetAllTestableFlagsNotification = NSNotification.Name("ResetAllTestableFlags")
|
|
}
|