TalerPOSApp.swift (5736B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2026 Bohdan, Volodymyr Potuzhnyi 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 import SwiftUI 18 19 20 @main 21 struct TalerPOSApp: App { 22 @StateObject private var configManager: ConfigManager 23 @StateObject private var orderManager: OrderManager 24 @StateObject private var paymentManager: PaymentManager 25 @StateObject private var historyManager: HistoryManager 26 @StateObject private var refundManager: RefundManager 27 @ObservedObject private var languageManager = LanguageManager.shared 28 29 init() { 30 let merchantApi = MerchantApi() 31 let config = ConfigManager(api: merchantApi) 32 let order = OrderManager() 33 let payment = PaymentManager(api: merchantApi, configManager: config) 34 let history = HistoryManager(api: merchantApi, configManager: config) 35 let refund = RefundManager(api: merchantApi, configManager: config) 36 37 config.addConfigurationReceiver(order) 38 39 _configManager = StateObject(wrappedValue: config) 40 _orderManager = StateObject(wrappedValue: order) 41 _paymentManager = StateObject(wrappedValue: payment) 42 _historyManager = StateObject(wrappedValue: history) 43 _refundManager = StateObject(wrappedValue: refund) 44 45 merchantApi.onUnauthorized = { [weak config] in 46 Task { @MainActor in 47 guard config?.merchantConfig != nil else { return } 48 config?.forgetPassword() 49 config?.sessionExpired = true 50 } 51 } 52 } 53 54 private func handleSetupUrl(_ url: URL) { 55 guard url.scheme == "taler-pos", let host = url.host, !host.isEmpty else { 56 configManager.configResult = .error("Invalid setup URL: missing scheme or host.") 57 return 58 } 59 60 let pathSegments = url.pathComponents.filter { $0 != "/" } 61 let pathStyleInstance: String? = { 62 guard pathSegments.count >= 2, 63 pathSegments[0].caseInsensitiveCompare("instances") == .orderedSame, 64 !pathSegments[1].isEmpty else { return nil } 65 return pathSegments[1] 66 }() 67 68 let fragment = url.fragment?.trimmingCharacters(in: CharacterSet(charactersIn: "/").union(.whitespaces)) ?? "" 69 let params: [String: String]? = fragment.contains("=") 70 ? Dictionary(uniqueKeysWithValues: fragment.split(separator: "&").compactMap { part in 71 let kv = part.split(separator: "=", maxSplits: 1) 72 guard kv.count == 2 else { return nil } 73 return (String(kv[0]), String(kv[1]).removingPercentEncoding ?? String(kv[1])) 74 }) 75 : nil 76 77 let instance = pathStyleInstance ?? params?["username"] 78 let token: String? = { 79 if pathStyleInstance != nil { 80 let decoded = fragment.removingPercentEncoding ?? fragment 81 return decoded.isEmpty ? nil : decoded 82 } 83 return params?["password"] 84 }() 85 86 guard let instance, !instance.isEmpty, let token, !token.isEmpty else { 87 configManager.configResult = .error("Invalid setup URL: missing instance or credentials.") 88 return 89 } 90 91 var components = URLComponents() 92 components.scheme = "https" 93 components.host = host 94 components.path = "/instances/\(instance)" 95 guard let merchantUrl = components.url?.absoluteString else { 96 configManager.configResult = .error("Invalid setup URL: could not build merchant URL.") 97 return 98 } 99 100 configManager.savePassword = true 101 configManager.fetchConfig(url: merchantUrl, token: token, save: true) 102 } 103 104 var body: some Scene { 105 WindowGroup { 106 ContentView() 107 .environment(\.locale, languageManager.locale) 108 .id(languageManager.selectedLanguage) 109 .environmentObject(configManager) 110 .environmentObject(orderManager) 111 .environmentObject(paymentManager) 112 .environmentObject(historyManager) 113 .environmentObject(refundManager) 114 .onAppear { 115 if let scenario = ScreenshotController.activeScenario { 116 ScreenshotController.applyLocaleIfNeeded() 117 ScreenshotController.applyFixture( 118 configManager: configManager, 119 orderManager: orderManager, 120 paymentManager: paymentManager, 121 historyManager: historyManager, 122 refundManager: refundManager, 123 scenario: scenario 124 ) 125 } else { 126 configManager.restoreCachedRuntimeConfig() 127 if configManager.isConfigured { 128 configManager.refreshConfigInBackground() 129 } 130 } 131 } 132 .onOpenURL { url in 133 handleSetupUrl(url) 134 } 135 } 136 } 137 }