diff --git a/FlurSchnaps.xcodeproj/project.pbxproj b/FlurSchnaps.xcodeproj/project.pbxproj index 326b0fd..0d9fe22 100644 --- a/FlurSchnaps.xcodeproj/project.pbxproj +++ b/FlurSchnaps.xcodeproj/project.pbxproj @@ -9,15 +9,14 @@ /* Begin PBXBuildFile section */ 881E0B26284B74E200435EC2 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E0B25284B74E200435EC2 /* Data+Extensions.swift */; }; 88DBE72A284B989C00D1573B /* DeviceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE729284B989C00D1573B /* DeviceList.swift */; }; - E2349959284E0695002B55F8 /* PushAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E2349958284E0695002B55F8 /* PushAPI */; }; E234995C284E1D02002B55F8 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E234995B284E1D02002B55F8 /* SFSafeSymbols */; }; E234995F284E372B002B55F8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E2349961284E372B002B55F8 /* Localizable.strings */; }; E2349964284F3133002B55F8 /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2349963284F3133002B55F8 /* TextEntryField.swift */; }; + E2349968284F78E3002B55F8 /* Push in Frameworks */ = {isa = PBXBuildFile; productRef = E2349967284F78E3002B55F8 /* Push */; }; E29A7E47284B6143000B908A /* FlurSchnapsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29A7E46284B6143000B908A /* FlurSchnapsApp.swift */; }; E29A7E49284B6143000B908A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29A7E48284B6143000B908A /* ContentView.swift */; }; E29A7E4B284B6144000B908A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E29A7E4A284B6144000B908A /* Assets.xcassets */; }; E29A7E4E284B6144000B908A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E29A7E4D284B6144000B908A /* Preview Assets.xcassets */; }; - E29A7E55284B619A000B908A /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29A7E54284B619A000B908A /* API.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -33,7 +32,6 @@ E29A7E48284B6143000B908A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; E29A7E4A284B6144000B908A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E29A7E4D284B6144000B908A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - E29A7E54284B619A000B908A /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,8 +39,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E2349968284F78E3002B55F8 /* Push in Frameworks */, E234995C284E1D02002B55F8 /* SFSafeSymbols in Frameworks */, - E2349959284E0695002B55F8 /* PushAPI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -75,7 +73,6 @@ E29A7E48284B6143000B908A /* ContentView.swift */, E2349963284F3133002B55F8 /* TextEntryField.swift */, 88DBE729284B989C00D1573B /* DeviceList.swift */, - E29A7E54284B619A000B908A /* API.swift */, 881E0B25284B74E200435EC2 /* Data+Extensions.swift */, E29A7E4A284B6144000B908A /* Assets.xcassets */, E29A7E4C284B6144000B908A /* Preview Content */, @@ -108,8 +105,8 @@ ); name = FlurSchnaps; packageProductDependencies = ( - E2349958284E0695002B55F8 /* PushAPI */, E234995B284E1D02002B55F8 /* SFSafeSymbols */, + E2349967284F78E3002B55F8 /* Push */, ); productName = FlurSchnaps; productReference = E29A7E43284B6143000B908A /* FlurSchnaps.app */; @@ -141,8 +138,8 @@ ); mainGroup = E29A7E3A284B6143000B908A; packageReferences = ( - E2349957284E0695002B55F8 /* XCRemoteSwiftPackageReference "Push-API" */, E234995A284E1D02002B55F8 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, + E2349966284F78E3002B55F8 /* XCRemoteSwiftPackageReference "Push-iOS" */, ); productRefGroup = E29A7E44284B6143000B908A /* Products */; projectDirPath = ""; @@ -174,7 +171,6 @@ E2349964284F3133002B55F8 /* TextEntryField.swift in Sources */, E29A7E49284B6143000B908A /* ContentView.swift in Sources */, 881E0B26284B74E200435EC2 /* Data+Extensions.swift in Sources */, - E29A7E55284B619A000B908A /* API.swift in Sources */, 88DBE72A284B989C00D1573B /* DeviceList.swift in Sources */, E29A7E47284B6143000B908A /* FlurSchnapsApp.swift in Sources */, ); @@ -399,14 +395,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - E2349957284E0695002B55F8 /* XCRemoteSwiftPackageReference "Push-API" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://christophhagen.de/git/ch/Push-API"; - requirement = { - branch = main; - kind = branch; - }; - }; E234995A284E1D02002B55F8 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols"; @@ -415,19 +403,27 @@ minimumVersion = 3.0.0; }; }; + E2349966284F78E3002B55F8 /* XCRemoteSwiftPackageReference "Push-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://christophhagen.de/git/ch/Push-iOS"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.2.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - E2349958284E0695002B55F8 /* PushAPI */ = { - isa = XCSwiftPackageProductDependency; - package = E2349957284E0695002B55F8 /* XCRemoteSwiftPackageReference "Push-API" */; - productName = PushAPI; - }; E234995B284E1D02002B55F8 /* SFSafeSymbols */ = { isa = XCSwiftPackageProductDependency; package = E234995A284E1D02002B55F8 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; productName = SFSafeSymbols; }; + E2349967284F78E3002B55F8 /* Push */ = { + isa = XCSwiftPackageProductDependency; + package = E2349966284F78E3002B55F8 /* XCRemoteSwiftPackageReference "Push-iOS" */; + productName = Push; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = E29A7E3B284B6143000B908A /* Project object */; diff --git a/FlurSchnaps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlurSchnaps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a3e664c..69318f3 100644 --- a/FlurSchnaps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlurSchnaps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -21,10 +21,19 @@ { "identity" : "push-api", "kind" : "remoteSourceControl", - "location" : "https://christophhagen.de/git/ch/Push-API", + "location" : "https://christophhagen.de/git/ch/Push-API.git", "state" : { - "branch" : "main", - "revision" : "d5fb765ac998e2f731ec6fe8bf473a396af9b61c" + "revision" : "50e74bf15637cbcab3f76ff1255db355cc87bc4e", + "version" : "0.5.0" + } + }, + { + "identity" : "push-ios", + "kind" : "remoteSourceControl", + "location" : "https://christophhagen.de/git/ch/Push-iOS", + "state" : { + "revision" : "910f7e5f91408ddf54ea15fb3ccef182b5daedff", + "version" : "0.2.0" } }, { diff --git a/FlurSchnaps/API.swift b/FlurSchnaps/API.swift deleted file mode 100644 index 824101c..0000000 --- a/FlurSchnaps/API.swift +++ /dev/null @@ -1,131 +0,0 @@ -import Foundation -import CryptoKit -import PushAPI -import SwiftUI - -final class API { - - @AppStorage("server") - var server: String = "" - - var url: URL? { - URL(string: server) - } - - init() { - } - - init(server: URL) { - self.server = server.path - } - - init(server: URL, application: ApplicationId) { - self.server = server.path - self.application = application - } - - @AppStorage("application") - var application: ApplicationId = "" - - private static let encoder = JSONEncoder() - private static let decoder = JSONDecoder() - - func register(token: PushToken, name: String) async -> AuthenticationToken? { - let device = DeviceRegistration( - pushToken: token, - application: application, - name: name) - guard let token = await post(.registerNewDevice, body: device) else { - print("Failed to register") - return nil - } - guard token.count == 16 else { - print("Failed to register: Unexpected token length: \(token.count)") - return nil - } - return token - } - - func getDeviceList(pushToken: PushToken, authToken: AuthenticationToken) async -> [DeviceRegistration] { - let device = DeviceAuthentication(pushToken: pushToken, authentication: authToken) - guard let data = await post(.listDevicesInApplication, body: device) else { - print("Devices: Failed") - return [] - } - do { - return try API.decoder.decode([DeviceRegistration].self, from: data) - } catch { - print("Devices: Failed to decode response") - return [] - } - } - - func getUnconfirmedDevices(masterKey: String) async -> [DeviceRegistration] { - let hash = hash(masterKey) - guard let data = await post(.listUnapprovedDevices, bodyData: hash) else { - print("Devices: Failed") - return [] - } - do { - return try API.decoder.decode([DeviceRegistration].self, from: data) - } catch { - print("Devices: Failed to decode response") - return [] - } - } - - private func hash(_ masterKey: String) -> Data { - Data(SHA256.hash(data: masterKey.data(using: .utf8)!)) - } - - func confirm(pushToken: PushToken, with masterKey: String) async -> Bool { - let hash = hash(masterKey) - let device = DeviceDecision(pushToken: pushToken, masterKeyHash: hash) - return await post(.approveDevice, body: device) != nil - } - - func reject(pushToken: PushToken, with masterKey: String) async -> Bool { - let hash = hash(masterKey) - let device = DeviceDecision(pushToken: pushToken, masterKeyHash: hash) - return await post(.rejectDevice, body: device) != nil - } - - func isConfirmed(token: PushToken, authentication: AuthenticationToken) async -> Bool { - let device = DeviceAuthentication(pushToken: token, authentication: authentication) - return await post(.isDeviceApproved, body: device) != nil - } - - func send(push: AuthenticatedPushMessage) async -> Bool { - await post(.sendPushNotification, body: push) != nil - } - - private func post(_ route: Route, body: T) async -> Data? where T: Encodable { - let bodyData = try! API.encoder.encode(body) - return await post(route, bodyData: bodyData) - } - - private func post(_ route: Route, bodyData: Data) async -> Data? { - guard let url = url else { - return nil - } - var request = URLRequest(url: url.appendingPathComponent(route.rawValue)) - request.httpBody = bodyData - request.httpMethod = "POST" - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let httpResponse = response as? HTTPURLResponse else { - return nil - } - guard httpResponse.statusCode == 200 else { - print("Failed with code: \(httpResponse.statusCode)") - return nil - } - return data - } catch { - print("Failed with error: \(error)") - return nil - } - } - - -} diff --git a/FlurSchnaps/ContentView.swift b/FlurSchnaps/ContentView.swift index 42adf43..fbc9e18 100644 --- a/FlurSchnaps/ContentView.swift +++ b/FlurSchnaps/ContentView.swift @@ -1,5 +1,6 @@ import SwiftUI import APNSwift +import Push import PushAPI import SFSafeSymbols @@ -25,7 +26,13 @@ struct ContentView: View { var showDeviceList = false @State - var api = API() + var api: PushClient? + + @AppStorage("server") + var server: String = "" + + @AppStorage("application") + var application = "" @AppStorage("deviceName") var deviceName: String = "" @@ -125,10 +132,10 @@ struct ContentView: View { } else if authToken == nil { Text("register-device-text") .padding() - TextEntryField("Server url", placeholder: "register-device-server-placeholder", symbol: .network, showClearButton: true, text: $api.server) + TextEntryField("Server url", placeholder: "register-device-server-placeholder", symbol: .network, showClearButton: true, text: $server) .padding(.horizontal, 50) .padding(.top) - TextEntryField("Application", placeholder: "register-device-application-placeholder", symbol: .questionmarkApp, showClearButton: true, text: $api.application) + TextEntryField("Application", placeholder: "register-device-application-placeholder", symbol: .questionmarkApp, showClearButton: true, text: $application) .padding(.horizontal, 50) .padding(.top) TextEntryField("Device name", placeholder: "register-device-name-placeholder", symbol: .iphone, text: $deviceName) @@ -161,7 +168,7 @@ struct ContentView: View { .navigationTitle("FlurSchnaps") } .sheet(isPresented: $showDeviceList) { - if let push = pushToken, let auth = authToken { + if let push = pushToken, let auth = authToken, let api = api { DeviceList(pushToken: push, authToken: auth, api: api, @@ -226,6 +233,11 @@ struct ContentView: View { print("No token to register") return } + guard let url = URL(string: server) else { + return + } + let api = PushClient(server: url, application: application) + self.api = api let name = deviceName Task { print("Registering...") @@ -246,13 +258,14 @@ struct ContentView: View { } func checkPushRegistrationStatus() { - guard let token = pushToken, let authToken = authToken else { + guard let token = pushToken, + let authToken = authToken, + let api = api else { return } Task { let confirmed = await api.isConfirmed(token: token, authentication: authToken) if !confirmed { - print("Not confirmed by server: \(api.url?.path ?? "No server") (\(api.server))") print(token.base64EncodedString()) print(authToken.base64EncodedString()) } @@ -264,13 +277,14 @@ struct ContentView: View { func updateDeviceList() { guard let authToken = authToken, - let pushToken = pushToken else { + let pushToken = pushToken, + let api = api else { return } Task { let devices = await api.getDeviceList(pushToken: pushToken, authToken: authToken) DispatchQueue.main.async { - self.deviceList = devices + self.deviceList = devices ?? [] } } } @@ -280,10 +294,9 @@ struct ContentView: View { } func sendPush() { - guard let authToken = authToken else { - return - } - guard let pushToken = pushToken else { + guard let authToken = authToken, + let pushToken = pushToken, + let api = api else { return } diff --git a/FlurSchnaps/DeviceList.swift b/FlurSchnaps/DeviceList.swift index f492b02..0d9974f 100644 --- a/FlurSchnaps/DeviceList.swift +++ b/FlurSchnaps/DeviceList.swift @@ -1,5 +1,6 @@ import SwiftUI import PushAPI +import Push extension String { @@ -14,7 +15,7 @@ struct DeviceList: View { let authToken: AuthenticationToken - let api: API + let api: PushClient @Binding var isPresented: Bool @@ -58,9 +59,9 @@ struct DeviceList: View { private func updateList() async { let devices = await api.getDeviceList(pushToken: pushToken, authToken: authToken) - print("Updated device list: \(devices.count)") + print("Updated device list: \(devices?.count ?? -1)") DispatchQueue.main.async { - self.devices = devices + self.devices = devices ?? [] } } @@ -73,7 +74,7 @@ struct DeviceList_Previews: PreviewProvider { static var previews: some View { DeviceList(pushToken: Data(repeating: 42, count: 32), authToken: Data(repeating: 42, count: 16), - api: .init(server: URL(string: "https://christophhagen.de/push")!), + api: .init(server: URL(string: "https://christophhagen.de/push")!, application: "some"), isPresented: .constant(true), devices: [DeviceRegistration( pushToken: Data([1,2,3,4,5]),