import Foundation enum DefaultContainer { private static let credentials: any CredentialStore = FileCredentialStore( location: documentFileURL("credentials.json") ) private static let logStore: any LogStore = FileLogStore( location: documentFileURL("log-center.json") ) /// The phone's relay identity, shared by every relay transport. /// /// It is loaded once rather than per transport: the key pair must be the same one the node /// paired with, and a second load that regenerated it would silently invalidate every pairing. static let deviceIdentities = FileDeviceIdentityStore( location: documentFileURL("device-identity.json") ) static func documentFileURL(_ name: String) -> URL { let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] return directory.appendingPathComponent(name) } static func makeInstanceRepository() -> FileInstanceRepository { FileInstanceRepository(location: documentFileURL("instances.json")) } /// The transport pair every feature service uses: direct on the LAN, relay through the agent. /// /// The relay side reads the identity file per request rather than holding a copy: pairing can /// happen after a transport is built, and a held copy would sign with keys the node has never /// seen, which fails as an unreadable request with no useful cause. private static func makeTransport( client: any SimAdminClientProtocol, sessions: InstanceSessionStore ) -> NodeTransportRouter { let identities = deviceIdentities return NodeTransportRouter( direct: DirectTransport(client: client, sessions: sessions, credentials: credentials), relay: RelayTransport(identity: { try identities.loadOrCreate() }) ) } @MainActor static func makeFleetModel() -> FleetViewModel { purgeLegacyGrouping() let sessions = InstanceSessionStore() return FleetViewModel( repository: makeInstanceRepository(), probe: ConnectionProbe(transport: makeTransport(client: SimAdminClient(), sessions: sessions)), credentials: credentials, deviceService: DeviceService( transport: makeTransport(client: SimAdminClient(), sessions: sessions) ) ) } /// 0.2.2 起分组被标签取代,遗留的分组文件与偏好键一次性清掉,避免 Documents 里留死数据。 private static func purgeLegacyGrouping() { try? FileManager.default.removeItem(at: documentFileURL("instance-groups.json")) UserDefaults.standard.removeObject(forKey: "fleet.groupMode") } @MainActor static func makeMessagesModel() -> MessagesViewModel { let sessions = InstanceSessionStore() return MessagesViewModel( repository: makeInstanceRepository(), service: MessageService( transport: makeTransport(client: SimAdminClient(), sessions: sessions) ) ) } @MainActor static func makeLogsModel(autoRefreshOnAppear: Bool = true) -> LogsViewModel { let store = logStore let sessions = InstanceSessionStore() return LogsViewModel( repository: makeInstanceRepository(), service: LogCenterService( transport: makeTransport(client: SimAdminClient(), sessions: sessions), store: store ), store: store, autoRefreshOnAppear: autoRefreshOnAppear ) } @MainActor static func makeSettingsModel() -> SettingsViewModel { SettingsViewModel(store: logStore) } @MainActor static func makeDeviceModel(instance: Instance) -> DeviceViewModel { let sessions = InstanceSessionStore() return DeviceViewModel( instance: instance, service: DeviceService( transport: makeTransport(client: SimAdminClient(), sessions: sessions) ), probe: ConnectionProbe( transport: makeTransport(client: SimAdminClient(), sessions: sessions) ) ) } }