mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Hardcode the sliding sync proxy. (#502)
* Add support for migrating between versions. * Rename InfoPlistReader property. * Bump SDK version.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
import Version
|
||||
|
||||
class AppCoordinator: AppCoordinatorProtocol {
|
||||
private let stateMachine: AppCoordinatorStateMachine
|
||||
@@ -66,13 +67,18 @@ class AppCoordinator: AppCoordinatorProtocol {
|
||||
|
||||
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
|
||||
|
||||
// Reset everything if the app has been deleted since the previous run
|
||||
if !ServiceLocator.shared.settings.hasAppLaunchedOnce {
|
||||
AppSettings.reset()
|
||||
userSessionStore.reset()
|
||||
ServiceLocator.shared.settings.hasAppLaunchedOnce = true
|
||||
guard let currentVersion = Version(InfoPlistReader(bundle: .main).bundleShortVersionString) else {
|
||||
fatalError("The app's version number **must** use semver for migration purposes.")
|
||||
}
|
||||
|
||||
if let previousVersion = ServiceLocator.shared.settings.lastVersionLaunched.flatMap(Version.init) {
|
||||
performMigrationsIfNecessary(from: previousVersion, to: currentVersion)
|
||||
} else {
|
||||
// The app has been deleted since the previous run. Reset everything.
|
||||
wipeUserData(includingSettings: true)
|
||||
}
|
||||
ServiceLocator.shared.settings.lastVersionLaunched = currentVersion.description
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
Bundle.elementFallbackLanguage = "en"
|
||||
@@ -126,6 +132,27 @@ class AppCoordinator: AppCoordinatorProtocol {
|
||||
MXLog.configure(loggerConfiguration)
|
||||
}
|
||||
|
||||
/// Perform any required migrations for the app to function correctly.
|
||||
private func performMigrationsIfNecessary(from oldVersion: Version, to newVersion: Version) {
|
||||
guard oldVersion != newVersion else { return }
|
||||
|
||||
if oldVersion < Version(1, 0, 17) {
|
||||
// Version 1.0.17 hardcoded a new sliding sync proxy for matrix.org
|
||||
// Force a sign out for the user to log in with the new proxy.
|
||||
MXLog.warning("Clearing user data for hardcoded proxy.")
|
||||
wipeUserData()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the keychain, app support directory etc ready for a fresh use.
|
||||
/// - Parameter includingSettings: Whether to additionally wipe the user's app settings too.
|
||||
private func wipeUserData(includingSettings: Bool = false) {
|
||||
if includingSettings {
|
||||
AppSettings.reset()
|
||||
}
|
||||
userSessionStore.reset()
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func setupStateMachine() {
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
|
||||
@@ -20,7 +20,7 @@ import SwiftUI
|
||||
/// Store Element specific app settings.
|
||||
final class AppSettings: ObservableObject {
|
||||
private enum UserDefaultsKeys: String {
|
||||
case hasAppLaunchedOnce
|
||||
case lastVersionLaunched
|
||||
case timelineStyle
|
||||
case enableAnalytics
|
||||
case isIdentifiedForAnalytics
|
||||
@@ -29,7 +29,7 @@ final class AppSettings: ObservableObject {
|
||||
case pusherProfileTag
|
||||
}
|
||||
|
||||
private static var suiteName: String = InfoPlistReader.target.appGroupIdentifier
|
||||
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||
|
||||
/// UserDefaults to be used on reads and writes.
|
||||
private static var store: UserDefaults! = UserDefaults(suiteName: suiteName)
|
||||
@@ -50,20 +50,27 @@ final class AppSettings: ObservableObject {
|
||||
|
||||
// MARK: - Application
|
||||
|
||||
/// Simple flag to check if app has been deleted between runs.
|
||||
/// Used to clear data stored in the shared container and keychain
|
||||
@AppStorage(UserDefaultsKeys.hasAppLaunchedOnce.rawValue, store: store)
|
||||
var hasAppLaunchedOnce = false
|
||||
/// The last known version of the app that was launched on this device, which is
|
||||
/// used to detect when migrations should be run. When `nil` the app may have been
|
||||
/// deleted between runs so should clear data in the shared container and keychain.
|
||||
@AppStorage(UserDefaultsKeys.lastVersionLaunched.rawValue, store: store)
|
||||
var lastVersionLaunched: String?
|
||||
|
||||
/// The default homeserver address used. This is intentionally a string without a scheme
|
||||
/// so that it can be passed to Rust as a ServerName for well-known discovery.
|
||||
let defaultHomeserverAddress = "matrix.org"
|
||||
|
||||
/// An override of the homeserver's Sliding Sync proxy URL. This allows development against servers
|
||||
/// that don't yet have an officially trusted proxy configured in their well-known.
|
||||
let slidingSyncProxyURL = URL(staticString: "https://slidingsync.lab.matrix.org")
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
var pusherAppId: String {
|
||||
#if DEBUG
|
||||
InfoPlistReader.target.baseBundleIdentifier + ".ios.dev"
|
||||
InfoPlistReader.main.baseBundleIdentifier + ".ios.dev"
|
||||
#else
|
||||
InfoPlistReader.target.baseBundleIdentifier + ".ios.prod"
|
||||
InfoPlistReader.main.baseBundleIdentifier + ".ios.prod"
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -84,14 +91,14 @@ final class AppSettings: ObservableObject {
|
||||
#if DEBUG
|
||||
/// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds.
|
||||
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
|
||||
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"),
|
||||
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.main.bundleIdentifier.starts(with: "io.element.elementx"),
|
||||
host: "https://posthog.element.dev",
|
||||
apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
|
||||
termsURL: URL(staticString: "https://element.io/cookie-policy"))
|
||||
#else
|
||||
/// The configuration to use for analytics. Set `isEnabled` to false to disable analytics.
|
||||
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
|
||||
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"),
|
||||
let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.main.bundleIdentifier.starts(with: "io.element.elementx"),
|
||||
host: "https://posthog.hss.element.io",
|
||||
apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||
termsURL: URL(staticString: "https://element.io/cookie-policy"))
|
||||
@@ -117,11 +124,6 @@ final class AppSettings: ObservableObject {
|
||||
@AppStorage(UserDefaultsKeys.timelineStyle.rawValue, store: store)
|
||||
var timelineStyle = TimelineStyle.bubbles
|
||||
|
||||
// MARK: - Client
|
||||
|
||||
@AppStorage(UserDefaultsKeys.slidingSyncProxyBaseURLString.rawValue, store: store)
|
||||
var slidingSyncProxyBaseURLString = "https://slidingsync.lab.element.dev"
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@AppStorage(UserDefaultsKeys.enableInAppNotifications.rawValue, store: store)
|
||||
|
||||
@@ -23,6 +23,6 @@ extension UIDevice {
|
||||
}
|
||||
|
||||
var initialDisplayName: String {
|
||||
ElementL10n.defaultSessionDisplayName(InfoPlistReader.target.bundleDisplayName)
|
||||
ElementL10n.defaultSessionDisplayName(InfoPlistReader.main.bundleDisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ extension URL {
|
||||
|
||||
/// The URL of the primary app group container.
|
||||
static var appGroupContainerDirectory: URL {
|
||||
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: InfoPlistReader.target.appGroupIdentifier) else {
|
||||
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: InfoPlistReader.main.appGroupIdentifier) else {
|
||||
fatalError("Should always be able to retrieve the container directory")
|
||||
}
|
||||
return url
|
||||
@@ -64,7 +64,7 @@ extension URL {
|
||||
var url = appGroupContainerDirectory
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Application Support", isDirectory: true)
|
||||
.appendingPathComponent(InfoPlistReader.target.baseBundleIdentifier, isDirectory: true)
|
||||
.appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true)
|
||||
|
||||
try? FileManager.default.createDirectoryIfNeeded(at: url)
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ struct InfoPlistReader {
|
||||
static let bundleDisplayName = "CFBundleDisplayName"
|
||||
}
|
||||
|
||||
/// Info.plist reader on the current target
|
||||
static let target = InfoPlistReader(bundle: .main)
|
||||
/// Info.plist reader on the bundle object that contains the current executable.
|
||||
static let main = InfoPlistReader(bundle: .main)
|
||||
|
||||
private let bundle: Bundle
|
||||
|
||||
|
||||
@@ -150,9 +150,9 @@ class MXLogger {
|
||||
MXLogger.logCrashes(false)
|
||||
|
||||
// Extract running app information
|
||||
let app = InfoPlistReader.target.bundleExecutable
|
||||
let appId = InfoPlistReader.target.bundleIdentifier
|
||||
let appVersion = "\(InfoPlistReader.target.bundleShortVersionString) (r\(InfoPlistReader.target.bundleVersion))"
|
||||
let app = InfoPlistReader.main.bundleExecutable
|
||||
let appId = InfoPlistReader.main.bundleIdentifier
|
||||
let appVersion = "\(InfoPlistReader.main.bundleShortVersionString) (r\(InfoPlistReader.main.bundleVersion))"
|
||||
|
||||
// Build the crash log
|
||||
let model = UIDevice.current.model
|
||||
|
||||
@@ -27,8 +27,8 @@ final class UserAgentBuilder {
|
||||
}
|
||||
|
||||
private class func makeUserAgent() -> String? {
|
||||
let clientName = InfoPlistReader.target.bundleDisplayName
|
||||
let clientVersion = InfoPlistReader.target.bundleShortVersionString
|
||||
let clientName = InfoPlistReader.main.bundleDisplayName
|
||||
let clientVersion = InfoPlistReader.main.bundleShortVersionString
|
||||
|
||||
#if os(iOS)
|
||||
return String(
|
||||
|
||||
@@ -44,10 +44,10 @@ struct AnalyticsPromptStrings {
|
||||
init() {
|
||||
// Create the opt in content with a placeholder.
|
||||
let linkPlaceholder = "{link}"
|
||||
var optInContent = AttributedString(ElementL10n.analyticsOptInContent(InfoPlistReader.target.bundleDisplayName, linkPlaceholder))
|
||||
var optInContent = AttributedString(ElementL10n.analyticsOptInContent(InfoPlistReader.main.bundleDisplayName, linkPlaceholder))
|
||||
|
||||
guard let range = optInContent.range(of: linkPlaceholder) else {
|
||||
self.optInContent = AttributedString(ElementL10n.analyticsOptInContent(InfoPlistReader.target.bundleDisplayName,
|
||||
self.optInContent = AttributedString(ElementL10n.analyticsOptInContent(InfoPlistReader.main.bundleDisplayName,
|
||||
ElementL10n.analyticsOptInContentLink))
|
||||
MXLog.failure("Failed to add a link attribute to the opt in content.")
|
||||
return
|
||||
|
||||
@@ -59,7 +59,7 @@ struct AnalyticsPrompt: View {
|
||||
Image(uiImage: Asset.Images.analyticsLogo.image)
|
||||
.padding(.bottom, 25)
|
||||
|
||||
Text(ElementL10n.analyticsOptInTitle(InfoPlistReader.target.bundleDisplayName))
|
||||
Text(ElementL10n.analyticsOptInTitle(InfoPlistReader.main.bundleDisplayName))
|
||||
.font(.element.title2Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
|
||||
@@ -64,6 +64,7 @@ struct LoginScreen: View {
|
||||
LoginServerInfoSection(address: context.viewState.homeserver.address) {
|
||||
context.send(viewAction: .selectServer)
|
||||
}
|
||||
.disabled(true) // The button is disabled for this demo.
|
||||
}
|
||||
|
||||
/// The form with text fields for username and password, along with a submit button.
|
||||
|
||||
@@ -48,6 +48,7 @@ struct LoginServerInfoSection: View {
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(.element.tertiaryContent)
|
||||
.padding(.trailing, 16)
|
||||
.hidden() // The button is disabled for this demo.
|
||||
}
|
||||
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system))
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ struct ServerSelectionViewState: BindableState {
|
||||
struct ServerSelectionBindings {
|
||||
/// The homeserver address input by the user.
|
||||
var homeserverAddress: String
|
||||
/// The sliding sync proxy address input by the user.
|
||||
var slidingSyncProxyAddress: String
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<ServerSelectionErrorType>?
|
||||
}
|
||||
|
||||
+1
-7
@@ -22,8 +22,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
|
||||
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)?
|
||||
|
||||
init(homeserverAddress: String, isModallyPresented: Bool) {
|
||||
let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress,
|
||||
slidingSyncProxyAddress: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString)
|
||||
let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress)
|
||||
|
||||
super.init(initialViewState: ServerSelectionViewState(bindings: bindings,
|
||||
isModallyPresented: isModallyPresented))
|
||||
@@ -32,11 +31,6 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
|
||||
override func process(viewAction: ServerSelectionViewAction) async {
|
||||
switch viewAction {
|
||||
case .confirm:
|
||||
if !state.bindings.slidingSyncProxyAddress.isEmpty,
|
||||
state.bindings.slidingSyncProxyAddress != ServiceLocator.shared.settings.slidingSyncProxyBaseURLString {
|
||||
ServiceLocator.shared.settings.slidingSyncProxyBaseURLString = state.bindings.slidingSyncProxyAddress
|
||||
}
|
||||
|
||||
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
|
||||
case .dismiss:
|
||||
callback?(.dismiss)
|
||||
|
||||
-10
@@ -71,16 +71,6 @@ struct ServerSelectionScreen: View {
|
||||
.onSubmit(submit)
|
||||
.accessibilityIdentifier("addressTextField")
|
||||
|
||||
TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.slidingSyncProxyAddress)
|
||||
.textFieldStyle(.elementInput(labelText: "Sliding sync proxy URL"))
|
||||
.keyboardType(.URL)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.submitLabel(.done)
|
||||
.onSubmit(submit)
|
||||
.accessibilityIdentifier("slidingSyncProxyAddressTextField")
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Button(action: submit) {
|
||||
Text(context.viewState.buttonTitle)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ struct OnboardingViewState: BindableState {
|
||||
init() {
|
||||
content = [
|
||||
OnboardingPageContent(title: ElementL10n.ftueAuthCarouselWelcomeTitle.tinting(".", color: .element.accent),
|
||||
message: ElementL10n.ftueAuthCarouselWelcomeBody(InfoPlistReader.target.bundleDisplayName),
|
||||
message: ElementL10n.ftueAuthCarouselWelcomeBody(InfoPlistReader.main.bundleDisplayName),
|
||||
image: Asset.Images.onboardingAppLogo)
|
||||
]
|
||||
bindings = OnboardingBindings()
|
||||
|
||||
@@ -23,7 +23,7 @@ struct InviteFriendsCoordinator: CoordinatorProtocol {
|
||||
guard let permalink = try? PermalinkBuilder.permalinkTo(userIdentifier: userId).absoluteString else {
|
||||
return AnyView(EmptyView())
|
||||
}
|
||||
let shareText = ElementL10n.inviteFriendsText(InfoPlistReader.target.bundleDisplayName, permalink)
|
||||
let shareText = ElementL10n.inviteFriendsText(InfoPlistReader.main.bundleDisplayName, permalink)
|
||||
|
||||
return AnyView(UIActivityViewControllerWrapper(activityItems: [shareText])
|
||||
.presentationDetents([.medium])
|
||||
|
||||
@@ -57,7 +57,7 @@ struct SettingsScreen: View {
|
||||
}
|
||||
|
||||
private var versionText: some View {
|
||||
Text(ElementL10n.settingsVersion + ": " + InfoPlistReader.target.bundleShortVersionString + " (" + InfoPlistReader.target.bundleVersion + ")")
|
||||
Text(ElementL10n.settingsVersion + ": " + InfoPlistReader.main.bundleShortVersionString + " (" + InfoPlistReader.main.bundleVersion + ")")
|
||||
}
|
||||
|
||||
private var backgroundColor: Color {
|
||||
|
||||
@@ -61,7 +61,7 @@ class BugReportService: BugReportServiceProtocol {
|
||||
// also enable logging crashes, to send them with bug reports
|
||||
MXLogger.logCrashes(true)
|
||||
// set build version for logger
|
||||
MXLogger.buildVersion = InfoPlistReader.target.bundleShortVersionString
|
||||
MXLogger.buildVersion = InfoPlistReader.main.bundleShortVersionString
|
||||
}
|
||||
|
||||
// MARK: - BugReportServiceProtocol
|
||||
@@ -148,8 +148,8 @@ class BugReportService: BugReportServiceProtocol {
|
||||
return [
|
||||
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
||||
MultipartFormData(key: "app", type: .text(value: applicationId)),
|
||||
MultipartFormData(key: "version", type: .text(value: InfoPlistReader.target.bundleShortVersionString)),
|
||||
MultipartFormData(key: "build", type: .text(value: InfoPlistReader.target.bundleVersion)),
|
||||
MultipartFormData(key: "version", type: .text(value: InfoPlistReader.main.bundleShortVersionString)),
|
||||
MultipartFormData(key: "build", type: .text(value: InfoPlistReader.main.bundleVersion)),
|
||||
MultipartFormData(key: "os", type: .text(value: os)),
|
||||
MultipartFormData(key: "resolved_language", type: .text(value: Bundle.preferredLanguages[0])),
|
||||
MultipartFormData(key: "user_language", type: .text(value: Bundle.elementLanguage ?? "null")),
|
||||
|
||||
@@ -254,7 +254,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
|
||||
do {
|
||||
let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyBaseURLString)
|
||||
let slidingSyncBuilder = try client.slidingSync().homeserver(url: ServiceLocator.shared.settings.slidingSyncProxyURL.absoluteString)
|
||||
|
||||
// Build the visibleRoomsSlidingSyncView here so that it can take advantage of the SS builder cold cache
|
||||
// We will still register the allRoomsSlidingSyncView later, and than will have no cache
|
||||
|
||||
@@ -22,7 +22,7 @@ enum KeychainControllerService: String {
|
||||
case tests
|
||||
|
||||
var identifier: String {
|
||||
InfoPlistReader.target.baseBundleIdentifier + "." + rawValue
|
||||
InfoPlistReader.main.baseBundleIdentifier + "." + rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(),
|
||||
kind: .http,
|
||||
appId: ServiceLocator.shared.settings.pusherAppId,
|
||||
appDisplayName: "\(InfoPlistReader.target.bundleDisplayName) (iOS)",
|
||||
appDisplayName: "\(InfoPlistReader.main.bundleDisplayName) (iOS)",
|
||||
deviceDisplayName: UIDevice.current.name,
|
||||
profileTag: pusherProfileTag(),
|
||||
lang: Bundle.preferredLanguages.first ?? "en",
|
||||
|
||||
@@ -30,7 +30,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
|
||||
init(backgroundTaskService: BackgroundTaskServiceProtocol) {
|
||||
keychainController = KeychainController(service: .sessions,
|
||||
accessGroup: InfoPlistReader.target.keychainAccessGroupIdentifier)
|
||||
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
baseDirectory = .sessionsBaseDirectory
|
||||
MXLog.info("Setup base directory at: \(baseDirectory)")
|
||||
|
||||
@@ -115,6 +115,7 @@ targets:
|
||||
- package: DesignKit
|
||||
- package: AnalyticsEvents
|
||||
- package: AppAuth
|
||||
- package: Collections
|
||||
- package: DeviceKit
|
||||
- package: DTCoreText
|
||||
- package: KeychainAccess
|
||||
@@ -124,7 +125,7 @@ targets:
|
||||
- package: SwiftState
|
||||
- package: GZIP
|
||||
- package: Sentry
|
||||
- package: Collections
|
||||
- package: Version
|
||||
|
||||
sources:
|
||||
- path: ../Sources
|
||||
|
||||
Reference in New Issue
Block a user