feat: Ipad app production readiness, Colony orchestration, Social posting (#44)
#38 Ipad app production readiness, Colony orchestration, Social posting Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #44
This commit was merged in pull request #44.
This commit is contained in:
@@ -2,8 +2,7 @@ import Foundation
|
||||
import Security
|
||||
|
||||
/// Central app configuration.
|
||||
/// Build settings remain the fallback, but production installs should prefer
|
||||
/// runtime configuration stored on-device.
|
||||
/// Enterprise installs must use runtime configuration stored on-device.
|
||||
enum AppConfig {
|
||||
private static let runtimeBaseURLKey = "velocity.runtime.base_url"
|
||||
private static let runtimeDreamWeaverBaseURLKey = "velocity.runtime.dream_weaver_base_url"
|
||||
@@ -42,10 +41,6 @@ enum AppConfig {
|
||||
return "Credentials required"
|
||||
}
|
||||
|
||||
private static func value(for key: String) -> String? {
|
||||
parsedValue(from: Bundle.main.infoDictionary, key: key)
|
||||
}
|
||||
|
||||
private static func sanitizedValue(_ raw: String?, key: String) -> String? {
|
||||
guard let raw else {
|
||||
return nil
|
||||
@@ -59,7 +54,7 @@ enum AppConfig {
|
||||
|
||||
/// Base URL for the Velocity backend / gateway.
|
||||
static var baseURL: String {
|
||||
runtimeBaseURL ?? value(for: "BASE_URL") ?? "https://velocity.desineuron.in/api"
|
||||
runtimeBaseURL ?? SessionConfigurationDefaults.productionBaseURL
|
||||
}
|
||||
|
||||
/// Dedicated Dream Weaver gateway endpoint when configured; otherwise
|
||||
@@ -76,19 +71,19 @@ enum AppConfig {
|
||||
}
|
||||
|
||||
static var dreamWeaverAPIKey: String? {
|
||||
runtimeDreamWeaverAPIKey ?? value(for: "DREAM_WEAVER_API_KEY")
|
||||
runtimeDreamWeaverAPIKey
|
||||
}
|
||||
|
||||
static var apiEmail: String? {
|
||||
runtimeEmail ?? value(for: "API_EMAIL")
|
||||
runtimeEmail
|
||||
}
|
||||
|
||||
static var apiPassword: String? {
|
||||
runtimePassword ?? value(for: "API_PASSWORD")
|
||||
runtimePassword
|
||||
}
|
||||
|
||||
static var apiBearerToken: String? {
|
||||
runtimeBearerToken ?? value(for: "API_BEARER_TOKEN")
|
||||
runtimeBearerToken
|
||||
}
|
||||
|
||||
static var apiAccessToken: String? {
|
||||
@@ -132,7 +127,7 @@ enum AppConfig {
|
||||
email: apiEmail,
|
||||
hasPassword: apiPassword != nil,
|
||||
hasBearerToken: apiBearerToken != nil,
|
||||
source: hasStoredRuntimeConfiguration ? .secureDeviceStorage : .buildConfiguration
|
||||
source: .secureDeviceStorage
|
||||
)
|
||||
}
|
||||
|
||||
@@ -144,23 +139,15 @@ enum AppConfig {
|
||||
password: String?,
|
||||
bearerToken: String?
|
||||
) throws {
|
||||
UserDefaults.standard.set(baseURL, forKey: runtimeBaseURLKey)
|
||||
|
||||
if let dreamWeaverBaseURL {
|
||||
UserDefaults.standard.set(dreamWeaverBaseURL, forKey: runtimeDreamWeaverBaseURLKey)
|
||||
} else {
|
||||
UserDefaults.standard.removeObject(forKey: runtimeDreamWeaverBaseURLKey)
|
||||
}
|
||||
|
||||
if let email {
|
||||
UserDefaults.standard.set(email, forKey: runtimeEmailKey)
|
||||
} else {
|
||||
UserDefaults.standard.removeObject(forKey: runtimeEmailKey)
|
||||
}
|
||||
|
||||
try storeSecret(baseURL, account: runtimeBaseURLKey)
|
||||
try storeSecret(dreamWeaverBaseURL, account: runtimeDreamWeaverBaseURLKey)
|
||||
try storeSecret(email, account: runtimeEmailKey)
|
||||
try storeSecret(dreamWeaverAPIKey, account: runtimeDreamWeaverAPIKeyKey)
|
||||
try storeSecret(password, account: runtimePasswordKey)
|
||||
try storeSecret(bearerToken, account: runtimeBearerTokenKey)
|
||||
UserDefaults.standard.removeObject(forKey: runtimeBaseURLKey)
|
||||
UserDefaults.standard.removeObject(forKey: runtimeDreamWeaverBaseURLKey)
|
||||
UserDefaults.standard.removeObject(forKey: runtimeEmailKey)
|
||||
try clearStoredAccessToken()
|
||||
}
|
||||
|
||||
@@ -168,6 +155,9 @@ enum AppConfig {
|
||||
UserDefaults.standard.removeObject(forKey: runtimeBaseURLKey)
|
||||
UserDefaults.standard.removeObject(forKey: runtimeDreamWeaverBaseURLKey)
|
||||
UserDefaults.standard.removeObject(forKey: runtimeEmailKey)
|
||||
try deleteSecret(account: runtimeBaseURLKey)
|
||||
try deleteSecret(account: runtimeDreamWeaverBaseURLKey)
|
||||
try deleteSecret(account: runtimeEmailKey)
|
||||
try deleteSecret(account: runtimeDreamWeaverAPIKeyKey)
|
||||
try deleteSecret(account: runtimePasswordKey)
|
||||
try deleteSecret(account: runtimeBearerTokenKey)
|
||||
@@ -186,16 +176,29 @@ enum AppConfig {
|
||||
}
|
||||
|
||||
private static var runtimeBaseURL: String? {
|
||||
sanitizedValue(UserDefaults.standard.string(forKey: runtimeBaseURLKey), key: runtimeBaseURLKey)
|
||||
canonicalizedBackendBaseURL(
|
||||
sanitizedValue(secret(account: runtimeBaseURLKey), key: runtimeBaseURLKey)
|
||||
)
|
||||
}
|
||||
|
||||
private static func canonicalizedBackendBaseURL(_ value: String?) -> String? {
|
||||
guard let value else {
|
||||
return nil
|
||||
}
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.caseInsensitiveCompare(SessionConfigurationDefaults.legacyVelocityWebBaseURL) == .orderedSame {
|
||||
return SessionConfigurationDefaults.productionBaseURL
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
private static var configuredDreamWeaverBaseURL: String? {
|
||||
runtimeDreamWeaverBaseURL ?? value(for: "DREAM_WEAVER_BASE_URL")
|
||||
runtimeDreamWeaverBaseURL
|
||||
}
|
||||
|
||||
private static var runtimeDreamWeaverBaseURL: String? {
|
||||
sanitizedValue(
|
||||
UserDefaults.standard.string(forKey: runtimeDreamWeaverBaseURLKey),
|
||||
secret(account: runtimeDreamWeaverBaseURLKey),
|
||||
key: runtimeDreamWeaverBaseURLKey
|
||||
)
|
||||
}
|
||||
@@ -205,7 +208,7 @@ enum AppConfig {
|
||||
}
|
||||
|
||||
private static var runtimeEmail: String? {
|
||||
sanitizedValue(UserDefaults.standard.string(forKey: runtimeEmailKey), key: runtimeEmailKey)
|
||||
sanitizedValue(secret(account: runtimeEmailKey), key: runtimeEmailKey)
|
||||
}
|
||||
|
||||
private static var runtimePassword: String? {
|
||||
|
||||
@@ -12,6 +12,12 @@ enum SessionConfigurationSource: String {
|
||||
case secureDeviceStorage = "Secure device storage"
|
||||
}
|
||||
|
||||
enum SessionConfigurationDefaults {
|
||||
static let productionBaseURL = "https://api.desineuron.in/api"
|
||||
static let legacyVelocityWebBaseURL = "https://velocity.desineuron.in/api"
|
||||
static let dreamWeaverBaseURL = "https://dreamweaver.desineuron.in"
|
||||
}
|
||||
|
||||
struct AppSessionConfiguration: Equatable {
|
||||
let baseURL: String
|
||||
let dreamWeaverBaseURL: String
|
||||
@@ -89,7 +95,13 @@ struct SessionConfigurationDraft: Equatable {
|
||||
}
|
||||
|
||||
var normalizedBaseURL: String? {
|
||||
Self.normalizedHTTPSOrigin(from: trimmedBaseURL)
|
||||
guard let normalized = Self.normalizedHTTPSOrigin(from: trimmedBaseURL) else {
|
||||
return nil
|
||||
}
|
||||
if normalized.caseInsensitiveCompare(SessionConfigurationDefaults.legacyVelocityWebBaseURL) == .orderedSame {
|
||||
return SessionConfigurationDefaults.productionBaseURL
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
var trimmedDreamWeaverBaseURL: String? {
|
||||
@@ -118,7 +130,7 @@ struct SessionConfigurationDraft: Equatable {
|
||||
}
|
||||
|
||||
guard normalizedBaseURL != nil else {
|
||||
errors.append("Backend endpoint must be an HTTPS API base like https://velocity.desineuron.in/api.")
|
||||
errors.append("Backend endpoint must be an HTTPS API base like \(SessionConfigurationDefaults.productionBaseURL).")
|
||||
return errors
|
||||
}
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ final class SessionStore {
|
||||
|
||||
func reloadFromPersistedConfiguration() {
|
||||
currentConfiguration = AppConfig.currentSessionConfiguration()
|
||||
draftBaseURL = currentConfiguration.baseURL
|
||||
draftDreamWeaverBaseURL = persistedDreamWeaverDraftValue
|
||||
draftBaseURL = trimmedNonEmpty(currentConfiguration.baseURL) ?? SessionConfigurationDefaults.productionBaseURL
|
||||
draftDreamWeaverBaseURL = trimmedNonEmpty(persistedDreamWeaverDraftValue) ?? SessionConfigurationDefaults.dreamWeaverBaseURL
|
||||
draftDreamWeaverAPIKey = ""
|
||||
draftAuthMode = currentConfiguration.authMode
|
||||
draftEmail = currentConfiguration.email ?? ""
|
||||
@@ -88,6 +88,11 @@ final class SessionStore {
|
||||
baselineEmail = currentConfiguration.email
|
||||
}
|
||||
|
||||
func markDraftEdited() {
|
||||
errorMessage = nil
|
||||
statusMessage = nil
|
||||
}
|
||||
|
||||
func discardDraftChanges() {
|
||||
errorMessage = nil
|
||||
statusMessage = nil
|
||||
@@ -185,6 +190,11 @@ final class SessionStore {
|
||||
currentConfiguration.usesDedicatedDreamWeaverBaseURL ? currentConfiguration.dreamWeaverBaseURL : ""
|
||||
}
|
||||
|
||||
private func trimmedNonEmpty(_ value: String) -> String? {
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
|
||||
private func verificationStatusMessage(
|
||||
successPrefix: String,
|
||||
backendRefreshError: String?,
|
||||
|
||||
Reference in New Issue
Block a user