diff --git a/Yeti.xcodeproj/project.pbxproj b/Yeti.xcodeproj/project.pbxproj index d89af68..d7674aa 100644 --- a/Yeti.xcodeproj/project.pbxproj +++ b/Yeti.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 3AA481172D3ECDFD0052A05C /* NostrSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 3AA481162D3ECDFD0052A05C /* NostrSDK */; }; + 3AC2CDAA2D4683AD00A6DEDB /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC2CDA92D4683AD00A6DEDB /* UniformTypeIdentifiers.framework */; }; + 3AC2CDB62D4683AD00A6DEDB /* YetiSignerAction.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AC2CDA72D4683AD00A6DEDB /* YetiSignerAction.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3AC2CDC32D468B7800A6DEDB /* NostrSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 3AC2CDC22D468B7800A6DEDB /* NostrSDK */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -25,12 +28,35 @@ remoteGlobalIDString = 3AA480B72D3DECF10052A05C; remoteInfo = Yeti; }; + 3AC2CDB42D4683AD00A6DEDB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3AA480B02D3DECF10052A05C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AC2CDA62D4683AD00A6DEDB; + remoteInfo = YetiSignerAction; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3AC2CDB72D4683AD00A6DEDB /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 3AC2CDB62D4683AD00A6DEDB /* YetiSignerAction.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 3AA480B82D3DECF10052A05C /* Yeti.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Yeti.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3AA480CA2D3DECF20052A05C /* YetiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YetiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3AA480D42D3DECF20052A05C /* YetiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YetiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AC2CDA72D4683AD00A6DEDB /* YetiSignerAction.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = YetiSignerAction.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AC2CDA92D4683AD00A6DEDB /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -41,6 +67,27 @@ ); target = 3AA480B72D3DECF10052A05C /* Yeti */; }; + 3AAAFACC2D47C0180065A065 /* Exceptions for "Yeti" folder in "YetiSignerAction" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Models/GeneralSettingsModel.swift, + Models/NostrClientModel.swift, + Models/ProfileSettingsModel.swift, + Models/SignerRequestModel.swift, + Models/SignEventPermissionModel.swift, + Models/SigningPolicy.swift, + Models/YetiModelContainer.swift, + Utilities/PrivateKeySecureStorage.swift, + ); + target = 3AC2CDA62D4683AD00A6DEDB /* YetiSignerAction */; + }; + 3AC2CDBB2D4683AD00A6DEDB /* Exceptions for "YetiSignerAction" folder in "YetiSignerAction" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 3AC2CDA62D4683AD00A6DEDB /* YetiSignerAction */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -48,6 +95,7 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 3AA481082D3DF3E40052A05C /* Exceptions for "Yeti" folder in "Yeti" target */, + 3AAAFACC2D47C0180065A065 /* Exceptions for "Yeti" folder in "YetiSignerAction" target */, ); path = Yeti; sourceTree = ""; @@ -62,6 +110,14 @@ path = YetiUITests; sourceTree = ""; }; + 3AC2CDAB2D4683AD00A6DEDB /* YetiSignerAction */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 3AC2CDBB2D4683AD00A6DEDB /* Exceptions for "YetiSignerAction" folder in "YetiSignerAction" target */, + ); + path = YetiSignerAction; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,6 +143,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AC2CDA42D4683AD00A6DEDB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AC2CDC32D468B7800A6DEDB /* NostrSDK in Frameworks */, + 3AC2CDAA2D4683AD00A6DEDB /* UniformTypeIdentifiers.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -96,6 +161,8 @@ 3AA480BA2D3DECF10052A05C /* Yeti */, 3AA480CD2D3DECF20052A05C /* YetiTests */, 3AA480D72D3DECF20052A05C /* YetiUITests */, + 3AC2CDAB2D4683AD00A6DEDB /* YetiSignerAction */, + 3AC2CDA82D4683AD00A6DEDB /* Frameworks */, 3AA480B92D3DECF10052A05C /* Products */, ); sourceTree = ""; @@ -106,10 +173,19 @@ 3AA480B82D3DECF10052A05C /* Yeti.app */, 3AA480CA2D3DECF20052A05C /* YetiTests.xctest */, 3AA480D42D3DECF20052A05C /* YetiUITests.xctest */, + 3AC2CDA72D4683AD00A6DEDB /* YetiSignerAction.appex */, ); name = Products; sourceTree = ""; }; + 3AC2CDA82D4683AD00A6DEDB /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3AC2CDA92D4683AD00A6DEDB /* UniformTypeIdentifiers.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -120,11 +196,13 @@ 3AA480B42D3DECF10052A05C /* Sources */, 3AA480B52D3DECF10052A05C /* Frameworks */, 3AA480B62D3DECF10052A05C /* Resources */, + 3AC2CDB72D4683AD00A6DEDB /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( 3AA480FA2D3DF10E0052A05C /* PBXTargetDependency */, + 3AC2CDB52D4683AD00A6DEDB /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 3AA480BA2D3DECF10052A05C /* Yeti */, @@ -185,6 +263,29 @@ productReference = 3AA480D42D3DECF20052A05C /* YetiUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + 3AC2CDA62D4683AD00A6DEDB /* YetiSignerAction */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3AC2CDBA2D4683AD00A6DEDB /* Build configuration list for PBXNativeTarget "YetiSignerAction" */; + buildPhases = ( + 3AC2CDA32D4683AD00A6DEDB /* Sources */, + 3AC2CDA42D4683AD00A6DEDB /* Frameworks */, + 3AC2CDA52D4683AD00A6DEDB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 3AC2CDAB2D4683AD00A6DEDB /* YetiSignerAction */, + ); + name = YetiSignerAction; + packageProductDependencies = ( + 3AC2CDC22D468B7800A6DEDB /* NostrSDK */, + ); + productName = YetiSignerAction; + productReference = 3AC2CDA72D4683AD00A6DEDB /* YetiSignerAction.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -206,6 +307,9 @@ CreatedOnToolsVersion = 16.2; TestTargetID = 3AA480B72D3DECF10052A05C; }; + 3AC2CDA62D4683AD00A6DEDB = { + CreatedOnToolsVersion = 16.2; + }; }; }; buildConfigurationList = 3AA480B32D3DECF10052A05C /* Build configuration list for PBXProject "Yeti" */; @@ -229,6 +333,7 @@ 3AA480B72D3DECF10052A05C /* Yeti */, 3AA480C92D3DECF20052A05C /* YetiTests */, 3AA480D32D3DECF20052A05C /* YetiUITests */, + 3AC2CDA62D4683AD00A6DEDB /* YetiSignerAction */, ); }; /* End PBXProject section */ @@ -255,6 +360,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AC2CDA52D4683AD00A6DEDB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -279,6 +391,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AC2CDA32D4683AD00A6DEDB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -304,6 +423,11 @@ isa = PBXTargetDependency; productRef = 3AA481012D3DF20D0052A05C /* SwiftLintBuildToolPlugin */; }; + 3AC2CDB52D4683AD00A6DEDB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AC2CDA62D4683AD00A6DEDB /* YetiSignerAction */; + targetProxy = 3AC2CDB42D4683AD00A6DEDB /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -433,6 +557,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Yeti/Yeti.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\""; @@ -464,6 +589,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Yeti/Yeti.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\""; @@ -558,6 +684,58 @@ }; name = Release; }; + 3AC2CDB82D4683AD00A6DEDB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = YetiSignerAction/YetiSignerAction.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S99A5B637C; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = YetiSignerAction/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Sign Nostr Event"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = xyz.tyiu.Yeti.YetiSignerAction; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3AC2CDB92D4683AD00A6DEDB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = YetiSignerAction/YetiSignerAction.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S99A5B637C; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = YetiSignerAction/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Sign Nostr Event"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = xyz.tyiu.Yeti.YetiSignerAction; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -597,6 +775,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 3AC2CDBA2D4683AD00A6DEDB /* Build configuration list for PBXNativeTarget "YetiSignerAction" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AC2CDB82D4683AD00A6DEDB /* Debug */, + 3AC2CDB92D4683AD00A6DEDB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -639,6 +826,11 @@ package = 3AA481152D3ECDFD0052A05C /* XCRemoteSwiftPackageReference "nostr-sdk-ios" */; productName = NostrSDK; }; + 3AC2CDC22D468B7800A6DEDB /* NostrSDK */ = { + isa = XCSwiftPackageProductDependency; + package = 3AA481152D3ECDFD0052A05C /* XCRemoteSwiftPackageReference "nostr-sdk-ios" */; + productName = NostrSDK; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3AA480B02D3DECF10052A05C /* Project object */; diff --git a/Yeti/Models/YetiModelContainer.swift b/Yeti/Models/YetiModelContainer.swift new file mode 100644 index 0000000..6092dde --- /dev/null +++ b/Yeti/Models/YetiModelContainer.swift @@ -0,0 +1,28 @@ +// +// YetiModelContainer.swift +// Yeti +// +// Created by Terry Yiu on 1/27/25. +// + +import SwiftData + +public func createYetiModelContainer() -> ModelContainer { + let schema = Schema([ + GeneralSettingsModel.self, + ProfileSettingsModel.self, + SignerRequestModel.self + ]) + + let modelConfiguration = ModelConfiguration( + schema: schema, + isStoredInMemoryOnly: false, + cloudKitDatabase: .none + ) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error.localizedDescription)") + } +} diff --git a/Yeti/SceneDelegate.swift b/Yeti/SceneDelegate.swift index e1bffac..10fab62 100644 --- a/Yeti/SceneDelegate.swift +++ b/Yeti/SceneDelegate.swift @@ -15,18 +15,7 @@ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject { private let modelContainer: ModelContainer override init() { - let schema = Schema([ - GeneralSettingsModel.self, - ProfileSettingsModel.self, - SignerRequestModel.self - ]) - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - - do { - modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } + modelContainer = createYetiModelContainer() } func scene( diff --git a/Yeti/Utilities/PrivateKeySecureStorage.swift b/Yeti/Utilities/PrivateKeySecureStorage.swift index 92a8168..7e528b1 100644 --- a/Yeti/Utilities/PrivateKeySecureStorage.swift +++ b/Yeti/Utilities/PrivateKeySecureStorage.swift @@ -13,6 +13,7 @@ class PrivateKeySecureStorage { static let shared = PrivateKeySecureStorage() private let service = "yeti-private-keys" + private let accessGroup = "S99A5B637C.xyz.tyiu.Yeti.SharedKeychain" func keypair(for publicKey: PublicKey) -> Keypair? { let query = [ @@ -20,7 +21,8 @@ class PrivateKeySecureStorage { kSecAttrAccount: publicKey.hex, kSecClass: kSecClassGenericPassword, kSecReturnData: true, - kSecMatchLimit: kSecMatchLimitOne + kSecMatchLimit: kSecMatchLimitOne, + kSecAttrAccessGroup: accessGroup ] as [CFString: Any] as CFDictionary var result: AnyObject? @@ -40,16 +42,19 @@ class PrivateKeySecureStorage { kSecAttrService: service, kSecAttrAccount: keypair.publicKey.hex, kSecClass: kSecClassGenericPassword, - kSecValueData: keypair.privateKey.hex.data(using: .utf8) as Any + kSecValueData: keypair.privateKey.hex.data(using: .utf8) as Any, + kSecAttrAccessGroup: accessGroup ] as [CFString: Any] as CFDictionary var status = SecItemAdd(query, nil) - if status == errSecDuplicateItem { + switch status { + case errSecDuplicateItem: let query = [ kSecAttrService: service, kSecAttrAccount: keypair.publicKey.hex, - kSecClass: kSecClassGenericPassword + kSecClass: kSecClassGenericPassword, + kSecAttrAccessGroup: accessGroup ] as [CFString: Any] as CFDictionary let updates = [ @@ -57,6 +62,12 @@ class PrivateKeySecureStorage { ] as CFDictionary status = SecItemUpdate(query, updates) + case errSecSuccess: + print("Successfully stored keypair.") + case errSecMissingEntitlement: + print("Missing entitlement error while storing keypair.") + default: + print("Error storing keypair: \(status)") } } @@ -64,7 +75,8 @@ class PrivateKeySecureStorage { let query = [ kSecAttrService: service, kSecAttrAccount: publicKey.hex, - kSecClass: kSecClassGenericPassword + kSecClass: kSecClassGenericPassword, + kSecAttrAccessGroup: accessGroup ] as [CFString: Any] as CFDictionary _ = SecItemDelete(query) diff --git a/Yeti/Yeti.entitlements b/Yeti/Yeti.entitlements new file mode 100644 index 0000000..db499b4 --- /dev/null +++ b/Yeti/Yeti.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.xyz.tyiu.Yeti + + keychain-access-groups + + $(AppIdentifierPrefix)xyz.tyiu.Yeti.SharedKeychain + + + diff --git a/Yeti/YetiApp.swift b/Yeti/YetiApp.swift index a10413c..892879a 100644 --- a/Yeti/YetiApp.swift +++ b/Yeti/YetiApp.swift @@ -5,6 +5,7 @@ // Created by Terry Yiu on 1/19/25. // +import NostrSDK import SwiftUI import SwiftData @@ -15,23 +16,16 @@ struct YetiApp: App { private let modelContainer: ModelContainer init() { - let schema = Schema([ - GeneralSettingsModel.self, - ProfileSettingsModel.self, - SignerRequestModel.self - ]) - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - - do { - modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } + modelContainer = createYetiModelContainer() var descriptor = FetchDescriptor() descriptor.fetchLimit = 1 - if (try? modelContainer.mainContext.fetch(descriptor))?.first == nil { + if let generalSettingsModel = (try? modelContainer.mainContext.fetch(descriptor))?.first, + let activePublicKey = generalSettingsModel.activePublicKey, + let publicKey = PublicKey(hex: activePublicKey) { + print("Found pubkey=\(PrivateKeySecureStorage.shared.keypair(for: publicKey)?.publicKey.npub ?? "")") + } else { let newGeneralSettingsModel = GeneralSettingsModel() modelContainer.mainContext.insert(newGeneralSettingsModel) do { diff --git a/YetiSignerAction/Info.plist b/YetiSignerAction/Info.plist new file mode 100644 index 0000000..31b8dcc --- /dev/null +++ b/YetiSignerAction/Info.plist @@ -0,0 +1,31 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsText + + + NSExtensionServiceAllowsFinderPreviewItem + + NSExtensionServiceAllowsTouchBarItem + + NSExtensionServiceFinderPreviewIconName + NSActionTemplate + NSExtensionServiceTouchBarBezelColorName + TouchBarBezel + NSExtensionServiceTouchBarIconName + NSActionTemplate + + NSExtensionPrincipalClass + YetiSignerAction.YetiSignerActionViewController + NSExtensionPointIdentifier + com.apple.ui-services + + + diff --git a/YetiSignerAction/Media.xcassets/Contents.json b/YetiSignerAction/Media.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/YetiSignerAction/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YetiSignerAction/Media.xcassets/TouchBarBezel.colorset/Contents.json b/YetiSignerAction/Media.xcassets/TouchBarBezel.colorset/Contents.json new file mode 100644 index 0000000..94a9fc2 --- /dev/null +++ b/YetiSignerAction/Media.xcassets/TouchBarBezel.colorset/Contents.json @@ -0,0 +1,14 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "mac", + "color" : { + "reference" : "systemPurpleColor" + } + } + ] +} \ No newline at end of file diff --git a/YetiSignerAction/YetiSignerAction.entitlements b/YetiSignerAction/YetiSignerAction.entitlements new file mode 100644 index 0000000..db499b4 --- /dev/null +++ b/YetiSignerAction/YetiSignerAction.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.xyz.tyiu.Yeti + + keychain-access-groups + + $(AppIdentifierPrefix)xyz.tyiu.Yeti.SharedKeychain + + + diff --git a/YetiSignerAction/YetiSignerActionView.swift b/YetiSignerAction/YetiSignerActionView.swift new file mode 100644 index 0000000..20073e8 --- /dev/null +++ b/YetiSignerAction/YetiSignerActionView.swift @@ -0,0 +1,54 @@ +// +// YetiSignerActionView.swift +// YetiSignerAction +// +// Created by Terry Yiu on 1/26/25. +// + +import NostrSDK +import SwiftUI + +struct YetiSignerActionView: View { + @State private var text: String + @State private var result: String = "" + + init(text: String) { + self.text = text + } + + var body: some View { + EmptyView() + .onAppear { + do { + try sign() + done() + } catch { + result = error.localizedDescription + } + } + } + + func sign() throws { + let data = Data(text.utf8) + let unsignedNostrEvent = try JSONDecoder().decode(NostrEvent.self, from: data) + let pubkey = unsignedNostrEvent.pubkey + if let publicKey = PublicKey(hex: pubkey), + let keypair = PrivateKeySecureStorage.shared.keypair(for: publicKey) { + let signedNostrEvent = try NostrEvent.Builder(nostrEvent: unsignedNostrEvent) + .build(signedBy: keypair) + + let encodedSignedEvent = try JSONEncoder().encode(signedNostrEvent) + let encodedSignedEventString = String(data: encodedSignedEvent, encoding: .utf8)! + + self.result = encodedSignedEventString + } + } + + func done() { + NotificationCenter.default.post(name: NSNotification.Name("done"), object: result) + } +} + +#Preview { + YetiSignerActionView(text: "Hello, world!") +} diff --git a/YetiSignerAction/YetiSignerActionViewController.swift b/YetiSignerAction/YetiSignerActionViewController.swift new file mode 100644 index 0000000..9aab771 --- /dev/null +++ b/YetiSignerAction/YetiSignerActionViewController.swift @@ -0,0 +1,81 @@ +// +// YetiSignerActionViewController.swift +// YetiSignerAction +// +// Created by Terry Yiu on 1/26/25. +// + +//import MobileCoreServices +import SwiftUI +import UIKit +import UniformTypeIdentifiers + +class YetiSignerActionViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + guard + let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, + let itemProvider = extensionItem.attachments?.first + else { + done() + return + } + + + let textDataType = UTType.plainText.identifier + guard itemProvider.hasItemConformingToTypeIdentifier(textDataType) else { + done() + return + } + + itemProvider.loadItem(forTypeIdentifier: textDataType , options: nil) { (providedText, error) in + if let error { + self.done(signedEvent: error.localizedDescription) + return + } + + if let text = providedText as? String { + DispatchQueue.main.async { + let contentView = UIHostingController(rootView: YetiSignerActionView(text: text)) + self.addChild(contentView) + self.view.addSubview(contentView.view) + + // set up constraints + contentView.view.translatesAutoresizingMaskIntoConstraints = false + contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true + contentView.view.bottomAnchor.constraint (equalTo: self.view.bottomAnchor).isActive = true + contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true + contentView.view.rightAnchor.constraint (equalTo: self.view.rightAnchor).isActive = true + } + } else { + self.done() + return + } + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name("done"), object: nil, queue: nil) { notification in + DispatchQueue.main.async { + self.done(signedEvent: notification.object as? String) + } + } + } + + func done(signedEvent: String? = nil) { + if let extensionContext = self.extensionContext { + if let signedEvent { + let itemProvider = NSItemProvider( + item: signedEvent as NSSecureCoding?, + typeIdentifier: UTType.text.identifier + ) + let extensionItem = NSExtensionItem() + extensionItem.attachments = [itemProvider] + extensionContext.completeRequest(returningItems: [extensionItem], completionHandler: nil) + } else { + extensionContext.completeRequest(returningItems: [], completionHandler: nil) + } + } + } + +}