@candypoets/nipworker 0.96.3 → 0.96.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -73
- package/crates/native-ffi/README.md +14 -151
- package/crates/native-ffi/ios/README.md +4 -50
- package/crates/native-ffi/react-native/README.md +8 -7
- package/crates/native-ffi/react-native/android/build.gradle +1 -0
- package/crates/native-ffi/react-native/android/src/main/cpp/NipworkerByteRuntime.cpp +348 -0
- package/crates/native-ffi/react-native/android/src/main/java/com/candypoets/nipworker/reactnative/NipworkerHooks.kt +292 -0
- package/crates/native-ffi/react-native/android/src/main/java/com/candypoets/nipworker/reactnative/NipworkerReactNativeModule.kt +283 -89
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/AddressPointer.java +75 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleBlock.java +102 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleBlockType.java +23 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleEntity.java +80 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleInline.java +85 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleInlineType.java +24 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/BadgeAwardRecipient.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/BufferFull.java +52 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheInput.java +16 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheInputMessage.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheRequest.java +84 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheResponse.java +67 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CashuData.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ChatLimiterPipeConfig.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CodeData.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ConnectionStatus.java +68 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Contact.java +70 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ContentBlock.java +70 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ContentData.java +23 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Coordinate.java +75 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CountResponse.java +60 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CounterPipeConfig.java +67 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/DLEQProof.java +69 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Emoji.java +62 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/EmojiData.java +68 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Eoce.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/EventPointer.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/GetPublicKey.java +43 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HTLCWitness.java +64 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HashtagData.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HistoryTag.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ImageData.java +67 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ImetaTag.java +100 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind0Parsed.java +168 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind10002Parsed.java +59 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind10019Parsed.java +73 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1018Parsed.java +79 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1068Parsed.java +107 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1111Parsed.java +159 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1311Parsed.java +91 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind17375Parsed.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind17Parsed.java +81 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1Parsed.java +109 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind20Parsed.java +107 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind22Parsed.java +111 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind30023Parsed.java +113 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind3Parsed.java +59 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind4Parsed.java +78 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind6Parsed.java +53 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7374Parsed.java +66 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7375Parsed.java +79 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7376Parsed.java +100 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7Parsed.java +81 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind8Parsed.java +78 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind9321Parsed.java +117 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind9735Parsed.java +119 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/KindFilterPipeConfig.java +60 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LinkPreviewData.java +73 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ListParsed.java +123 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveActivityRef.java +72 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveChatParticipant.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveChatThreadRef.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MainContent.java +20 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MainMessage.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MediaGroupData.java +58 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MediaItem.java +58 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Message.java +24 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MessageType.java +19 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MintInfo.java +65 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MintProofs.java +66 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MuteFilterPipeConfig.java +84 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip07.java +43 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip46Bunker.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip46QR.java +61 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NostrData.java +81 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NostrEvent.java +95 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NpubLimiterKey.java +15 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NpubLimiterPipeConfig.java +64 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/P2PKWitness.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsePipeConfig.java +43 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedData.java +39 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedDataUnion.java +35 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedEvent.java +108 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Pipe.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PipeConfig.java +23 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PipelineConfig.java +59 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PollOption.java +62 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PollType.java +15 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreGenericParsed.java +213 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreParticipant.java +73 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreRefEvent.java +67 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PrivateKey.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ProfilePointer.java +64 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Proof.java +90 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ProofVerificationPipeConfig.java +52 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Pubkey.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Publish.java +80 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Raw.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ReactionType.java +17 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/RelayInfo.java +63 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Request.java +134 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SaveToDbPipeConfig.java +43 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SerializeEventsPipeConfig.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SetSigner.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignEvent.java +54 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignedEvent.java +54 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerOp.java +19 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerRequest.java +80 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerResponse.java +64 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerType.java +18 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/StringVec.java +57 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Subscribe.java +72 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SubscribeKind.java +19 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SubscriptionConfig.java +95 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Tag.java +64 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Template.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Unsubscribe.java +55 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ValidProofs.java +59 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/VideoData.java +67 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/VideoVariant.java +102 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Witness.java +17 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/WitnessString.java +54 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/WorkerMessage.java +74 -0
- package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ZapRequest.java +83 -0
- package/crates/native-ffi/react-native/ios/NipworkerReactNativeModule.h +18 -0
- package/crates/native-ffi/react-native/ios/NipworkerReactNativeModule.mm +263 -8
- package/dist/EngineManager.d.ts +1 -0
- package/dist/EngineManager.d.ts.map +1 -1
- package/dist/EngineManager.js +143 -137
- package/dist/NostrManager.d.ts +1 -0
- package/dist/NostrManager.d.ts.map +1 -1
- package/dist/NostrManager.js +49 -44
- package/dist/connections/index.js +87 -87
- package/dist/engine/index.js +119 -119
- package/dist/engine.d.ts.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +27 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/legacy.d.ts.map +1 -1
- package/dist/lib/ArrayBufferReader.d.ts +2 -1
- package/dist/lib/ArrayBufferReader.d.ts.map +1 -1
- package/dist/lib/BaseBackend.d.ts +5 -3
- package/dist/lib/BaseBackend.d.ts.map +1 -1
- package/dist/manager.d.ts +2 -6
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +10 -28
- package/dist/react-native.d.ts +33 -4
- package/dist/react-native.d.ts.map +1 -1
- package/dist/react-native.js +400 -122
- package/dist/scheduleMicrotask.js +58 -50
- package/package.json +13 -11
- package/swift/Package.swift +36 -0
- package/swift/README.md +438 -0
- package/swift/Sources/NipworkerSwift/ArrayBufferReader.swift +136 -0
- package/swift/Sources/NipworkerSwift/AuthSession.swift +24 -0
- package/swift/Sources/NipworkerSwift/FFI.swift +94 -0
- package/swift/Sources/NipworkerSwift/FlatBufferHelpers.swift +273 -0
- package/swift/Sources/NipworkerSwift/Generated/message_generated.swift +6530 -0
- package/swift/Sources/NipworkerSwift/Hooks.swift +300 -0
- package/swift/Sources/NipworkerSwift/Models.swift +156 -0
- package/swift/Sources/NipworkerSwift/NostrManager.swift +594 -0
- package/swift/Sources/NipworkerSwift/PublishTracker.swift +40 -0
- package/swift/Sources/NipworkerSwift/Subscription.swift +44 -0
- package/swift/Sources/NipworkerSwift/WorkerMessageView.swift +145 -0
- package/dist/NativeBackend.d.ts +0 -53
- package/dist/NativeBackend.d.ts.map +0 -1
- package/dist/NativeBackend.js +0 -464
- package/dist/native.d.ts +0 -29
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -273
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Combine
|
|
3
|
+
import FlatBuffers
|
|
4
|
+
|
|
5
|
+
public extension Notification.Name {
|
|
6
|
+
static let nipworkerSubscriptionUpdated = Notification.Name("nipworkerSubscriptionUpdated")
|
|
7
|
+
static let nipworkerRelayStatusUpdated = Notification.Name("nipworkerRelayStatusUpdated")
|
|
8
|
+
static let nipworkerAuthUpdated = Notification.Name("nipworkerAuthUpdated")
|
|
9
|
+
|
|
10
|
+
static func nipworkerSubscriptionUpdated(subId: String) -> Notification.Name {
|
|
11
|
+
Notification.Name("nipworkerSubscriptionUpdated.\(subId)")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Weak reference box for the C callback
|
|
16
|
+
private final class ManagerBox {
|
|
17
|
+
weak var manager: NostrManager?
|
|
18
|
+
init(manager: NostrManager) { self.manager = manager }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public actor NostrManager {
|
|
22
|
+
private static let sharedRuntimeNotification = "NipworkerRuntimeDataNotification"
|
|
23
|
+
private static let sharedRuntimeDataKey = "data"
|
|
24
|
+
|
|
25
|
+
private static var reactNativeSharedManager: NostrManager?
|
|
26
|
+
|
|
27
|
+
private var handle: UnsafeMutableRawPointer?
|
|
28
|
+
private let ownsHandle: Bool
|
|
29
|
+
private var bufferViews: [String: SubscriptionBuffer] = [:]
|
|
30
|
+
private var relayStatuses: [String: RelayStatus] = [:]
|
|
31
|
+
private var activePubkey: String?
|
|
32
|
+
private var pendingSession: (type: String, payload: Any)?
|
|
33
|
+
private var sharedRuntimeObserver: NSObjectProtocol?
|
|
34
|
+
|
|
35
|
+
private var signContinuation: CheckedContinuation<NostrEvent, Error>?
|
|
36
|
+
private var pubkeyContinuation: CheckedContinuation<String, Error>?
|
|
37
|
+
|
|
38
|
+
private var boxPtr: UnsafeMutablePointer<ManagerBox>
|
|
39
|
+
|
|
40
|
+
public init(config: NostrManagerConfig = NostrManagerConfig()) {
|
|
41
|
+
self.handle = nil
|
|
42
|
+
self.ownsHandle = true
|
|
43
|
+
self.boxPtr = UnsafeMutablePointer<ManagerBox>.allocate(capacity: 1)
|
|
44
|
+
self.boxPtr.initialize(to: ManagerBox(manager: self))
|
|
45
|
+
|
|
46
|
+
self.handle = nipworker_init({ userdata, ptr, len in
|
|
47
|
+
guard let ptr = ptr else { return }
|
|
48
|
+
let data = Data(bytes: ptr, count: len)
|
|
49
|
+
nipworker_free_bytes(UnsafeMutablePointer(mutating: ptr), len)
|
|
50
|
+
|
|
51
|
+
let box = userdata!.assumingMemoryBound(to: ManagerBox.self).pointee
|
|
52
|
+
guard let manager = box.manager else { return }
|
|
53
|
+
|
|
54
|
+
Task {
|
|
55
|
+
await manager.handleNativeMessage(data)
|
|
56
|
+
}
|
|
57
|
+
}, self.boxPtr)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private init(borrowedHandle: UnsafeMutableRawPointer?) {
|
|
61
|
+
self.handle = borrowedHandle
|
|
62
|
+
self.ownsHandle = false
|
|
63
|
+
self.boxPtr = UnsafeMutablePointer<ManagerBox>.allocate(capacity: 1)
|
|
64
|
+
self.boxPtr.initialize(to: ManagerBox(manager: self))
|
|
65
|
+
|
|
66
|
+
self.sharedRuntimeObserver = NotificationCenter.default.addObserver(
|
|
67
|
+
forName: Notification.Name(rawValue: Self.sharedRuntimeNotification),
|
|
68
|
+
object: nil,
|
|
69
|
+
queue: .main
|
|
70
|
+
) { [weak self] notification in
|
|
71
|
+
guard let data = notification.userInfo?[Self.sharedRuntimeDataKey] as? Data else { return }
|
|
72
|
+
Task { [weak self] in
|
|
73
|
+
await self?.handleNativeMessage(data)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public static func reactNativeShared() -> NostrManager {
|
|
79
|
+
if let manager = reactNativeSharedManager {
|
|
80
|
+
return manager
|
|
81
|
+
}
|
|
82
|
+
let manager = NostrManager(borrowedHandle: nipworker_react_native_shared_handle_if_available())
|
|
83
|
+
reactNativeSharedManager = manager
|
|
84
|
+
return manager
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
deinit {
|
|
88
|
+
if ownsHandle, let handle = handle {
|
|
89
|
+
nipworker_deinit(handle)
|
|
90
|
+
}
|
|
91
|
+
if let observer = sharedRuntimeObserver {
|
|
92
|
+
NotificationCenter.default.removeObserver(observer)
|
|
93
|
+
sharedRuntimeObserver = nil
|
|
94
|
+
}
|
|
95
|
+
boxPtr.pointee.manager = nil
|
|
96
|
+
boxPtr.deinitialize(count: 1)
|
|
97
|
+
boxPtr.deallocate()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: - Subscribe
|
|
101
|
+
|
|
102
|
+
public func subscribe(
|
|
103
|
+
subscriptionId: String,
|
|
104
|
+
requests: [RequestObject],
|
|
105
|
+
options: SubscriptionConfig = SubscriptionConfig()
|
|
106
|
+
) -> SubscriptionBuffer {
|
|
107
|
+
let subId = subscriptionId
|
|
108
|
+
let existingRetained = subId.withCString { nipworker_retain_subscription(handle, $0) }
|
|
109
|
+
if existingRetained, let buffer = bufferView(for: subId) {
|
|
110
|
+
return buffer
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let fbData = buildSubscribeMessage(subId: subId, requests: requests, options: options)
|
|
114
|
+
let ok = fbData.withUnsafeBytes { bytes in
|
|
115
|
+
nipworker_subscribe_message(
|
|
116
|
+
handle,
|
|
117
|
+
bytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
|
118
|
+
bytes.count
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
guard ok, let buffer = bufferView(for: subId) else {
|
|
123
|
+
return SubscriptionBuffer(capacity: 4)
|
|
124
|
+
}
|
|
125
|
+
return buffer
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public func unsubscribe(subscriptionId: String) {
|
|
129
|
+
subscriptionId.withCString { nipworker_release_subscription(handle, $0) }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// MARK: - Publish
|
|
133
|
+
|
|
134
|
+
public func publish(
|
|
135
|
+
publishId: String,
|
|
136
|
+
event: NostrEvent,
|
|
137
|
+
defaultRelays: [String] = [],
|
|
138
|
+
optimisticSubIds: [String] = []
|
|
139
|
+
) -> SubscriptionBuffer {
|
|
140
|
+
let fbData = buildPublishMessage(
|
|
141
|
+
publishId: publishId,
|
|
142
|
+
event: event,
|
|
143
|
+
defaultRelays: defaultRelays,
|
|
144
|
+
optimisticSubIds: optimisticSubIds
|
|
145
|
+
)
|
|
146
|
+
let ok = fbData.withUnsafeBytes { bytes in
|
|
147
|
+
nipworker_publish_message(
|
|
148
|
+
handle,
|
|
149
|
+
bytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
|
150
|
+
bytes.count
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
guard ok, let buffer = bufferView(for: publishId) else {
|
|
155
|
+
return SubscriptionBuffer(capacity: 4)
|
|
156
|
+
}
|
|
157
|
+
return buffer
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public func releasePublish(publishId: String) {
|
|
161
|
+
publishId.withCString { nipworker_release_subscription(handle, $0) }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: - Signer
|
|
165
|
+
|
|
166
|
+
public func setSigner(_ signer: SignerTypeName) {
|
|
167
|
+
switch signer {
|
|
168
|
+
case .pubkey(let pubkey):
|
|
169
|
+
self.activePubkey = pubkey
|
|
170
|
+
DispatchQueue.main.async {
|
|
171
|
+
NotificationCenter.default.post(
|
|
172
|
+
name: .nipworkerAuthUpdated,
|
|
173
|
+
object: nil,
|
|
174
|
+
userInfo: ["pubkey": pubkey, "hasSigner": false]
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
case .privkey(let secret):
|
|
178
|
+
let fbData = buildSetPrivateKeyMessage(secret: secret)
|
|
179
|
+
fbData.withUnsafeBytes { bytes in
|
|
180
|
+
nipworker_handle_message(
|
|
181
|
+
handle,
|
|
182
|
+
bytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
|
183
|
+
bytes.count
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
Task {
|
|
187
|
+
_ = try? await getPublicKey()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public func getPublicKey() async throws -> String {
|
|
193
|
+
let fbData = buildGetPublicKeyMessage()
|
|
194
|
+
fbData.withUnsafeBytes { bytes in
|
|
195
|
+
nipworker_handle_message(
|
|
196
|
+
handle,
|
|
197
|
+
bytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
|
198
|
+
bytes.count
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
203
|
+
self.pubkeyContinuation = continuation
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public func signEvent(_ template: EventTemplate) async throws -> NostrEvent {
|
|
208
|
+
let fbData = buildSignEventMessage(template: template)
|
|
209
|
+
fbData.withUnsafeBytes { bytes in
|
|
210
|
+
nipworker_handle_message(
|
|
211
|
+
handle,
|
|
212
|
+
bytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
|
|
213
|
+
bytes.count
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
218
|
+
self.signContinuation = continuation
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public func logout() {
|
|
223
|
+
activePubkey = nil
|
|
224
|
+
pendingSession = nil
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// MARK: - Cleanup
|
|
228
|
+
|
|
229
|
+
public func cleanup() {
|
|
230
|
+
nipworker_cleanup_subscriptions(handle)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// MARK: - Read Events (called by Subscription)
|
|
234
|
+
|
|
235
|
+
public func readEvents(for subId: String, from position: Int) -> (events: [NostrEvent], newPosition: Int) {
|
|
236
|
+
guard let buffer = bufferView(for: subId) else { return ([], position) }
|
|
237
|
+
let result = ArrayBufferReader.readMessages(buffer: buffer, lastReadPosition: position)
|
|
238
|
+
let events = result.messages.compactMap { parseWorkerMessage($0) }
|
|
239
|
+
return (events, result.newReadPosition)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public func readWorkerMessages(for subId: String, from position: Int) -> (messages: [WorkerMessageView], newPosition: Int) {
|
|
243
|
+
guard let buffer = bufferView(for: subId) else { return ([], position) }
|
|
244
|
+
let result = ArrayBufferReader.readMessages(buffer: buffer, lastReadPosition: position)
|
|
245
|
+
let messages = result.messages.compactMap { WorkerMessageView($0) }
|
|
246
|
+
return (messages, result.newReadPosition)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public func readPublishStatuses(for publishId: String, from position: Int) -> (statuses: [String: PublishStatus], newPosition: Int) {
|
|
250
|
+
guard let buffer = bufferView(for: publishId) else { return ([:], position) }
|
|
251
|
+
let result = ArrayBufferReader.readMessages(buffer: buffer, lastReadPosition: position)
|
|
252
|
+
var statuses: [String: PublishStatus] = [:]
|
|
253
|
+
for data in result.messages {
|
|
254
|
+
if let (url, status) = parsePublishStatus(data) {
|
|
255
|
+
statuses[url] = status
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return (statuses, result.newReadPosition)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public func getRelayStatuses() -> [String: RelayStatus] {
|
|
262
|
+
return relayStatuses
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public func getActivePubkey() -> String? {
|
|
266
|
+
return activePubkey
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// MARK: - Callback Handling
|
|
270
|
+
|
|
271
|
+
private func handleNativeMessage(_ data: Data) {
|
|
272
|
+
if let routeSubId = decodeRouteWakeFrame(data) {
|
|
273
|
+
handleSubscriptionMessage(subId: routeSubId)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
guard data.count >= 4 else { return }
|
|
277
|
+
let bb = ByteBuffer(data: data)
|
|
278
|
+
let rootOffset = bb.read(def: Int32.self, position: 0)
|
|
279
|
+
if rootOffset < 0 || Int(rootOffset) >= data.count { return }
|
|
280
|
+
let workerMsg = nostr_fb_WorkerMessage(bb, o: rootOffset)
|
|
281
|
+
let subId = workerMsg.subId ?? ""
|
|
282
|
+
|
|
283
|
+
if subId == "crypto" {
|
|
284
|
+
handleCryptoMessage(data)
|
|
285
|
+
} else if subId.isEmpty {
|
|
286
|
+
handleDirectResponse(data)
|
|
287
|
+
} else {
|
|
288
|
+
handleSubscriptionMessage(subId: subId)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private func handleSubscriptionMessage(subId: String) {
|
|
293
|
+
DispatchQueue.main.async {
|
|
294
|
+
NotificationCenter.default.post(
|
|
295
|
+
name: .nipworkerSubscriptionUpdated(subId: subId),
|
|
296
|
+
object: nil,
|
|
297
|
+
userInfo: ["subId": subId]
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func handleDirectResponse(_ payload: Data) {
|
|
303
|
+
guard payload.count >= 4 else { return }
|
|
304
|
+
|
|
305
|
+
let firstWord = Int(payload.withUnsafeBytes { $0.load(fromByteOffset: 0, as: UInt32.self).littleEndian })
|
|
306
|
+
if firstWord > 0, payload.count == 4 + firstWord {
|
|
307
|
+
_parseDirectResponse(payload.subdata(in: 4..<4 + firstWord))
|
|
308
|
+
} else {
|
|
309
|
+
_parseDirectResponse(payload)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private func handleCryptoMessage(_ payload: Data) {
|
|
314
|
+
_parseCryptoResponse(payload)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private func decodeRouteWakeFrame(_ data: Data) -> String? {
|
|
318
|
+
guard data.count >= 8 else { return nil }
|
|
319
|
+
guard [UInt8](data.prefix(4)) == [0x4e, 0x57, 0x52, 0x31] else { return nil }
|
|
320
|
+
let subIdLength = Int(data.withUnsafeBytes {
|
|
321
|
+
$0.load(fromByteOffset: 4, as: UInt32.self).littleEndian
|
|
322
|
+
})
|
|
323
|
+
guard subIdLength > 0, subIdLength == data.count - 8 else { return nil }
|
|
324
|
+
return String(data: data.subdata(in: 8..<data.count), encoding: .utf8)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// MARK: - Helpers
|
|
328
|
+
|
|
329
|
+
private func bufferView(for id: String) -> SubscriptionBuffer? {
|
|
330
|
+
let nativePointer = id.withCString { nipworker_subscription_buffer_ptr(handle, $0) }
|
|
331
|
+
let nativeLength = id.withCString { nipworker_subscription_buffer_len(handle, $0) }
|
|
332
|
+
guard let nativePointer, nativeLength > 0 else {
|
|
333
|
+
bufferViews.removeValue(forKey: id)
|
|
334
|
+
return nil
|
|
335
|
+
}
|
|
336
|
+
if let existing = bufferViews[id],
|
|
337
|
+
existing.pointer == UnsafeMutableRawPointer(nativePointer),
|
|
338
|
+
existing.capacity == nativeLength {
|
|
339
|
+
return existing
|
|
340
|
+
}
|
|
341
|
+
let buffer = SubscriptionBuffer(pointer: UnsafeMutableRawPointer(nativePointer), capacity: nativeLength)
|
|
342
|
+
bufferViews[id] = buffer
|
|
343
|
+
return buffer
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public nonisolated func createShortId(_ input: String) -> String {
|
|
347
|
+
if input.count < 64 { return input }
|
|
348
|
+
var hash: Int32 = 0
|
|
349
|
+
for char in input.utf16 {
|
|
350
|
+
hash = (hash << 5) &- hash &+ Int32(char)
|
|
351
|
+
}
|
|
352
|
+
let shortId = String(Int(hash).magnitude, radix: 36)
|
|
353
|
+
return String(shortId.prefix(63))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// MARK: - FlatBuffer Parsing
|
|
357
|
+
|
|
358
|
+
private func parseWorkerMessage(_ data: Data) -> NostrEvent? {
|
|
359
|
+
let bb = ByteBuffer(data: data)
|
|
360
|
+
let rootOffset = bb.read(def: Int32.self, position: 0)
|
|
361
|
+
let workerMsg = nostr_fb_WorkerMessage(bb, o: rootOffset)
|
|
362
|
+
|
|
363
|
+
switch workerMsg.contentType {
|
|
364
|
+
case .nostrevent:
|
|
365
|
+
guard let event = workerMsg.content(type: nostr_fb_NostrEvent.self) else { return nil }
|
|
366
|
+
return NostrEvent(
|
|
367
|
+
id: event.id,
|
|
368
|
+
pubkey: event.pubkey,
|
|
369
|
+
kind: event.kind,
|
|
370
|
+
content: event.content,
|
|
371
|
+
tags: event.tags.map { $0.items.compactMap { $0 } },
|
|
372
|
+
createdAt: Int(event.createdAt),
|
|
373
|
+
sig: event.sig
|
|
374
|
+
)
|
|
375
|
+
case .parsedevent:
|
|
376
|
+
guard let parsed = workerMsg.content(type: nostr_fb_ParsedEvent.self) else { return nil }
|
|
377
|
+
return NostrEvent(
|
|
378
|
+
id: parsed.id,
|
|
379
|
+
pubkey: parsed.pubkey,
|
|
380
|
+
kind: parsed.kind,
|
|
381
|
+
content: "",
|
|
382
|
+
tags: parsed.tags.map { $0.items.compactMap { $0 } },
|
|
383
|
+
createdAt: Int(parsed.createdAt),
|
|
384
|
+
sig: ""
|
|
385
|
+
)
|
|
386
|
+
case .raw:
|
|
387
|
+
guard let raw = workerMsg.content(type: nostr_fb_Raw.self) else { return nil }
|
|
388
|
+
return parseRawEventMessage(raw.raw)
|
|
389
|
+
default:
|
|
390
|
+
return nil
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private func parsePublishStatus(_ data: Data) -> (String, PublishStatus)? {
|
|
395
|
+
let bb = ByteBuffer(data: data)
|
|
396
|
+
let rootOffset = bb.read(def: Int32.self, position: 0)
|
|
397
|
+
let workerMsg = nostr_fb_WorkerMessage(bb, o: rootOffset)
|
|
398
|
+
|
|
399
|
+
switch workerMsg.contentType {
|
|
400
|
+
case .connectionstatus:
|
|
401
|
+
guard let status = workerMsg.content(type: nostr_fb_ConnectionStatus.self) else { return nil }
|
|
402
|
+
let relayUrl: String = status.relayUrl
|
|
403
|
+
let statusStr: String = status.status
|
|
404
|
+
let message: String = status.message ?? ""
|
|
405
|
+
switch statusStr {
|
|
406
|
+
case "ok":
|
|
407
|
+
return (relayUrl, .success(message))
|
|
408
|
+
case "failed":
|
|
409
|
+
return (relayUrl, .failed(message))
|
|
410
|
+
default:
|
|
411
|
+
return (relayUrl, .pending)
|
|
412
|
+
}
|
|
413
|
+
case .raw:
|
|
414
|
+
guard let raw = workerMsg.content(type: nostr_fb_Raw.self) else { return nil }
|
|
415
|
+
let text: String = raw.raw
|
|
416
|
+
if text.contains("\"OK\"") || text.contains("\"ok\"") {
|
|
417
|
+
return (text, .success(text))
|
|
418
|
+
} else if text.contains("NOTICE") {
|
|
419
|
+
return (text, .failed(text))
|
|
420
|
+
}
|
|
421
|
+
return nil
|
|
422
|
+
default:
|
|
423
|
+
return nil
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private func _parseDirectResponse(_ data: Data) {
|
|
428
|
+
guard data.count >= 4 else { return }
|
|
429
|
+
let bb = ByteBuffer(data: data)
|
|
430
|
+
let rootOffset = bb.read(def: Int32.self, position: 0)
|
|
431
|
+
guard rootOffset >= 0, Int(rootOffset) < data.count else { return }
|
|
432
|
+
let workerMsg = nostr_fb_WorkerMessage(bb, o: rootOffset)
|
|
433
|
+
|
|
434
|
+
switch workerMsg.contentType {
|
|
435
|
+
case .pubkey:
|
|
436
|
+
guard let pubkey = workerMsg.content(type: nostr_fb_Pubkey.self) else { return }
|
|
437
|
+
let key: String = pubkey.pubkey
|
|
438
|
+
activePubkey = key
|
|
439
|
+
if let continuation = pubkeyContinuation {
|
|
440
|
+
pubkeyContinuation = nil
|
|
441
|
+
continuation.resume(returning: key)
|
|
442
|
+
}
|
|
443
|
+
DispatchQueue.main.async {
|
|
444
|
+
NotificationCenter.default.post(
|
|
445
|
+
name: .nipworkerAuthUpdated,
|
|
446
|
+
object: nil,
|
|
447
|
+
userInfo: ["pubkey": key, "hasSigner": true]
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
case .signedevent:
|
|
451
|
+
guard let signed = workerMsg.content(type: nostr_fb_SignedEvent.self),
|
|
452
|
+
let event = signed.event else { return }
|
|
453
|
+
let nostrEvent = NostrEvent(
|
|
454
|
+
id: event.id,
|
|
455
|
+
pubkey: event.pubkey,
|
|
456
|
+
kind: event.kind,
|
|
457
|
+
content: event.content,
|
|
458
|
+
tags: event.tags.map { $0.items.compactMap { $0 } },
|
|
459
|
+
createdAt: Int(event.createdAt),
|
|
460
|
+
sig: event.sig
|
|
461
|
+
)
|
|
462
|
+
if let continuation = signContinuation {
|
|
463
|
+
signContinuation = nil
|
|
464
|
+
continuation.resume(returning: nostrEvent)
|
|
465
|
+
}
|
|
466
|
+
case .connectionstatus:
|
|
467
|
+
guard let status = workerMsg.content(type: nostr_fb_ConnectionStatus.self) else { return }
|
|
468
|
+
let url: String = status.relayUrl
|
|
469
|
+
let statusStr: String = status.status
|
|
470
|
+
let message: String = status.message ?? ""
|
|
471
|
+
let relayStatus: RelayStatus
|
|
472
|
+
switch statusStr {
|
|
473
|
+
case "ok":
|
|
474
|
+
relayStatus = .connected
|
|
475
|
+
case "failed":
|
|
476
|
+
relayStatus = .failed
|
|
477
|
+
case "closed":
|
|
478
|
+
relayStatus = .closed(message.isEmpty ? nil : message)
|
|
479
|
+
default:
|
|
480
|
+
relayStatus = .failed
|
|
481
|
+
}
|
|
482
|
+
relayStatuses[url] = relayStatus
|
|
483
|
+
DispatchQueue.main.async {
|
|
484
|
+
NotificationCenter.default.post(
|
|
485
|
+
name: .nipworkerRelayStatusUpdated,
|
|
486
|
+
object: nil,
|
|
487
|
+
userInfo: ["url": url, "status": relayStatus]
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
default:
|
|
491
|
+
break
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private func _parseCryptoResponse(_ data: Data) {
|
|
496
|
+
let bb = ByteBuffer(data: data)
|
|
497
|
+
let rootOffset = bb.read(def: Int32.self, position: 0)
|
|
498
|
+
let workerMsg = nostr_fb_WorkerMessage(bb, o: rootOffset)
|
|
499
|
+
|
|
500
|
+
guard workerMsg.contentType == .raw else { return }
|
|
501
|
+
guard let raw = workerMsg.content(type: nostr_fb_Raw.self) else { return }
|
|
502
|
+
let jsonStr: String = raw.raw
|
|
503
|
+
|
|
504
|
+
guard let jsonData = jsonStr.data(using: .utf8),
|
|
505
|
+
let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else { return }
|
|
506
|
+
|
|
507
|
+
let op = json["op"] as? String
|
|
508
|
+
let result = json["result"] as? String
|
|
509
|
+
let error = json["error"] as? String
|
|
510
|
+
|
|
511
|
+
if let error = error, !error.isEmpty {
|
|
512
|
+
if let continuation = pubkeyContinuation {
|
|
513
|
+
pubkeyContinuation = nil
|
|
514
|
+
continuation.resume(throwing: NostrManagerError.cryptoError(error))
|
|
515
|
+
}
|
|
516
|
+
if let continuation = signContinuation {
|
|
517
|
+
signContinuation = nil
|
|
518
|
+
continuation.resume(throwing: NostrManagerError.cryptoError(error))
|
|
519
|
+
}
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
switch op {
|
|
524
|
+
case "get_public_key":
|
|
525
|
+
if let key = result {
|
|
526
|
+
activePubkey = key
|
|
527
|
+
if let continuation = pubkeyContinuation {
|
|
528
|
+
pubkeyContinuation = nil
|
|
529
|
+
continuation.resume(returning: key)
|
|
530
|
+
}
|
|
531
|
+
DispatchQueue.main.async {
|
|
532
|
+
NotificationCenter.default.post(
|
|
533
|
+
name: .nipworkerAuthUpdated,
|
|
534
|
+
object: nil,
|
|
535
|
+
userInfo: ["pubkey": key, "hasSigner": true]
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
case "sign_event":
|
|
540
|
+
if let eventJson = result,
|
|
541
|
+
let eventData = eventJson.data(using: String.Encoding.utf8),
|
|
542
|
+
let eventDict = try? JSONSerialization.jsonObject(with: eventData) as? [String: Any] {
|
|
543
|
+
let event = parseEventDict(eventDict)
|
|
544
|
+
if let continuation = signContinuation {
|
|
545
|
+
signContinuation = nil
|
|
546
|
+
continuation.resume(returning: event)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
default:
|
|
550
|
+
break
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private func parseEventDict(_ dict: [String: Any]) -> NostrEvent {
|
|
555
|
+
return NostrEvent(
|
|
556
|
+
id: dict["id"] as? String ?? "",
|
|
557
|
+
pubkey: dict["pubkey"] as? String ?? "",
|
|
558
|
+
kind: uint16Value(dict["kind"]),
|
|
559
|
+
content: dict["content"] as? String ?? "",
|
|
560
|
+
tags: (dict["tags"] as? [[String]]) ?? [],
|
|
561
|
+
createdAt: intValue(dict["created_at"]),
|
|
562
|
+
sig: dict["sig"] as? String ?? ""
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private func parseRawEventMessage(_ raw: String) -> NostrEvent? {
|
|
567
|
+
guard let data = raw.data(using: .utf8),
|
|
568
|
+
let array = try? JSONSerialization.jsonObject(with: data) as? [Any],
|
|
569
|
+
array.count >= 3,
|
|
570
|
+
let messageType = array[0] as? String,
|
|
571
|
+
messageType == "EVENT",
|
|
572
|
+
let eventDict = array[2] as? [String: Any] else {
|
|
573
|
+
return nil
|
|
574
|
+
}
|
|
575
|
+
return parseEventDict(eventDict)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private func uint16Value(_ value: Any?) -> UInt16 {
|
|
579
|
+
if let value = value as? UInt16 { return value }
|
|
580
|
+
if let value = value as? Int { return UInt16(value) }
|
|
581
|
+
if let value = value as? NSNumber { return value.uint16Value }
|
|
582
|
+
return 0
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private func intValue(_ value: Any?) -> Int {
|
|
586
|
+
if let value = value as? Int { return value }
|
|
587
|
+
if let value = value as? NSNumber { return value.intValue }
|
|
588
|
+
return 0
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
public enum NostrManagerError: Error {
|
|
593
|
+
case cryptoError(String)
|
|
594
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Combine
|
|
3
|
+
|
|
4
|
+
@MainActor
|
|
5
|
+
public final class PublishTracker: ObservableObject {
|
|
6
|
+
@Published public private(set) var statuses: [String: PublishStatus] = [:]
|
|
7
|
+
|
|
8
|
+
public let publishId: String
|
|
9
|
+
private let manager: NostrManager
|
|
10
|
+
private var lastReadPosition: Int = 4
|
|
11
|
+
private var notificationToken: NSObjectProtocol?
|
|
12
|
+
|
|
13
|
+
public init(publishId: String, manager: NostrManager) {
|
|
14
|
+
self.publishId = publishId
|
|
15
|
+
self.manager = manager
|
|
16
|
+
|
|
17
|
+
self.notificationToken = NotificationCenter.default.addObserver(
|
|
18
|
+
forName: .nipworkerSubscriptionUpdated(subId: publishId),
|
|
19
|
+
object: nil,
|
|
20
|
+
queue: .main
|
|
21
|
+
) { [weak self] _ in
|
|
22
|
+
guard let self = self else { return }
|
|
23
|
+
Task { await self.sync() }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
deinit {
|
|
28
|
+
if let token = notificationToken {
|
|
29
|
+
NotificationCenter.default.removeObserver(token)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func sync() async {
|
|
34
|
+
let result = await manager.readPublishStatuses(for: publishId, from: lastReadPosition)
|
|
35
|
+
lastReadPosition = result.newPosition
|
|
36
|
+
for (url, status) in result.statuses {
|
|
37
|
+
statuses[url] = status
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Combine
|
|
3
|
+
|
|
4
|
+
@MainActor
|
|
5
|
+
public final class Subscription: ObservableObject {
|
|
6
|
+
@Published public private(set) var events: [NostrEvent] = []
|
|
7
|
+
@Published public private(set) var isComplete: Bool = false
|
|
8
|
+
|
|
9
|
+
public let id: String
|
|
10
|
+
private let manager: NostrManager
|
|
11
|
+
private var lastReadPosition: Int = 4
|
|
12
|
+
private var notificationToken: NSObjectProtocol?
|
|
13
|
+
|
|
14
|
+
public init(id: String, manager: NostrManager) {
|
|
15
|
+
self.id = id
|
|
16
|
+
self.manager = manager
|
|
17
|
+
|
|
18
|
+
self.notificationToken = NotificationCenter.default.addObserver(
|
|
19
|
+
forName: .nipworkerSubscriptionUpdated(subId: id),
|
|
20
|
+
object: nil,
|
|
21
|
+
queue: .main
|
|
22
|
+
) { [weak self] _ in
|
|
23
|
+
guard let self = self else { return }
|
|
24
|
+
Task { await self.sync() }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
deinit {
|
|
29
|
+
if let token = notificationToken {
|
|
30
|
+
NotificationCenter.default.removeObserver(token)
|
|
31
|
+
}
|
|
32
|
+
Task { [id, manager] in
|
|
33
|
+
await manager.unsubscribe(subscriptionId: id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private func sync() async {
|
|
38
|
+
let result = await manager.readEvents(for: id, from: lastReadPosition)
|
|
39
|
+
lastReadPosition = result.newPosition
|
|
40
|
+
if !result.events.isEmpty {
|
|
41
|
+
events.append(contentsOf: result.events)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|