Add preliminary handling of nostrsigner URL scheme in scene delegate

This commit is contained in:
2025-01-19 23:38:12 -05:00
parent 62fe96bd2f
commit 248fd03d4d
5 changed files with 165 additions and 0 deletions

View File

@@ -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 = "<group>";
};
@@ -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;

25
Yeti/AppDelegate.swift Normal file
View File

@@ -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
}
}

19
Yeti/Info.plist Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>xyz.tyiu.Yeti</string>
<key>CFBundleURLSchemes</key>
<array>
<string>nostrsigner</string>
</array>
</dict>
</array>
</dict>
</plist>

104
Yeti/SceneDelegate.swift Normal file
View File

@@ -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<UIOpenURLContext>) {
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
}

View File

@@ -10,6 +10,8 @@ import SwiftData
@main
struct YetiApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self