diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index 09782c406c..3a64ce6042 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -18,8 +18,18 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02hhx", $0) }.joined() - ChatModel.shared.deviceToken = token logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)") + ChatModel.shared.deviceToken = token + let useNotifications = UserDefaults.standard.bool(forKey: "useNotifications") + if useNotifications { + Task { + do { + try await apiRegisterToken(token: token) + } catch { + logger.error("apiRegisterToken error: \(responseError(error))") + } + } + } } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { @@ -30,12 +40,24 @@ class AppDelegate: NSObject, UIApplicationDelegate { didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { logger.debug("AppDelegate: didReceiveRemoteNotification") - print(userInfo) - if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any] { - if let verification = ntfData["verification"] as? String { - logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)") - // TODO send to chat - completionHandler(.newData) + if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any], + UserDefaults.standard.bool(forKey: "useNotifications") { + if let verification = ntfData["verification"] as? String, + let nonce = ntfData["nonce"] as? String { + if let token = ChatModel.shared.deviceToken { + logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)") + Task { + do { + try await apiVerifyToken(token: token, code: verification, nonce: nonce) + try await apiIntervalNofication(token: token, interval: 20) + } catch { + logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))") + } + completionHandler(.newData) + } + } else { + completionHandler(.noData) + } } else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages { // TODO check if app in background logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages") @@ -44,7 +66,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { // TODO check if app in background logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessage \(smpQueue)") receiveMessages(completionHandler) + } else { + completionHandler(.noData) } + } else { + completionHandler(.noData) } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 3cace7634d..4bf008600a 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -26,7 +26,9 @@ enum ChatCommand { case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent) case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) case apiRegisterToken(token: String) - case apiVerifyToken(token: String, code: String) + case apiVerifyToken(token: String, code: String, nonce: String) + case apiIntervalNofication(token: String, interval: Int) + case apiDeleteToken(token: String) case getUserSMPServers case setUserSMPServers(smpServers: [String]) case addContact @@ -61,8 +63,10 @@ enum ChatCommand { } case let .apiUpdateChatItem(type, id, itemId, mc): return "/_update item \(ref(type, id)) \(itemId) \(mc.cmdString)" case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)" - case let .apiRegisterToken(token): return "/_ntf register apn \(token)" - case let .apiVerifyToken(token, code): return "/_ntf verify apn \(token) \(code)" + case let .apiRegisterToken(token): return "/_ntf register apns \(token)" + case let .apiVerifyToken(token, code, nonce): return "/_ntf verify apns \(token) \(code) \(nonce)" + case let .apiIntervalNofication(token, interval): return "/_ntf interval apns \(token) \(interval)" + case let .apiDeleteToken(token): return "/_ntf delete apns \(token)" case .getUserSMPServers: return "/smp_servers" case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))" case .addContact: return "/connect" @@ -96,6 +100,8 @@ enum ChatCommand { case .apiDeleteChatItem: return "apiDeleteChatItem" case .apiRegisterToken: return "apiRegisterToken" case .apiVerifyToken: return "apiRegisterToken" + case .apiIntervalNofication: return "apiIntervalNofication" + case .apiDeleteToken: return "apiDeleteToken" case .getUserSMPServers: return "getUserSMPServers" case .setUserSMPServers: return "setUserSMPServers" case .addContact: return "addContact" @@ -451,15 +457,19 @@ func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteM } func apiRegisterToken(token: String) async throws { - let r = await chatSendCmd(.apiRegisterToken(token: token)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiRegisterToken(token: token)) } -func apiVerifyToken(token: String, code: String) async throws { - let r = await chatSendCmd(.apiVerifyToken(token: token, code: code)) - if case .cmdOk = r { return } - throw r +func apiVerifyToken(token: String, code: String, nonce: String) async throws { + try await sendCommandOkResp(.apiVerifyToken(token: token, code: code, nonce: nonce)) +} + +func apiIntervalNofication(token: String, interval: Int) async throws { + try await sendCommandOkResp(.apiIntervalNofication(token: token, interval: interval)) +} + +func apiDeleteToken(token: String) async throws { + try await sendCommandOkResp(.apiDeleteToken(token: token)) } func getUserSMPServers() throws -> [String] { @@ -469,9 +479,7 @@ func getUserSMPServers() throws -> [String] { } func setUserSMPServers(smpServers: [String]) async throws { - let r = await chatSendCmd(.setUserSMPServers(smpServers: smpServers)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.setUserSMPServers(smpServers: smpServers)) } func apiAddContact() throws -> String { @@ -577,9 +585,7 @@ func apiRejectContactRequest(contactReqId: Int64) async throws { } func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws { - let r = await chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) } func receiveFile(fileId: Int64) async throws { @@ -628,6 +634,20 @@ func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { } } +private func sendCommandOkResp(_ cmd: ChatCommand) async throws { + let r = await chatSendCmd(cmd) + if case .cmdOk = r { return } + throw r +} + +func responseError(_ err: Error) -> String { + if let r = err as? ChatResponse { + return String(describing: r) + } else { + return err.localizedDescription + } +} + func initializeChat() { do { ChatModel.shared.currentUser = try apiGetActiveUser() diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 933f5f95bd..aa00ca8375 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -18,6 +18,9 @@ struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var chatModel: ChatModel @Binding var showSettings: Bool + @AppStorage("useNotifications") private var useNotifications: Bool = false + @State var showNotificationsAlert: Bool = false + @State var whichNotificationsAlert = NotificationAlert.enable var body: some View { let user: User = chatModel.currentUser! @@ -128,12 +131,86 @@ struct SettingsView: View { .padding(.trailing, 8) Text("Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)") } + if let token = chatModel.deviceToken { + HStack { + Image(systemName: "bolt.fill") + .padding(.trailing, 4) + NotificationsToggle(token) + } + } Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") } } .navigationTitle("Your settings") } } + + enum NotificationAlert { + case enable + case error(LocalizedStringKey, String) + } + + private func NotificationsToggle(_ token: String) -> some View { + Toggle("Check messages", isOn: $useNotifications) + .onChange(of: useNotifications) { enable in + if enable { + showNotificationsAlert = true + whichNotificationsAlert = .enable + } else { + Task { + do { + try await apiDeleteToken(token: token) + } + catch { + DispatchQueue.main.async { + if let cr = error as? ChatResponse { + let err = String(describing: cr) + logger.error("apiDeleteToken error: \(err)") + showNotificationsAlert = true + whichNotificationsAlert = .error("Error deleting token", err) + } else { + logger.error("apiDeleteToken unknown error: \(error.localizedDescription)") + } + } + } + } + } + } + .alert(isPresented: $showNotificationsAlert) { + switch (whichNotificationsAlert) { + case .enable: return enableNotificationsAlert(token) + case let .error(title, err): return Alert(title: Text(title), message: Text(err)) + } + } + } + + private func enableNotificationsAlert(_ token: String) -> Alert { + Alert( + title: Text("Enable notifications? (BETA)"), + message: Text("The app can receive background notifications every 20 minutes to check the new messages.\n*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server."), + primaryButton: .destructive(Text("Confirm")) { + Task { + do { + try await apiRegisterToken(token: token) + } catch { + DispatchQueue.main.async { + useNotifications = false + if let cr = error as? ChatResponse { + let err = String(describing: cr) + logger.error("apiRegisterToken error: \(err)") + showNotificationsAlert = true + whichNotificationsAlert = .error("Error registering token", err) + } else { + logger.error("apiRegisterToken unknown error: \(error.localizedDescription)") + } + } + } + } + }, secondaryButton: .cancel() { + withAnimation() { useNotifications = false } + } + ) + } } struct SettingsView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index bdd35577d8..d8ee08ef96 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -18,17 +18,15 @@ 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E261127A30FEA00F70299 /* TerminalView.swift */; }; 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; }; 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; - 5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; }; - 5C36027427F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; }; 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; }; 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; }; 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; }; - 5C545C81280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C7C280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a */; }; - 5C545C82280D7A7E007A6B96 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C7D280D7A7E007A6B96 /* libffi.a */; }; - 5C545C83280D7A7E007A6B96 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C7E280D7A7E007A6B96 /* libgmp.a */; }; - 5C545C84280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C7F280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a */; }; - 5C545C85280D7A7E007A6B96 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C80280D7A7E007A6B96 /* libgmpxx.a */; }; + 5C545C8C2812A834007A6B96 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C872812A833007A6B96 /* libgmpxx.a */; }; + 5C545C8D2812A834007A6B96 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C882812A833007A6B96 /* libgmp.a */; }; + 5C545C8E2812A834007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C892812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a */; }; + 5C545C8F2812A834007A6B96 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C8A2812A833007A6B96 /* libffi.a */; }; + 5C545C902812A834007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C545C8B2812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a */; }; 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; }; 5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6C27EBC3FE006A9D5F /* ImagePicker.swift */; }; 5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */; }; @@ -102,11 +100,11 @@ 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = ""; }; 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = ""; }; - 5C545C7C280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a"; sourceTree = ""; }; - 5C545C7D280D7A7E007A6B96 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C545C7E280D7A7E007A6B96 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C545C7F280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a"; sourceTree = ""; }; - 5C545C80280D7A7E007A6B96 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C545C872812A833007A6B96 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C545C882812A833007A6B96 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C545C892812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a"; sourceTree = ""; }; + 5C545C8A2812A833007A6B96 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C545C8B2812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a"; sourceTree = ""; }; 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = ""; }; 5C5F2B6C27EBC3FE006A9D5F /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImage.swift; sourceTree = ""; }; @@ -161,14 +159,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5C545C8C2812A834007A6B96 /* libgmpxx.a in Frameworks */, + 5C545C8D2812A834007A6B96 /* libgmp.a in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, - 5C545C81280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a in Frameworks */, - 5C545C82280D7A7E007A6B96 /* libffi.a in Frameworks */, - 5C545C85280D7A7E007A6B96 /* libgmpxx.a in Frameworks */, - 5C545C84280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a in Frameworks */, - 5C545C83280D7A7E007A6B96 /* libgmp.a in Frameworks */, + 5C545C8F2812A834007A6B96 /* libffi.a in Frameworks */, + 5C545C8E2812A834007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, + 5C545C902812A834007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -213,11 +211,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C545C7D280D7A7E007A6B96 /* libffi.a */, - 5C545C7E280D7A7E007A6B96 /* libgmp.a */, - 5C545C80280D7A7E007A6B96 /* libgmpxx.a */, - 5C545C7F280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q-ghc8.10.7.a */, - 5C545C7C280D7A7E007A6B96 /* libHSsimplex-chat-1.6.0-JmSjOVFru1I9XqltphBD8q.a */, + 5C545C8A2812A833007A6B96 /* libffi.a */, + 5C545C882812A833007A6B96 /* libgmp.a */, + 5C545C872812A833007A6B96 /* libgmpxx.a */, + 5C545C8B2812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK-ghc8.10.7.a */, + 5C545C892812A833007A6B96 /* libHSsimplex-chat-1.6.0-IWA13KO27d7KsfAsFQIqFK.a */, ); path = Libraries; sourceTree = ""; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 06f99671da..a5bae03eee 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -54,6 +54,7 @@ import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Notifications.Client (NtfServer) import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), PushProvider (..)) import Simplex.Messaging.Parsers (base64P, parseAll) import Simplex.Messaging.Protocol (ErrorType (..), MsgBody) @@ -97,6 +98,9 @@ defaultSMPServers = "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im" ] +defaultNtfServers :: [NtfServer] +defaultNtfServers = ["smp://ZH1Dkt2_EQRbxUUyjLlcUjg1KAhBrqfvE0xfn7Ki0Zg=@ntf1.simplex.im"] + logCfg :: LogConfig logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} @@ -109,7 +113,7 @@ newChatController chatStore user cfg@ChatConfig {agentConfig = aCfg, tbqSize} Ch firstTime <- not <$> doesFileExist f currentUser <- newTVarIO user initialSMPServers <- resolveServers - let servers = InitialAgentServers {smp = initialSMPServers, ntf = ["smp://smAc80rtvJKA02nysCCmiDzMUmcGnYA3gujwKU1NT30=@127.0.0.1:443"]} + let servers = InitialAgentServers {smp = initialSMPServers, ntf = defaultNtfServers} smpAgent <- getSMPAgentClient aCfg {dbFile = dbFilePrefix <> "_agent.db"} servers agentAsync <- newTVarIO Nothing idsDrg <- newTVarIO =<< drgNew