Add signer action extension with shared keychain and data store
This commit is contained in:
31
YetiSignerAction/Info.plist
Normal file
31
YetiSignerAction/Info.plist
Normal 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>
|
||||
6
YetiSignerAction/Media.xcassets/Contents.json
Normal file
6
YetiSignerAction/Media.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"color" : {
|
||||
"reference" : "systemPurpleColor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
14
YetiSignerAction/YetiSignerAction.entitlements
Normal file
14
YetiSignerAction/YetiSignerAction.entitlements
Normal 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>
|
||||
54
YetiSignerAction/YetiSignerActionView.swift
Normal file
54
YetiSignerAction/YetiSignerActionView.swift
Normal 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!")
|
||||
}
|
||||
81
YetiSignerAction/YetiSignerActionViewController.swift
Normal file
81
YetiSignerAction/YetiSignerActionViewController.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user