From 248fd03d4d34f6ecaa0244f96cc5b117ae9f9a37 Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Sun, 19 Jan 2025 23:38:12 -0500 Subject: [PATCH] Add preliminary handling of nostrsigner URL scheme in scene delegate --- Yeti.xcodeproj/project.pbxproj | 15 +++++ Yeti/AppDelegate.swift | 25 ++++++++ Yeti/Info.plist | 19 ++++++ Yeti/SceneDelegate.swift | 104 +++++++++++++++++++++++++++++++++ Yeti/YetiApp.swift | 2 + 5 files changed, 165 insertions(+) create mode 100644 Yeti/AppDelegate.swift create mode 100644 Yeti/Info.plist create mode 100644 Yeti/SceneDelegate.swift diff --git a/Yeti.xcodeproj/project.pbxproj b/Yeti.xcodeproj/project.pbxproj index 42b547d..e64e139 100644 --- a/Yeti.xcodeproj/project.pbxproj +++ b/Yeti.xcodeproj/project.pbxproj @@ -29,9 +29,22 @@ 3AA480D42D3DECF20052A05C /* YetiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YetiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 3AA481082D3DF3E40052A05C /* Exceptions for "Yeti" folder in "Yeti" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 3AA480B72D3DECF10052A05C /* Yeti */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ 3AA480BA2D3DECF10052A05C /* Yeti */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 3AA481082D3DF3E40052A05C /* Exceptions for "Yeti" folder in "Yeti" target */, + ); path = Yeti; sourceTree = ""; }; @@ -417,6 +430,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Yeti/Info.plist; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -446,6 +460,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Yeti/Info.plist; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/Yeti/AppDelegate.swift b/Yeti/AppDelegate.swift new file mode 100644 index 0000000..88600fb --- /dev/null +++ b/Yeti/AppDelegate.swift @@ -0,0 +1,25 @@ +// +// AppDelegate.swift +// Yeti +// +// Created by Terry Yiu on 1/19/25. +// + +import Foundation +import UIKit + +class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: nil, + sessionRole: connectingSceneSession.role) + if connectingSceneSession.role == .windowApplication { + configuration.delegateClass = SceneDelegate.self + } + return configuration + } +} diff --git a/Yeti/Info.plist b/Yeti/Info.plist new file mode 100644 index 0000000..3214d02 --- /dev/null +++ b/Yeti/Info.plist @@ -0,0 +1,19 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + xyz.tyiu.Yeti + CFBundleURLSchemes + + nostrsigner + + + + + diff --git a/Yeti/SceneDelegate.swift b/Yeti/SceneDelegate.swift new file mode 100644 index 0000000..7c923df --- /dev/null +++ b/Yeti/SceneDelegate.swift @@ -0,0 +1,104 @@ +// +// SceneDelegate.swift +// Yeti +// +// Created by Terry Yiu on 1/19/25. +// + +import Foundation +import UIKit + +class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject { + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + if let urlContext = connectionOptions.urlContexts.first { + self.scene(scene, openURLContext: urlContext) + } + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let urlContext = URLContexts.first { + self.scene(scene, openURLContext: urlContext) + } + } + + private func scene(_ scene: UIScene, openURLContext: UIOpenURLContext) { + let sendingAppID = openURLContext.options.sourceApplication + let url = openURLContext.url + + guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true), + components.scheme == "nostrsigner", + let queryItems = components.queryItems else { + print("Invalid URL or path missing") + return + } + + print("source application = \(sendingAppID ?? "Unknown")") + print("url = \(url)") + + if let path = components.path { + print("path = \(path)") + } + if let host = components.host { + print("host = \(host)") + } + + print("queryItems = \(queryItems)") + + let params = queryItems.reduce(into: [:]) { $0[$1.name] = $1.value } + + let compressionType: NostrSignerCompressionType + if let rawCompressionType = params["compressionType"] { + if let maybeCompressionType = NostrSignerCompressionType(rawValue: rawCompressionType) { + compressionType = maybeCompressionType + } else { + return + } + } else { + compressionType = .none + } + + guard let rawType = params["type"], + let type = NostrSignerType(rawValue: rawType), + let rawReturnType = params["returnType"], + let returnType = NostrSignerReturnType(rawValue: rawReturnType) else { + return + } + + print("type = \(type)") + print("returnType = \(returnType)") + print("compressionType = \(compressionType)") + + if let pubkey = params["pubkey"] { + print("pubkey = \(pubkey)") + } + + if let callbackURL = params["callbackUrl"] { + print("callbackURL = \(callbackURL)") + } + } +} + +enum NostrSignerType: String, CaseIterable { + case getPublicKey = "get_public_key" + case signEvent = "sign_event" + case nip04Encrypt = "nip04_encrypt" + case nip44Encrypt = "nip44_encrypt" + case nip04Decrypt = "nip04_decrypt" + case nip44Decrypt = "nip44_decrypt" + case getRelays = "get_relays" + case decryptZapEvent = "decrypt_zap_event" +} + +enum NostrSignerReturnType: String, CaseIterable { + case signature + case event +} + +enum NostrSignerCompressionType: String, CaseIterable { + case none + case gzip +} diff --git a/Yeti/YetiApp.swift b/Yeti/YetiApp.swift index 4b7f059..e2c6cbe 100644 --- a/Yeti/YetiApp.swift +++ b/Yeti/YetiApp.swift @@ -10,6 +10,8 @@ import SwiftData @main struct YetiApp: App { + @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate + var sharedModelContainer: ModelContainer = { let schema = Schema([ Item.self