Add signer action extension with shared keychain and data store

This commit is contained in:
2025-01-26 13:27:19 -05:00
parent aae25543e0
commit f9cab6f498
12 changed files with 459 additions and 30 deletions

View File

@@ -0,0 +1,31 @@
<?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>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
</dict>
<key>NSExtensionServiceAllowsFinderPreviewItem</key>
<true/>
<key>NSExtensionServiceAllowsTouchBarItem</key>
<true/>
<key>NSExtensionServiceFinderPreviewIconName</key>
<string>NSActionTemplate</string>
<key>NSExtensionServiceTouchBarBezelColorName</key>
<string>TouchBarBezel</string>
<key>NSExtensionServiceTouchBarIconName</key>
<string>NSActionTemplate</string>
</dict>
<key>NSExtensionPrincipalClass</key>
<string>YetiSignerAction.YetiSignerActionViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,14 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "mac",
"color" : {
"reference" : "systemPurpleColor"
}
}
]
}

View File

@@ -0,0 +1,14 @@
<?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>com.apple.security.application-groups</key>
<array>
<string>group.xyz.tyiu.Yeti</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)xyz.tyiu.Yeti.SharedKeychain</string>
</array>
</dict>
</plist>

View File

@@ -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!")
}

View File

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