| 1 | import Foundation |
| 2 | |
| 3 | // π€ Where "home" is. Production (https://home.elevenmessenger.com) is the only |
| 4 | // answer a Release build can give β the override below compiles out entirely β |
| 5 | // but Debug builds (device installs, simulators, Mac dev alts) may aim at the |
| 6 | // dev clone (https://home.dev.elevenmessenger.com) so native sign-in and vault |
| 7 | // sync can be exercised without touching real accounts. Resolution order: |
| 8 | // |
| 9 | // 1. LCHAT_HOME_BASE in the process environment (an Xcode scheme, a |
| 10 | // simctl/devicectl launch, a shell `open` of a Mac build), then |
| 11 | // 2. the `home_base_override` string in standard UserDefaults β on Mac alts |
| 12 | // that's per-bundle-id, which is exactly right: each alt keeps its own |
| 13 | // aim (`defaults write com.elevenmessenger.messenger.mac.alt-<slug> |
| 14 | // home_base_override https://home.dev.elevenmessenger.com`), then |
| 15 | // 3. production. |
| 16 | // |
| 17 | // Resolved ONCE per launch so every surface β HomeClient, the ceremony's rpID, |
| 18 | // the browser-handoff links, HomeSessionStore's scoping β agrees for the whole |
| 19 | // run; changing the override means relaunching (matching how the ClickLab |
| 20 | // default is read). |
| 21 | public enum HomeBase { |
| 22 | public static let production = URL(string: "https://home.elevenmessenger.com")! |
| 23 | |
| 24 | /// The resolved base every home surface builds URLs from. |
| 25 | public static let url: URL = resolve() |
| 26 | |
| 27 | /// The WebAuthn relying-party id β the base's host, which is what the |
| 28 | /// production default always was. An overridden base's ceremonies bind to |
| 29 | /// ITS host (the dev clone serves its own AASA; the Debug entitlements |
| 30 | /// carry the extra webcredentials association). |
| 31 | public static var rpID: String { url.host ?? "home.elevenmessenger.com" } |
| 32 | |
| 33 | /// A per-base suffix for scoping stored home state ("" on production, so |
| 34 | /// existing sessions keep their account name). The dev home has entirely |
| 35 | /// separate accounts, so a session minted against one base must never be |
| 36 | /// replayed against another β HomeSessionStore appends this to its |
| 37 | /// Keychain account to keep them apart. |
| 38 | public static var storageSuffix: String { |
| 39 | guard url != production else { return "" } |
| 40 | let host = url.host ?? "?" |
| 41 | return url.port.map { "#\(host):\($0)" } ?? "#\(host)" |
| 42 | } |
| 43 | |
| 44 | private static func resolve() -> URL { |
| 45 | #if DEBUG |
| 46 | if let url = normalize(ProcessInfo.processInfo.environment["LCHAT_HOME_BASE"]) { |
| 47 | return url |
| 48 | } |
| 49 | if let url = normalize(UserDefaults.standard.string(forKey: "home_base_override")) { |
| 50 | return url |
| 51 | } |
| 52 | #endif |
| 53 | return production |
| 54 | } |
| 55 | |
| 56 | #if DEBUG |
| 57 | /// Accept a full URL or a bare host (https assumed); reject anything a |
| 58 | /// URLRequest couldn't use, so a typo'd default falls back to production |
| 59 | /// rather than wedging every home call. |
| 60 | private static func normalize(_ raw: String?) -> URL? { |
| 61 | guard let raw else { return nil } |
| 62 | let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) |
| 63 | guard !trimmed.isEmpty else { return nil } |
| 64 | let withScheme = trimmed.contains("://") ? trimmed : "https://" + trimmed |
| 65 | guard let url = URL(string: withScheme), url.host != nil else { return nil } |
| 66 | return url |
| 67 | } |
| 68 | #endif |
| 69 | } |
| 70 | |