Add some support for signer requests
This commit is contained in:
@@ -436,6 +436,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = S99A5B637C;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -466,6 +467,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = S99A5B637C;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
||||
@@ -10,12 +10,10 @@ import SwiftData
|
||||
|
||||
@Model
|
||||
final class NostrClientModel {
|
||||
@Attribute(.unique) var id: String
|
||||
@Attribute(.unique) var name: String
|
||||
|
||||
@Relationship(deleteRule: .cascade) var signEventPermissions: [SignEventPermissionModel] = []
|
||||
|
||||
var signingPolicy: SigningPolicy?
|
||||
|
||||
var readPublicKeyPermission: Bool = false
|
||||
var nip04EncryptPermission: Bool = false
|
||||
var nip44EncryptPermission: Bool = false
|
||||
@@ -24,7 +22,7 @@ final class NostrClientModel {
|
||||
var getRelaysPermission: Bool = false
|
||||
var decryptZapEventPermission: Bool = false
|
||||
|
||||
init(id: String) {
|
||||
self.id = id
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,11 @@ final class ProfileSettingsModel {
|
||||
self.publicKey = publicKey
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileSettingsModel {
|
||||
static func predicateByPublicKey(_ publicKey: String) -> Predicate<ProfileSettingsModel> {
|
||||
#Predicate<ProfileSettingsModel> { profileSettingsModel in
|
||||
profileSettingsModel.publicKey == publicKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import SwiftData
|
||||
@Model
|
||||
final class SignEventPermissionModel {
|
||||
var kind: Int
|
||||
var allowed: Bool
|
||||
var approved: Bool
|
||||
|
||||
init(kind: Int, allowed: Bool) {
|
||||
init(kind: Int, approved: Bool) {
|
||||
self.kind = kind
|
||||
self.allowed = allowed
|
||||
self.approved = approved
|
||||
}
|
||||
}
|
||||
|
||||
68
Yeti/Models/SignerRequestModel.swift
Normal file
68
Yeti/Models/SignerRequestModel.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// SignerRequestModel.swift
|
||||
// Yeti
|
||||
//
|
||||
// Created by Terry Yiu on 1/22/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
final class SignerRequestModel {
|
||||
var type: NostrSignerType
|
||||
var returnType: NostrSignerReturnType
|
||||
var compressionType: NostrSignerCompressionType
|
||||
var createdAt: Date
|
||||
var callbackURL: String?
|
||||
var pubkey: String?
|
||||
var sourceApplication: String?
|
||||
var targetApplication: String?
|
||||
var approved: Bool?
|
||||
var decidedAt: Date?
|
||||
|
||||
init(
|
||||
type: NostrSignerType,
|
||||
returnType: NostrSignerReturnType,
|
||||
compressionType: NostrSignerCompressionType,
|
||||
createdAt: Date,
|
||||
callbackURL: String? = nil,
|
||||
pubkey: String? = nil,
|
||||
sourceApplication: String? = nil,
|
||||
targetApplication: String? = nil,
|
||||
approved: Bool? = nil,
|
||||
decidedAt: Date? = nil
|
||||
) {
|
||||
self.type = type
|
||||
self.returnType = returnType
|
||||
self.compressionType = compressionType
|
||||
self.createdAt = createdAt
|
||||
self.callbackURL = callbackURL
|
||||
self.pubkey = pubkey
|
||||
self.sourceApplication = sourceApplication
|
||||
self.targetApplication = targetApplication
|
||||
self.approved = approved
|
||||
self.decidedAt = decidedAt
|
||||
}
|
||||
}
|
||||
|
||||
enum NostrSignerType: String, Codable, 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, Codable {
|
||||
case signature
|
||||
case event
|
||||
}
|
||||
|
||||
enum NostrSignerCompressionType: String, Codable {
|
||||
case none
|
||||
case gzip
|
||||
}
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum SigningPolicy: Int, Codable, CaseIterable {
|
||||
case basic = 0
|
||||
case manual = 1
|
||||
enum SigningPolicy: Codable, CaseIterable {
|
||||
case basic
|
||||
case manual
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
"Add your key" : {
|
||||
"comment" : "Title of view to add the user’s private key."
|
||||
},
|
||||
"Allow" : {
|
||||
"comment" : "Button to allow a permission request."
|
||||
},
|
||||
"Approve basic actions" : {
|
||||
"comment" : "Name of event signing policy that approves basic actions."
|
||||
},
|
||||
"Approved: %@" : {
|
||||
"comment" : "Text indicating that a signer request is either approved or not approved."
|
||||
},
|
||||
"Create a key" : {
|
||||
"comment" : "Button to create a key."
|
||||
},
|
||||
@@ -22,6 +28,9 @@
|
||||
"Decrypt zap events" : {
|
||||
"comment" : "Permission toggle to allow Nostr client to decrypt zap events."
|
||||
},
|
||||
"Deny" : {
|
||||
"comment" : "Button to deny a permission request."
|
||||
},
|
||||
"Done" : {
|
||||
"comment" : "Button to go to the next view that adds the user’s entered private key."
|
||||
},
|
||||
@@ -49,9 +58,15 @@
|
||||
"Next" : {
|
||||
"comment" : "Button to go to the next view that adds the user’s entered private key."
|
||||
},
|
||||
"Nothing to approve yet" : {
|
||||
"comment" : "Text on Home tab to indicate that there are no permission requests to review for approval."
|
||||
},
|
||||
"nsec / private key" : {
|
||||
"comment" : "Prompt asking user to enter in a Nostr private key."
|
||||
},
|
||||
"Pending" : {
|
||||
"comment" : "Text indicating that a signer request is pending approval."
|
||||
},
|
||||
"Permissions" : {
|
||||
"comment" : "Title for Permissions tab"
|
||||
},
|
||||
@@ -76,6 +91,9 @@
|
||||
"Sign kind %@ events" : {
|
||||
"comment" : "Permission toggle to allow Nostr client to sign events of a specific kind.."
|
||||
},
|
||||
"Why not explore your favorite Nostr app a bit?" : {
|
||||
"comment" : "Text on Home tab to suggest what the user could do instead\nsince there are no permission requests to review for approval."
|
||||
},
|
||||
"Yeti: Nostr Helper" : {
|
||||
"comment" : "Application title."
|
||||
},
|
||||
|
||||
@@ -6,9 +6,29 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
|
||||
private static let nostrSignerURLScheme = "nostrsigner"
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
func scene(
|
||||
_ scene: UIScene,
|
||||
willConnectTo session: UISceneSession,
|
||||
@@ -26,32 +46,29 @@ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
|
||||
}
|
||||
|
||||
private func scene(_ scene: UIScene, openURLContext: UIOpenURLContext) {
|
||||
let sendingAppID = openURLContext.options.sourceApplication
|
||||
let url = openURLContext.url
|
||||
var generalSettingsModelDescriptor = FetchDescriptor<GeneralSettingsModel>()
|
||||
generalSettingsModelDescriptor.fetchLimit = 1
|
||||
|
||||
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
components.scheme == "nostrsigner",
|
||||
let queryItems = components.queryItems else {
|
||||
print("Invalid URL or path missing")
|
||||
guard let generalSettingsModel = try? modelContainer.mainContext.fetch(generalSettingsModelDescriptor).first,
|
||||
let activePublicKey = generalSettingsModel.activePublicKey
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
print("source application = \(sendingAppID ?? "Unknown")")
|
||||
print("url = \(url)")
|
||||
let sourceApplication = openURLContext.options.sourceApplication
|
||||
let url = openURLContext.url
|
||||
|
||||
if let path = components.path {
|
||||
print("path = \(path)")
|
||||
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
components.scheme == SceneDelegate.nostrSignerURLScheme,
|
||||
let queryItems = components.queryItems else {
|
||||
print("Invalid URL or path missing. \(url.absoluteString)")
|
||||
return
|
||||
}
|
||||
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 rawCompressionType = params[NostrSignerURLQueryItemName.compressionType.rawValue] {
|
||||
if let maybeCompressionType = NostrSignerCompressionType(rawValue: rawCompressionType) {
|
||||
compressionType = maybeCompressionType
|
||||
} else {
|
||||
@@ -61,44 +78,52 @@ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
|
||||
compressionType = .none
|
||||
}
|
||||
|
||||
guard let rawType = params["type"],
|
||||
guard let rawType = params[NostrSignerURLQueryItemName.type.rawValue],
|
||||
let type = NostrSignerType(rawValue: rawType),
|
||||
let rawReturnType = params["returnType"],
|
||||
let returnType = NostrSignerReturnType(rawValue: rawReturnType) else {
|
||||
let rawReturnType = params[NostrSignerURLQueryItemName.returnType.rawValue],
|
||||
let returnType = NostrSignerReturnType(rawValue: rawReturnType)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
print("type = \(type)")
|
||||
print("returnType = \(returnType)")
|
||||
print("compressionType = \(compressionType)")
|
||||
let callbackURLString = params[NostrSignerURLQueryItemName.callbackURL.rawValue]
|
||||
let targetApplication = targetApplication(callbackURLString)
|
||||
|
||||
if let pubkey = params["pubkey"] {
|
||||
print("pubkey = \(pubkey)")
|
||||
}
|
||||
let pubkey = params[NostrSignerURLQueryItemName.pubkey.rawValue]
|
||||
|
||||
if let callbackURL = params["callbackUrl"] {
|
||||
print("callbackURL = \(callbackURL)")
|
||||
let signerRequestModel = SignerRequestModel(
|
||||
type: type,
|
||||
returnType: returnType,
|
||||
compressionType: compressionType,
|
||||
createdAt: Date.now,
|
||||
callbackURL: callbackURLString,
|
||||
pubkey: pubkey,
|
||||
sourceApplication: sourceApplication,
|
||||
targetApplication: targetApplication
|
||||
)
|
||||
|
||||
modelContainer.mainContext.insert(signerRequestModel)
|
||||
}
|
||||
|
||||
private func targetApplication(_ callbackURLString: String?) -> String? {
|
||||
if let callbackURLString, let callbackURL = URL(string: callbackURLString) {
|
||||
if let scheme = callbackURL.scheme {
|
||||
let lowercasedScheme = scheme.lowercased()
|
||||
if lowercasedScheme == "http" || lowercasedScheme == "https" {
|
||||
return callbackURL.host()
|
||||
} else {
|
||||
return lowercasedScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
private enum NostrSignerURLQueryItemName: String {
|
||||
case callbackURL = "callbackUrl"
|
||||
case compressionType
|
||||
case pubkey
|
||||
case returnType
|
||||
case type
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if generalSettingsModels.first!.activePublicKey == nil {
|
||||
OnboardingView()
|
||||
if let activePublicKey = generalSettingsModels.first?.activePublicKey {
|
||||
LoggedInView(publicKey: activePublicKey)
|
||||
} else {
|
||||
LoggedInView()
|
||||
OnboardingView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,5 +25,5 @@ struct ContentView: View {
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
.modelContainer(for: ProfileSettingsModel.self, inMemory: true)
|
||||
.modelContainer(for: GeneralSettingsModel.self, inMemory: true)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,40 @@
|
||||
// Created by Terry Yiu on 1/20/25.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct HistoryView: View {
|
||||
@Query(sort: \SignerRequestModel.createdAt, order: .reverse) private var signerRequestModels: [SignerRequestModel]
|
||||
|
||||
var body: some View {
|
||||
EmptyView()
|
||||
List {
|
||||
ForEach(signerRequestModels, id: \.self) { signerRequestModel in
|
||||
Section {
|
||||
VStack {
|
||||
Text(signerRequestModel.type.rawValue)
|
||||
Text(signerRequestModel.returnType.rawValue)
|
||||
Text(signerRequestModel.compressionType.rawValue)
|
||||
Text(signerRequestModel.createdAt.description)
|
||||
Text(signerRequestModel.callbackURL ?? "Unknown callbackURL")
|
||||
Text(signerRequestModel.pubkey ?? "Unknown pubkey")
|
||||
Text(signerRequestModel.sourceApplication ?? "Unknown sourceApplication")
|
||||
Text(signerRequestModel.targetApplication ?? "Unknown targetApplication")
|
||||
|
||||
switch signerRequestModel.approved {
|
||||
case .none:
|
||||
Text("Pending", comment: "Text indicating that a signer request is pending approval.")
|
||||
case .some(let approved):
|
||||
Text(
|
||||
"Approved: \(approved.description)",
|
||||
comment: "Text indicating that a signer request is either approved or not approved.")
|
||||
}
|
||||
|
||||
Text(signerRequestModel.decidedAt?.description ?? "Unknown decisionTimestamp")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,72 @@
|
||||
// Created by Terry Yiu on 1/20/25.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct HomeView: View {
|
||||
@Query(
|
||||
filter: #Predicate<SignerRequestModel> { signerRequestModel in
|
||||
signerRequestModel.approved == nil
|
||||
},
|
||||
sort: \SignerRequestModel.createdAt, order: .reverse
|
||||
)
|
||||
private var signerRequestModels: [SignerRequestModel]
|
||||
|
||||
var body: some View {
|
||||
EmptyView()
|
||||
if let signerRequestModel = signerRequestModels.first {
|
||||
Section {
|
||||
VStack {
|
||||
Text(signerRequestModel.type.rawValue)
|
||||
Text(signerRequestModel.returnType.rawValue)
|
||||
Text(signerRequestModel.compressionType.rawValue)
|
||||
Text(signerRequestModel.createdAt.description)
|
||||
Text(signerRequestModel.callbackURL ?? "Unknown callbackURL")
|
||||
Text(signerRequestModel.pubkey ?? "Unknown pubkey")
|
||||
Text(signerRequestModel.sourceApplication ?? "Unknown sourceApplication")
|
||||
Text(signerRequestModel.targetApplication ?? "Unknown targetApplication")
|
||||
|
||||
HStack {
|
||||
Button(
|
||||
String(localized: "Allow", comment: "Button to allow a permission request."),
|
||||
systemImage: "checkmark"
|
||||
) {
|
||||
signerRequestModel.approved = true
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
Button(
|
||||
String(localized: "Deny", comment: "Button to deny a permission request."),
|
||||
systemImage: "xmark"
|
||||
) {
|
||||
signerRequestModel.approved = false
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Text(
|
||||
"Nothing to approve yet",
|
||||
comment:
|
||||
"""
|
||||
Text on Home tab to indicate that there are no permission requests to review for approval.
|
||||
"""
|
||||
)
|
||||
.font(.headline)
|
||||
|
||||
Text(
|
||||
"Why not explore your favorite Nostr app a bit?",
|
||||
comment:
|
||||
"""
|
||||
Text on Home tab to suggest what the user could do instead
|
||||
since there are no permission requests to review for approval.
|
||||
"""
|
||||
)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
// Created by Terry Yiu on 1/20/25.
|
||||
//
|
||||
|
||||
import NostrSDK
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct LoggedInView: View {
|
||||
let publicKey: String
|
||||
|
||||
@State var selectedTab: YetiTab = .home
|
||||
|
||||
var body: some View {
|
||||
@@ -18,7 +22,7 @@ struct LoggedInView: View {
|
||||
case .home:
|
||||
HomeView()
|
||||
case .permissions:
|
||||
PermissionsView()
|
||||
PermissionsView(publicKey: publicKey)
|
||||
case .history:
|
||||
HistoryView()
|
||||
case .settings:
|
||||
@@ -64,5 +68,5 @@ enum YetiTab: CustomStringConvertible, CaseIterable {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LoggedInView()
|
||||
LoggedInView(publicKey: Keypair()!.publicKey.hex)
|
||||
}
|
||||
|
||||
@@ -11,82 +11,82 @@ import SwiftUI
|
||||
struct NostrClientView: View {
|
||||
@Query private var nostrClientModels: [NostrClientModel]
|
||||
|
||||
init(nostrClientModelId: String) {
|
||||
self._nostrClientModels = Query(filter: #Predicate<NostrClientModel> {
|
||||
$0.id == nostrClientModelId
|
||||
})
|
||||
init(nostrClientModelName: String) {
|
||||
self._nostrClientModels = Query(filter: NostrClientModel.predicateByName(nostrClientModelName))
|
||||
}
|
||||
|
||||
var nostrClientModel: NostrClientModel {
|
||||
nostrClientModels.first!
|
||||
var nostrClientModel: NostrClientModel? {
|
||||
nostrClientModels.first
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
let bindableNostrClientModel = Bindable(nostrClientModel)
|
||||
if let nostrClientModel {
|
||||
let bindableNostrClientModel = Bindable(nostrClientModel)
|
||||
|
||||
Text(bindableNostrClientModel.id)
|
||||
.font(.headline)
|
||||
Text(bindableNostrClientModel.name.wrappedValue)
|
||||
.font(.headline)
|
||||
|
||||
List {
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Read your profile",
|
||||
comment: "Permission toggle to allow Nostr client to read your profile."
|
||||
),
|
||||
isOn: bindableNostrClientModel.readPublicKeyPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Get relays",
|
||||
comment: "Permission toggle to allow Nostr client to get relays."
|
||||
),
|
||||
isOn: bindableNostrClientModel.getRelaysPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Encrypt DMs",
|
||||
comment: "Permission toggle to allow Nostr client to encrypt direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip44EncryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt DMs",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip44DecryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Encrypt legacy DMs",
|
||||
comment: "Permission toggle to allow Nostr client to encrypt legacy direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip04EncryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt legacy DMs",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt legacy direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip04DecryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt zap events",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt zap events."
|
||||
),
|
||||
isOn: bindableNostrClientModel.decryptZapEventPermission
|
||||
)
|
||||
|
||||
ForEach(bindableNostrClientModel.signEventPermissions, id: \.self) { signEventPermissionModel in
|
||||
List {
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Sign kind \(signEventPermissionModel.kind.wrappedValue.description) events",
|
||||
comment: "Permission toggle to allow Nostr client to sign events of a specific kind.."
|
||||
localized: "Read your profile",
|
||||
comment: "Permission toggle to allow Nostr client to read your profile."
|
||||
),
|
||||
isOn: signEventPermissionModel.allowed
|
||||
isOn: bindableNostrClientModel.readPublicKeyPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Get relays",
|
||||
comment: "Permission toggle to allow Nostr client to get relays."
|
||||
),
|
||||
isOn: bindableNostrClientModel.getRelaysPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Encrypt DMs",
|
||||
comment: "Permission toggle to allow Nostr client to encrypt direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip44EncryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt DMs",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip44DecryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Encrypt legacy DMs",
|
||||
comment: "Permission toggle to allow Nostr client to encrypt legacy direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip04EncryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt legacy DMs",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt legacy direct messages."
|
||||
),
|
||||
isOn: bindableNostrClientModel.nip04DecryptPermission
|
||||
)
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Decrypt zap events",
|
||||
comment: "Permission toggle to allow Nostr client to decrypt zap events."
|
||||
),
|
||||
isOn: bindableNostrClientModel.decryptZapEventPermission
|
||||
)
|
||||
|
||||
ForEach(bindableNostrClientModel.signEventPermissions, id: \.self) { signEventPermissionModel in
|
||||
Toggle(
|
||||
String(
|
||||
localized: "Sign kind \(signEventPermissionModel.kind.wrappedValue.description) events",
|
||||
comment: "Permission toggle to allow Nostr client to sign events of a specific kind.."
|
||||
),
|
||||
isOn: signEventPermissionModel.approved
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,13 +94,13 @@ struct NostrClientView: View {
|
||||
}
|
||||
|
||||
extension NostrClientModel {
|
||||
static func predicateById(_ id: String) -> Predicate<NostrClientModel> {
|
||||
static func predicateByName(_ name: String) -> Predicate<NostrClientModel> {
|
||||
#Predicate<NostrClientModel> { nostrClientModel in
|
||||
nostrClientModel.id == id
|
||||
nostrClientModel.name == name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NostrClientView(nostrClientModelId: UUID().uuidString)
|
||||
NostrClientView(nostrClientModelName: UUID().uuidString)
|
||||
}
|
||||
|
||||
@@ -5,41 +5,46 @@
|
||||
// Created by Terry Yiu on 1/20/25.
|
||||
//
|
||||
|
||||
import NostrSDK
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
struct PermissionsView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Query(sort: \NostrClientModel.id) var nostrClientModels: [NostrClientModel]
|
||||
@Query var profileSettingsModels: [ProfileSettingsModel]
|
||||
|
||||
init(publicKey: String) {
|
||||
var descriptor = FetchDescriptor<ProfileSettingsModel>(
|
||||
predicate: #Predicate { profileSettingsModel in
|
||||
profileSettingsModel.publicKey == publicKey
|
||||
}
|
||||
)
|
||||
descriptor.fetchLimit = 1
|
||||
|
||||
self._profileSettingsModels = Query(descriptor)
|
||||
}
|
||||
|
||||
var profileSettingsModel: ProfileSettingsModel? {
|
||||
profileSettingsModels.first
|
||||
}
|
||||
|
||||
var nostrClientModels: [NostrClientModel] {
|
||||
profileSettingsModel?.nostrClientModels ?? []
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(nostrClientModels, id: \.self) { nostrClientModel in
|
||||
NavigationLink(
|
||||
nostrClientModel.id,
|
||||
destination: NostrClientView(nostrClientModelId: nostrClientModel.id)
|
||||
nostrClientModel.name,
|
||||
destination: NostrClientView(nostrClientModelName: nostrClientModel.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO Remove this dummy code
|
||||
.onAppear(perform: addNostrClientModel)
|
||||
}
|
||||
|
||||
// TODO Remove this dummy code
|
||||
private func addNostrClientModel() {
|
||||
withAnimation {
|
||||
let newNostrClientModel = NostrClientModel(id: Date().timeIntervalSince1970.rounded().description)
|
||||
newNostrClientModel.signEventPermissions = [
|
||||
SignEventPermissionModel(kind: 1, allowed: true),
|
||||
SignEventPermissionModel(kind: 31923, allowed: true)
|
||||
]
|
||||
modelContext.insert(newNostrClientModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PermissionsView()
|
||||
PermissionsView(publicKey: Keypair()!.publicKey.hex)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ You’re set for now. You’ll need to come back here with every new app and app
|
||||
profileSettingsModel.signingPolicy = signingPolicy
|
||||
modelContext.insert(profileSettingsModel)
|
||||
|
||||
generalSettingsModels.first!.activePublicKey = keypair.publicKey.hex
|
||||
generalSettingsModels.first?.activePublicKey = keypair.publicKey.hex
|
||||
})
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ struct YetiApp: App {
|
||||
init() {
|
||||
let schema = Schema([
|
||||
GeneralSettingsModel.self,
|
||||
ProfileSettingsModel.self
|
||||
ProfileSettingsModel.self,
|
||||
SignerRequestModel.self
|
||||
])
|
||||
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user